I have a UserControl that contains several ComboBoxes and Buttons.
In the window that hosts the UserControl I need to update some properties when the UserControl gets the focus.
My problem is, that every time, when the focus inside the user control changes, that get a GotFocus event in the hosting windows.
Is there some kind of best practice to make sure, that I only get one GotFocused event in the hosting window? I mean, if I step through controls inside the UserControl the focus is always inside the UserControl, so I don't want a GotFocused event.
This is the solution I came up to:
First of all this post was the key for my solution: WPF UserControl detect LostFocus ignoring children .
And Refer to active Window in WPF? .
Using the functions in these posts I registered the LostFocus event in my UserControl.
private void UserControl_LostFocus(object sender, RoutedEventArgs e)
{
var focused_element = FocusManager.GetFocusedElement(Application.Current.Windows.OfType<Window>().FirstOrDefault(x => x.IsActive));
var parent = (focused_element as FrameworkElement).TryFindParent<KeywordSelector>();
if (parent != this) userControlHasFocus=false;
}
And then ...
private void UserControl_GotFocus(object sender, RoutedEventArgs e)
{
if (userControlHasFocus == true) e.Handled = true;
else userControlHasFocus = true;
}
This way I keep track of the focus. userControlHasFocus is false be default. When the GotFocus() happens for the first time it's false and the GotFocus event is not stopped form bubbling up. But userControlHasFocus gets set to true, because now the focus is inside the UserControl.
Whenever the focus moves to another control, LostFocus checks if the new controls parent is the UserControl. If not, it resets the userControlHasFocus to false.
I have a WPF Datagrid that is bound to a model.
Inside the model I have a property defined as
public String status
{
get
{
return m_status;
}
set
{
m_status = value;
OnPropertyChanged("status");
}
}
This property informs the grid of changes via OnPropertyChanged.
I also handle the SelectionChanged event to trigger different activities.
SelectionChanged="gridSongs_SelectionChanged"
private void gridSongs_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
Console.WriteLine("gridSongs_SelectionChanged " + sender.ToString());
}
During testing this I have noticed that every time I change the property "status" in code the grid updates automatically (which is what I want) but also fires the SelectionChanged Event as well.
Is there any way I can stop the event from firing when I change the model from code but let it go through when user clicks an item in the grid ?
Maybe I could use a different event for the manual selection of items in the grid ?
thanks a lot in advance.
Is there any way I can stop the event from firing when I change the model from code but let it go through when user clicks an item in the grid?
No, but there is a simple workaround. Add a private bool isLocal variable and set it to true before you make any changes and back to false afterwards:
isLocal = true;
status = "Some Value";
isLocal = false;
Then, in your SelectionChanged handler, check this variable and only react if it is false:
private void gridSongs_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (!isLocal ) Console.WriteLine("gridSongs_SelectionChanged " + sender.ToString());
}
There is unfortunately no TabControl.SelectionChanging event (Selector.SelectionChanging), I am struggling to implement this behavior so I can cancel the changing request.
I tried to handle the TabControl.Items.CurrentChanging (the Items property is and ItemCollection) event setting e.Cancel (of the CurrentChangingEventArgs) to true, but the UI is is updated with the new tab although the item is not changed in the collection.
Is there any way to prevent user from switching to a different TabItem when a condition is dissatisfied?
I don't know the exact reason why this happens, and it annoys me greatly.
But here's my workaround for it:
In the sample below, checkbox is "locking" the current tab. So checked means user can't change tab.
void Items_CurrentChanging(object sender, CurrentChangingEventArgs e)
{
if (checkBox1.IsChecked.Value)
{
var item = ((ICollectionView)sender).CurrentItem;
e.Cancel = true;
tabControl1.SelectedItem = item;
}
}
Basically, what happens is (if I understand this correctly) the visual tree gets updated, but the logical tree does not. The above way forces the visual to sync with the logical tree.
You can also handle the PreviewLostKeyboardFocus event on each TabItem, and set the Handled property of the event arguments to true to prevent switching to another tab:
protected void tabItem_PreviewLostKeyboardFocus(object sender,
KeyboardFocusChangedEventArgs e)
{
if (!ValidateTabItem((TabItem) sender)) {
e.Handled = true;
}
}
See http://www.netframeworkdev.com/windows-presentation-foundation-wpf/how-to-cancel-navigation-between-tabitems-in-a-tabcontrol-84994.shtml.
I creating a custonmized box class (inherits from ComboBox). I don't want the text box to react to right mouse clicks. I can get rid of the context menu by setting this to null in ApplyTemplate, but right mouse clicks move the cursor. I tried hooking up PreviewMouseRightButtonDown in ApplyTemplate and setting Handled to True, but the event still gets through which is strange as it seems to work for the left click.
The cursor actually moves when the mouse button is released, so you want mark the MouseRightButtonUp event as handled. You could override OnMouseRightButtonUp:
protected override void OnMouseRightButtonUp(MouseButtonEventArgs e)
{
base.OnMouseRightButtonUp(e);
e.Handled = true;
}
Or you could attach a class handler to the MouseRightButtonUp event to mark it as handled:
static MyComboBox()
{
EventManager.RegisterClassHandler(
typeof(MyComboBox),
MouseRightButtonUpEvent,
new MouseButtonEventHandler(MyComboBox_MouseRightButtonUp));
}
private static void MyComboBox_MouseRightButtonUp(
object sender, MouseButtonEventArgs e)
{
e.Handled = true;
}
That will also prevent the context menu from being created without you having to set it to null explicitly.
XAML
<TreeView Name="GroupView" ItemsSource="{Binding Documents}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<EventSetter Event="MouseDoubleClick" Handler="OnTreeNodeDoubleClick"/>
</Style>
</TreeView.ItemContainerStyle>
....
</TreeView>
Code-Behind
private void OnTreeNodeDoubleClick(object sender, MouseButtonEventArgs mouseEvtArgs)
{
Console.WriteLine("{3} MouseDoubleClick Clicks={0} ChangedButton={1} Source={2} Handled={4} ButtonState={5}",
mouseEvtArgs.ClickCount, mouseEvtArgs.ChangedButton, mouseEvtArgs.OriginalSource,
mouseEvtArgs.Timestamp, mouseEvtArgs.Handled, mouseEvtArgs.ButtonState);
}
I find that for one double click, the event handler is called multiple times. I'm trying to open up a document in tab on a double-click on the corresponding tree node; so I'd need to filter out the extra calls.
23479156 MouseDoubleClick Clicks=1 ChangedButton=Left Source=System.Windows.Controls.TextBlock Handled=False ButtonState=Pressed
23479156 MouseDoubleClick Clicks=1 ChangedButton=Left Source=System.Windows.Controls.TextBlock Handled=False ButtonState=Pressed
In my slightly-complicated app, it is being raised 4 times per double-click. On a simple repro-app, it is being raised 2 times per double click. Also all the event argument parameters are the same too, so I can't distinguish the last one of a set.
Any ideas why this is the way it is?
I know this is an old question, but as I came across it in my searches for the solution, here are my findings for any future visitors!
TreeViewItems are recursively contained within each other. TreeViewItem is a HeaderedContentControl (see msdn), with the child nodes as the Content. So, each TreeViewItem's bounds include all of its child items. This can be verified using the excellent WPF Inspector by selecting a TreeViewItem in the visual tree, which will highlights the bounds of the TreeViewItem.
In the OP's example, the MouseDoubleClick event is registered on each TreeViewItem using the style. Therefore, the event will be raised for the TreeViewItems that you double-clicked on - and each of its parent items - separately. This can be verified in your debugger by putting a breakpoint in your double-click event handler and putting a watch on the event args' Source property - you will notice that it changes each time the event handler is called. Incidentally, as can be expected, the OriginalSource of the event stays the same.
To counter this unexpected behaviour, checking whether the source TreeViewItem is selected, as suggested by Pablo in his answer, has worked the best for me.
When a TreeViewItem is double clicked, that item is selected as part of the control behavior. Depending on the particular scenario it could be possible to say:
...
TreeViewItem tviSender = sender as TreeViewItem;
if (tviSender.IsSelected)
DoAction();
...
I've done some debugging and it appears to be a bug in WPF. Most answers already given are correct, and the workaround is to check if the tree view item is selected.
#ristogod's answer is the closest to the root problem - it mentions that setting e.Handled = true the first time handler is invoked doesn't have the desired effect and the event continues to bubble up, calling parent TreeViewItems' handlers (where e.Handled is false again).
The bug seems to be in this code in WPF:
http://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Controls/Control.cs,5ed30e0aec6a58b2
It receives the MouseLeftButtonDown event (which is handled by the child control already), but it fails to check if e.Handled is already set to true. Then it proceeds to create a new MouseDoubleClick event args (with e.Handled == false) and invokes that always.
The question also remains why after having set it to handled the first time the event continues to bubble? Because in this line, when we register the handler Control.HandleDoubleClick:
http://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Controls/Control.cs,40
we pass true as the last argument to RegisterClassHandler:
http://referencesource.microsoft.com/#PresentationCore/Core/CSharp/System/Windows/EventManager.cs,161
which is handledEventsToo.
So the unfortunate behavior is a confluence of two factors:
Control.HandleDoubleClick is called always (for handled events too), and
Control.HandleDoubleClick fails to check if the event had already been handled
I will notify the WPF team but I'm not sure this bug is worth fixing because it might break existing apps (who rely on the current behavior of event handlers being called even if Handled was set to true by a previous handler).
private void TreeView_OnItemMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (e.Source is TreeViewItem
&& (e.Source as TreeViewItem).IsSelected)
{
// your code
e.Handled = true;
}
}
This is not actually a bubbling issue. I've seen this before. Even when you tell the event that you handled it, it continues to keep bubbling up. Except that I don't think that it's actually bubbling up, but rather firing the node above's own double click event. I could be totally wrong on that. But in either case, it's important to know that saying:
e.handled = true;
Does nothing to stop this from happening.
One way to prevent this behavior is to note that when you are double clicking, you are first single clicking and that the selected event should fire first. So while you can't stop the Double Click events from occurring, you should be able to check inside the handler to see whether the event logic should run. This example leverages that:
TreeViewItem selectedNode;
private void MouseDoubleClickEventHandler(object sender, MouseButtonEventArgs e)
{
if(selectedNode = e.Source)
{
//do event logic
}
}
private void TreeViewSelectedEventHandler(object sender, RoutedEventArgs e)
{
selectedNode = (TreeViewItem)e.Source;
}
Sometimes however you have situations where the nodes are being selected by other beans than through the TreeView SelectedItemChanged event. In that case you can do something like this. If you happen to have a TreeView with a single declared top node, you can give that node a specific name and then do something like this:
bool TreeViewItemDoubleClickhandled;
private void MouseDoubleClickEventHandler(object sender, MouseButtonEventArgs e)
{
if (!TreeViewItemDoubleClickhandled)
{
//do logic here
TreeViewItemDoubleClickhandled = true;
}
if (e.Source == tviLoadTreeTop)
{
TreeViewItemDoubleClickhandled = false;
}
e.Handled = true;
}
Regardless of the method you use, the important thing is to note that for whatever reason with TreeViewItem double clicking that you can't stop the events from firing up the tree. At least I haven't found a way.
I have a little bit more elegant solution than checking for selection or creating flags:
A helper method:
public static object GetParent(this DependencyObject obj, Type expectedType) {
var parent = VisualTreeHelper.GetParent(obj);
while (parent != null && parent.GetType() != expectedType)
parent = VisualTreeHelper.GetParent(parent);
return parent;
}
And then your handler:
public void HandleDoubleClick(object sender, MouseButtonEventArgs e)
{
if (e.OriginalSource is DependencyObject)
if (sender == (e.OriginalSource as DependencyObject).GetParent(typeof(TreeViewItem)))
{
// sender is the node, which was directly doubleclicked
}
}
This is the wonderful world of event bubbling. The event is bubbling up the node hierarchy of your TreeView and your handler is called once for every node in the hierarchy path.
Just use something like
// ...
if (sender != this)
{
return;
}
// Your handler code goes here ...
args.Handled = true;
// ...
in your handler code.
There are some pretty major problems with this solution, but it could work in case someone needs to solve this problem in multiple places and I did find a scenario where the accepted solution doesn't work (double clicking on a toggle button that opens up a popup, where the toggle button is inside another element that handles double click.)
public class DoubleClickEventHandlingTool
{
private const string DoubleClickEventHandled = "DoubleClickEventHandled";
public static void HandleDoubleClickEvent()
{
Application.Current.Properties[DoubleClickEventHandled] = DateTime.Now.AddSeconds(1);
}
public static bool IsDoubleClickEventHandled()
{
var doubleClickWasHandled = Application.Current.Properties[DoubleClickEventHandled] as DateTime?;
return doubleClickWasHandled.HasValue && !IsDateTimeExpired(doubleClickWasHandled.Value);
}
private static bool IsDateTimeExpired(DateTime value)
{
return value < DateTime.Now;
}
public static void EnableDoubleClickHandling()
{
Application.Current.Properties[DoubleClickEventHandled] = null;
}
public static bool IsDoubleClickEventHandledAndEnableHandling()
{
var handled = IsDoubleClickEventHandled();
EnableDoubleClickHandling();
return handled;
}
}
Use DoubleClickEventHandlingTool.HandleDoubleClickEvent()
inside the inner/low level element eg:
private void OnPreviewMouseDown(object sender, MouseButtonEventArgs e)
{if (e.ClickCount == 2) DoubleClickEventHandlingTool.HandleDoubleClickEvent();}
High level double click event then only performs it's action when:
if (!DoubleClickEventHandlingTool.IsDoubleClickEventHandledAndEnableHandling())
The most likely reason is that the doubleclick handler is installed multiple times, so each instance of the handler is being called once for each click.