WPF combobox-like custom control - wpf

I would like to create custom control that will look like standard WPF ComboBox, but instead of instead of having an ItemsPresenter in the popup there will be another custom control. So, I created a new class that derives from System.Windows.Controls.Control, added a IsDropDownOpen property and created a style that is actually a copy of default ComboBox style (main idea is that the Popup.IsOpen and ToggleButton.IsPressed properties are bound to the IsDropDownOpen property of the control).
The problem is that the Popup is not closed when I click outside of the control.
I took a look at the ComboBox class in the Reflector and found out that ComboBox used some logic to update the IsDropDownOpen property when it loses mouse capture. But that code uses some internal classes. Is there any alternative way to determine if the user clicked outside of the control and close the Popup?
UPD: I didn't find the way to attach a file to post, so I uploaded sample project here
There is a custom control that looks like ComboBox, but it has a TreeView in a popup. When you open popup and click outside of the control it closes automatically, but if you open popup, expand 'Item2' and then click outside the popup isn't closed. The question is how to fix this?

There is the Control.LostFocus event, maybe handling that would be sufficient for this.

This code solves the problem.
In the static contructor:
EventManager.RegisterClassHandler(typeof(CustomComboBox), Mouse.LostMouseCaptureEvent, new MouseEventHandler(OnMouseCaptureLost));
Event handler implementation:
private void OnMouseCaptureLost(object sender, MouseEventArgs e)
{
if (Mouse.Captured != _container)
{
if (e.OriginalSource != _container)
{
Mouse.Capture(_container, CaptureMode.SubTree);
e.Handled = true;
}
}
}

Related

How to use the context menu of the parent in a textbox

I have a listbox with several controls. Each control contains an custom autocompletebox which contains a System.Windows.Controls.AutoCompleteBox. When I right click on the control a custom contextm menu shows up. But with a right click on the textbox there appears the default contextmenu of the TextBox (with Copy, Cut and Paste).
My goal is to show my custom context menu with a right click on the TextBox.
Further informations:
My custom context menu is defined in the DataTemplate of the ListBox but I could define it in the Ressources or somewhere else as well.
I tried:
- when I null the context menu of the custom autocompletebox or the System.Windows.Controls.AutoCompleteBox of it, there is no effect at all
Thanks for every help ;)
You can either bind the context menu property to parent element's context menu or bind to the context menu once you define in the resource.xaml
Try using PreviewMouseDown instead of MouseDown to handle the MouseDown event.
In XAML:
<ListBox Margin="3" PreviewMouseDown="MouseDownOnListBox">
In Code:
private void MouseDownOnListBox(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Right)
{
//Display your context menu
}
}
If you are using PreviewMouseDown on a list, when you click anywhere on the list, this event will be triggered first.

Prevent control loosing focus when child gets focus

Ok so I have an issue where I have been trying to create a custom control in Silverlight. It is simply a button that when pressed opens a dropdown menu, however the dropdown menu is a child control. I have a property (isDropDownOpen) that controls whether the dropdown is open. I want that when the control loses focus that the property goes to false so implemented the following override in my class.
protected override void OnLostFocus(System.Windows.RoutedEventArgs e)
{
base.OnLostFocus(e);
Object focusedElement = FocusManager.GetFocusedElement();
FrameworkElement element = focusedElement as FrameworkElement;
if (element != null)
{
IsDropDownOpen = false;
}
}
The problem is that the control looses focus when the child control gains focus so the menu closes as soon as I click on anything other than the button. I can’t really see how to work around this, any ideas?
EDIT: Essentially what I wish to do is check if the item is a child before actually changing the property. In wpf I would do something using 'IsChild()' or '.containsFocus()' however these do not appear to e available in silverlight...
Using silverlight 5.0.
I found the following link to be a solution to my issue.
http://icircusmonkey.wordpress.com/2012/08/26/silverlight-how-to-close-the-popup-when-user-clicks-outside-of-the-control/
The solution in my case was to find the ancestor of the control (going up to the window) and subscribing an event handler to close the menu when a click is registered on the window/ancestor. Works perfectly.

Setting Default Keyboard Focus On Loading A UserControl

I have an MVVM setup with a mainwindow that contains a ContentControl.
I set this to a particular viewmodel which then maps to a view.
A view is a usercontrol.
I want to be able to set the default keyboard focus to a default element in the usercontrol(View) when it loads so the application can eventually be driven just by using up, down, left, right and enter.
Some of my failed attempts are setting
FocusManager.FocusedElement="{Binding ElementName=DefaultElement}"
in my content control tag. This sets the logical focus but not the keyboard focus
I'd rather keep the solution in xaml if possable but have tried placing the following in code behind.
Keyboard.Focus(DefaultElement);
This does not work but if I popup a message box first it does. I'm a little confused as to why.
MessageBox.Show(Keyboard.FocusedElement.ToString());
Keyboard.Focus(DefaultElement);
EDIT::::
I just placed this in my onloaded event of my user control. It seems to work but can anyone see any issues that might arrise at this priority level. I.E a circumstance when the action will never run?
Dispatcher.BeginInvoke(
DispatcherPriority.ContextIdle,
new Action(delegate()
{
Keyboard.Focus(DefaultElement);
}));
It seems that this wpf the you have to implement a workaround on a case by case basis. The solution that seemed to work best, most of the time for me was to insert the focus code inside the dispatcher when OnVisible was changed. This sets the focus not only when the View/Usercontrol loads but also if you a changing Views by way of Visibility. If you Hide and then Show a ContentControl that is mapped to your ViewModels then the Loaded event won't fire and you'll be forced to Mouse input, or tabbing (Not so good if you want to navigate your app with a remote control).
VisibilityChanged will always fire however. This is what I ended up with for my listbox.
private void ItemsFlowListBox_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue == true)
{
Dispatcher.BeginInvoke(
DispatcherPriority.ContextIdle,
new Action(delegate()
{
ItemsFlowListBox.Focus();
ItemsFlowListBox.ScrollIntoView(ItemsFlowListBox.SelectedItem);
}));
}
}
I had the same symptom for a WPF UserControl hosted in a Winforms application. Just wanted to note I was about to try this solution when I found a normal TabIndex in the Winforms app fixed it
Per How to set which control gets the focus on application start
"The one with the minimum tab index automatically gets the focus
(assuming the TabStop property is set to true). Just set the tab
indices appropriately."
It's a tricky one with no easy answer. I'm currently doing this, although I'm not sure I like it:
public MyView()
{
InitializeComponent();
// When DataContext changes hook the txtName.TextChanged event so we can give it initial focus
DataContextChanged +=
(sender, args) =>
{
txtName.TextChanged += OnTxtNameOnTextChanged;
};
}
private void OnTxtNameOnTextChanged(object o, TextChangedEventArgs eventArgs)
{
// Setting focus will select all text in the TextBox due to the global class handler on TextBox
txtName.Focus();
// Now unhook the event handler, since it's no longer required
txtName.TextChanged -= OnTxtNameOnTextChanged;
}
And in case you're wondering what the global class handler does, it's this:
protected override void OnStartup(StartupEventArgs e)
{
...
// Register a global handler for this app-domain to select all text in a textBox when
// the textBox receives keyboard focus.
EventManager.RegisterClassHandler(
typeof (TextBox), UIElement.GotKeyboardFocusEvent,
new RoutedEventHandler((sender, args) => ((TextBox) sender).SelectAll()));
which auto selects TextBox text when receiving keyboard focus.

No events passed to WPF adorner layer

I am trying to make a nice "drag and drop zone" in WPF that is displayed in the adorner layer when something is being dragged into the main application. The problem is that I do not get any events from my adorner, even though it according to documentation should receive all input events since it is in a higher z-order.
To debug my problem I created a really simple example where I have a user control with only a button in it. This user control is displayed in the adorner layer, but I cannot click the button. Why? What have I done wrong?
My adorner class is constructed like this:
public ShellOverlayAdorner(UIElement element, AdornerLayer adornerLayer)
:base(element)
{
_adornerLayer = adornerLayer;
_overlayView = new AdornedElement();
_overlayView.AllowDrop = true;
_adornerLayer.Add(this);
}
and is created in the main window by
private void Window_Loaded(object sender, RoutedEventArgs e)
{
adornerLayer = AdornerLayer.GetAdornerLayer(MyTopGridWithButtonInIt);
ShellOverlayAdorner shell = new ShellOverlayAdorner(MyTopGridWithButtonInIt, adornerLayer);
}
I do not get any events at all from my control, i.e. no mouse clicks, mouse over, button clicks. I cannot even click the button in the adorner layer. What have I done wrong?
I don't know if you already tried that:
If you want the element added to react to events, I think that the element must be bound to the visual tree of the adorner.
The way to do it is to use a VisualCollection, intitialized to the adorner itself, or at least, this way it seems to be working:
VisualCollection visualChildren;
FrameworkElement #object;
public CustomAdorner(UIElement adornedElement) :
base(adornedElement)
{
visualChildren = new VisualCollection(this);
#object = new Button {Content = "prova"};
visualChildren.Add(#object);
}
protected override Visual GetVisualChild(int index)
{
return visualChildren[index];
}
This way the events are correctly routed.
I just had the same issue. Following the advice from MSDN sorted it for me:
Adorners receive input events just
like any other FrameworkElement.
Because an adorner always has a higher
z-order than the element it adorns,
the adorner receives input events
(such as Drop or MouseMove) that may
be intended for the underlying adorned
element. An adorner can listen for
certain input events and pass these on
to the underlying adorned element by
re-raising the event.
To enable pass-through hit testing of
elements under an adorner, set the hit
test IsHitTestVisible property to
false on the adorner.
i.e In the adorner itself, make sure IsHitTestVisible = false

Can't set focus to a child of UserControl

I have a UserControl which contains a TextBox. When my main window loads I want to set the focus to this textbox so I added Focusable="True" GotFocus="UC_GotFocus" to the UserControls definition and FocusManager.FocusedElement="{Binding ElementName=login}" to my main windows definition. In the UC_GotFocus method i simply call .Focus() on the control i want to focus on but this doesn't work.
All i need to do is have a TextBox in a UserControl receive focus when the application starts.
Any help would be appreciated, thanks.
I recently fixed this problem for a login splash screen that is being displayed via a storyboard when the main window is first loaded.
I believe there were two keys to the fix. One was to make the containing element a focus scope. The other was to handle the Storyboard Completed event for the storyboard that was triggered by the window being loaded.
This storyboard makes the username and password canvas visible and then fades into being 100% opaque. The key is that the username control was not visible until the storyboard ran and therefore that control could not get keyboard focus until it was visible. What threw me off for awhile was that it had "focus" (i.e. focus was true, but as it turns out this was only logical focus) and I did not know that WPF had the concept of both logical and keyboard focus until reading Kent Boogaart's answer and looking at Microsoft's WPF link text
Once I did that the solution for my particular problem was straightforward:
1) Make the containing element a focus scope
<Canvas FocusManager.IsFocusScope="True" Visibility="Collapsed">
<TextBox x:Name="m_uxUsername" AcceptsTab="False" AcceptsReturn="False">
</TextBox>
</Canvas>
2) Attach a Completed Event Handler to the Storyboard
<Storyboard x:Key="Splash Screen" Completed="UserNamePassword_Storyboard_Completed">
...
</Storyboard>
and
3) Set my username TextBox to have the keyboard focus in the storyboard completed event handler.
void UserNamePassword_Storyboard_Completed(object sender, EventArgs e)
{
m_uxUsername.Focus();
}
Note that calling item.Focus() results in the call Keyboard.Focus(this), so you don't need to call this explicitly. See this question about the difference between Keyboard.Focus(item) and item.Focus.
Its stupid but it works:
Pop a thread that waits a while then comes back and sets the focus you want. It even works within the context of an element host.
private void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
System.Threading.ThreadPool.QueueUserWorkItem(
(a) =>
{
System.Threading.Thread.Sleep(100);
someUiElementThatWantsFocus.Dispatcher.Invoke(
new Action(() =>
{
someUiElementThatWantsFocus.Focus();
}));
}
);
}
Just recently I had a list-box that housed some TextBlocks. I wanted to be able to double click on the text block and have it turn into a TextBox, then focus on it and select all the text so the user could just start typing the new name (Akin to Adobe Layers)
Anyway, I was doing this with an event and it just wasn't working. The magic bullet for me here was making sure that I set the event to handled. I figure it was setting focus, but as soon as the event went down the path it was switching the logical focus.
The moral of the story is, make sure you're marking the event as handled, that might be your issue.
“When setting initial focus at application startup, the element to
receive focus must be connected to a PresentationSource and the
element must have Focusable and IsVisible set to true. The recommended
place to set initial focus is in the Loaded event handler"
(MSDN)
Simply add a "Loaded" event handler in the constructor of your Window (or Control), and in that event handler call the Focus() method on the target control.
public MyWindow() {
InitializeComponent();
this.Loaded += new RoutedEventHandler(MyWindow_Loaded);
}
void MyWindow_Loaded(object sender, RoutedEventArgs e) {
textBox.Focus();
}
since i tried a fuzquat's solution and found it the most generic one, i thought i'd share a different version, since some complained about it looking messy. so here it is:
casted.Dispatcher.BeginInvoke(new Action<UIElement>(x =>
{
x.Focus();
}), DispatcherPriority.ApplicationIdle, casted);
no Thread.Sleep, no ThreadPool. Clean enough i hope.
UPDATE:
Since people seem to like pretty code:
public static class WpfExtensions
{
public static void BeginInvoke<T>(this T element, Action<T> action, DispatcherPriority priority = DispatcherPriority.ApplicationIdle) where T : UIElement
{
element.Dispatcher.BeginInvoke(priority, action);
}
}
now you can call it like this:
child.BeginInvoke(d => d.Focus());
WPF supports two different flavors of focus:
Keyboard focus
Logical focus
The FocusedElement property gets or sets logical focus within a focus scope. I suspect your TextBox does have logical focus, but its containing focus scope is not the active focus scope. Ergo, it does not have keyboard focus.
So the question is, do you have multiple focus scopes in your visual tree?
I found a good series of blog posts on WPF focus.
Part 1: It’s Basically Focus
Part 2: Changing WPF focus in code
Part 3: Shifting focus to the first available element in WPF
They are all good to read, but the 3rd part specifically deals with setting focus to a UI element in a UserControl.
Set your user control to Focusable="True" (XAML)
Handle the GotFocus event on your control and call yourTextBox.Focus()
Handle the Loaded event on your window and call yourControl.Focus()
I have a sample app running with this solution as I type. If this does not work for you, there must be something specific to your app or environment that causes the problem. In your original question, I think the binding is causing the problem.
I hope this helps.
After having a 'WPF Initial Focus Nightmare' and based on some answers on stack, the following proved for me to be the best solution.
First, add your App.xaml OnStartup() the followings:
EventManager.RegisterClassHandler(typeof(Window), Window.LoadedEvent,
new RoutedEventHandler(WindowLoaded));
Then add the 'WindowLoaded' event also in App.xaml :
void WindowLoaded(object sender, RoutedEventArgs e)
{
var window = e.Source as Window;
System.Threading.Thread.Sleep(100);
window.Dispatcher.Invoke(
new Action(() =>
{
window.MoveFocus(new TraversalRequest(FocusNavigationDirection.First));
}));
}
The threading issue must be use as WPF initial focus mostly fails due to some framework race conditions.
I found the following solution best as it is used globally for the whole app.
Hope it helps...
Oran
I converted fuzquat's answer to an extension method. I'm using this instead of Focus() where Focus() did not work.
using System;
using System.Threading;
using System.Windows;
namespace YourProject.Extensions
{
public static class UIElementExtension
{
public static void WaitAndFocus(this UIElement element, int ms = 100)
{
ThreadPool.QueueUserWorkItem(f =>
{
Thread.Sleep(ms);
element.Dispatcher.Invoke(new Action(() =>
{
element.Focus();
}));
});
}
}
}
I've noticed a focus issue specifically related to hosting WPF UserControls within ElementHosts which are contained within a Form that is set as an MDI child via the MdiParent property.
I'm not sure if this is the same issue others are experiencing but you dig into the details by following the link below.
Issue with setting focus within a WPF UserControl hosted within an ElementHost in a WindowsForms child MDI form
I don't like solutions with setting another tab scope for UserControl. In that case, you will have two different carets when navigating by keyboard: on the window and the another - inside user control. My solution is simply to redirect focus from user control to inner child control. Set user control focusable (because by default its false):
<UserControl ..... Focusable="True">
and override focus events handlers in code-behind:
protected override void OnGotFocus(RoutedEventArgs e)
{
base.OnGotFocus(e);
MyTextBox.Focus();
}
protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
base.OnGotKeyboardFocus(e);
Keyboard.Focus(MyTextBox);
}
What did the trick for me was the FocusManager.FocusedElement attribute. I first tried to set it on the UserControl, but it didn't work.
So I tried putting it on the UserControl's first child instead:
<UserControl x:Class="WpfApplication3.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid FocusManager.FocusedElement="{Binding ElementName=MyTextBox, Mode=OneWay}">
<TextBox x:Name="MyTextBox"/>
</Grid>
... and it worked! :)
I have user control - stack panel with two text boxes.The text boxes were added in contructor, not in the xaml. When i try to focus first text box, nothing happend.
The siggestion with Loaded event fix my problem. Just called control.Focus() in Loaded event and everthing.
Assuming you want to set focus for Username textbox, thus user can type in directly every time it shows up.
In Constructor of your control:
this.Loaded += (sender, e) => Keyboard.Focus(txtUsername);
After trying combinations of the suggestions above, I was able to reliably assign focus to a desired text box on a child UserControl with the following. Basically, give focus to the child control and have the child UserControl give focus to its TextBox. The TextBox's focus statement returned true by itself, however did not yield the desired result until the UserControl was given focus as well. I should also note that the UserControl was unable to request focus for itself and had to be given by the Window.
For brevity I left out registering the Loaded events on the Window and UserControl.
Window
private void OnWindowLoaded(object sender, RoutedEventArgs e)
{
ControlXYZ.Focus();
}
UserControl
private void OnControlLoaded(object sender, RoutedEventArgs e)
{
TextBoxXYZ.Focus();
}
I set it in the PageLoaded() or control loaded, but then I'm calling WCF async service and doing stuff that seems to lose the focus. I have to to set it at the end of all the stuff I do. That's fine and all, but sometimes I make changes to the code and then I forget that I'm also setting the cursor.
I had same problem with setting keyboard focus to canvas in WPF user control.
My solution
In XAML set element to Focusable="True"
In element_mousemove event create simple check:
if(!element.IsKeyBoardFocused)
element.Focus();
In my case it works fine.

Resources