Preventing WPF TreeView's GotFocus event from bubbling up the tree - wpf

I'm trying to write an event handler that fires every time a node in a TreeView gets the focus. The problem I'm running into is that the event handler fires on the TreeViewItem (node) that I click on with the mouse, and then it continues to bubble up the control tree, even though I've set e.Handled = true on the RoutedEventArgs provided to the handler. Does anybody have an idea what the problem could be ? I've double checked my code and I can see no reason why this should be happening.

Are you using TreeView.GotFocus when you really want TreeViewItem.Selected?
<TreeView TreeViewItem.Selected="treeView1_Selected" />
If you really want focus, use TreeViewItem.Focus instead so that items are targeted instead of the whole tree.
<TreeView TreeViewItem.GotFocus="treeView1_GotFocus"/>

Related

How can I block event in root element and let the event fired in the container(parent) level?

I'm working with WPF.
My visual tree hierarchy as the following:
RadDiagram > RadDiagramShape > MyControl
The content of RadDiagramShape is mycontrol.
In myControl class, I have handled (MouseLeftButtonDown) Event, I put drag-drop code inside it(which I need it in another place). So, it's Direct Event not Tunneling or Bubbling!
While I'm moving my Custom Control which is the content of the RadDiagramShape, in RadDiagram, it doesn't move (It's trying to be dragged) because the MouseLeftButtonDown has been handled inside MyControl.
It prevents the event from bubbling up the Visual tree, preventing any parent handlers from being notified of the event.
I tried to handled the Event for the RadDiagramShape and for the RadDiagram as e.Handled = true;
but it did nothing because It's MouseLeftButtonDown and it's handled inside the root element so, it won't bubble or tunnel and I didn't override movement code, which I don't want to override it. Because I tried it before and it didn't give me the same slightly move that built-in in WPF.
How can I block MouseLeftButtonDown event in root element and let the event fired in the container(parent) level?
Please check in your control handler if 'OriginalSource' is the same control that you want or not.
if ((e.OriginalSource is TextBox) && (e.OriginalSource as TextBox).Name == "TextBoxName")
{
//Do every thing you want
}
Thank you leila karimi. You gave me the orientation, the condition itself didn't work. But I put another condition and it worked
void MyLabel_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
MyLabel dc = (MyLabel)sender;
if (dc.Parent.GetType() != typeof(RadDiagramShape))
{///....drop it}
}

How to prevent InvokeCommandAction from propagating event to parent elements?

I realised that when using an InvokeCommandAcction associated to an EventTrigger, the original event was still routing up to the parent elements until it is handled. Well, I guess it is an expected behavior. But my question is how I can mark the event as Handled so it does not propagate up through the whole UI tree?
Actually, as you handle this event in a command, everything will be handled in this command, therefore it does not need to propagate. And in one corner case I found, it causes some unwanted behavior. For example, I open a new window when a user double click an element (MouseDoubleClick event). The problem is that the new windows opens and then the main window come back in front of the new one because the MouseDoubleClick event just reached the top element in the UI tree. The wanted behavior would be to keep the new window in front, but as the InvokeCommandAction lets the event propagate up, the main window takes back the focus...
What I could do is to use the CallMethodAction asset instead but as I am in a MVVM scenario, I don't want UI event arguments in my code. Even if this would let me implicitely mark the event as handled and fix the issue.
<UserControl x:Class="..."
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction Command="{Binding Path=DisplayReportCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
...
</UserControl>
You could implement your own EventTrigger that marks events as handled.
public class HandlingEventTrigger : System.Windows.Interactivity.EventTrigger
{
protected override void OnEvent(System.EventArgs eventArgs)
{
var routedEventArgs = eventArgs as RoutedEventArgs;
if (routedEventArgs != null)
routedEventArgs.Handled = true;
base.OnEvent(eventArgs);
}
}
Then replace <i:EventTrigger EventName="MouseDoubleClick"> with <local:HandlingEventTrigger EventName="MouseDoubleClick"> and add
xmlns:local="clr-namespace:HandlingEventTrigger's namespace here"
to your usercontrol's atributes.
Add attached event to user control
CommandManager.PreviewCanExecute="PreviewCanExecute"
and in event handler
e.ContinueRouting = false;
Hope this will help!
MouseDoubleClick Event is actually not a bubbling routed event but a direct routed event.
However, this event is raised along the element tree, which can be checked with Snoop tool. Moreover, even if Handled for MouseDoubleClick is set to true, this event will occur along the element tree.
Although this routed event(MouseDoubleClick Event) seems to follow a bubbling route through an element tree, it actually is a direct routed event that is raised along the element tree by each UIElement.
If you set the Handled property to true in a MouseDoubleClick event handler, subsequent MouseDoubleClick events along the route will occur with Handled set to false. This is a higher-level event for control consumers who want to be notified when the user double-clicks the control and to handle the event in an application. (From MSDN)
As above, your problem may be not caused by the propagating as you mentioned. There is Window.ShowActivated property, which determines whether a window is activated when first shown. You can set the property in a sub window(xaml) as below but please note that though ShowActivated can give the focus to the main window, it cannot let the main window visually keep in front of the sub window. I have tried to find the solution but have no idea until now.
<Window ShowActivated="False" ....>
....
</Window>

Attached Command Behavior and LostFocus

I am using the method described here to attach a ViewModel ICommand to the LostFocus event of a Combobox, by setting CommandBehavior.RoutedEventName="LostFocus". I expected the event to fire at the same time the binding for UpdateSourceTrigger=LostFocus fired, but this turns out not to be the case.
The selecteditem Binding UpdateSourceTrigger=LostFocus fires whenever the keyboard tabs away, or after the user actually selects an item from the dropdown by clicking (not sure why this causes lostfocus, but at least it fires AFTER a selection is made).
The attached behavior event fires anytime the user clicks on the Combobox. Immediately. If using the keyboard it behaves normally, firing when you tab away from it. However, when using the mouse, the event fires when the control GAINS focus, before the user has even made a selection. Is there any way to make this behave like lostfocus does for the selecteditem?
Edit: I am curious if another answer exists, but I found a way around this problem, by setting up an additional binding. SelectedItem updates by defualt, handling the normal property change notifications, and selectedvalue updates on lostfocus, handling only the command I was trying to run. Binding looks like this:
SelectedItem="{Binding Path=SelectedCustomer, Mode=TwoWay}"
SelectedValuePath="CM_CUSTOMER_ID"
SelectedValue="{Binding Path=CustomerLostFocus, UpdateSourceTrigger=LostFocus}"
You would need to check the OriginalSource of the event arguments for the LostFocus event:
The LostFocus event is a bubbling event. This means that if multiple
LostFocus event handlers are registered for a sequence of objects
connected by parent-child relationships in the object tree, the event
is received by each object in that relationship. The bubbling metaphor
indicates that the event starts at the object that directly receives
the input condition, and works its way up the object tree. For a
bubbling event, the sender available to the event handler identifies
the object where the event is handled, not necessarily the object that
actually received the input condition that initiated the event. To get
the object that initiated the event, use the OriginalSource value of
the event's RoutedEventArgs event data.
So for the ComboBox, you may receive events for the various focusable elements inside the ComboBox.

WPF Default Button command not triggering binding in textbox

I have a search screen with some textboxes and a Search button as the default. If I type in a textbox and I CLICK the button, everything's great. But if I press enter within a text box, the button command fires but the binding on whatever text box I was in does NOT fire and so my criteria doesn't make it to the view model to get filtered on.
I know one fix is to set the bindings on the text boxes to PropertyChanged, but this seems like way overkill. I might have logic in the viewmodel doing stuff and I don't want that to trigger on every single keystroke.
What I really want is a way for the button itself to either trigger a focus change or somehow trigger binding. Or to have the textbox trigger binding if focus is lost OR I press enter OR a command is executed from anywhere
One way to do this is with a BindingGroup.
http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.bindinggroup.aspx
If your TextBox(es) and Button are both contained within a Grid (for example), you would add a BindingGroup like this:
<Grid>
<Grid.BindingGroup>
<BindingGroup Name="bindingGroup1"/>
</Grid.BindingGroup>
Then you could add a Click event handler to your button and call CommitEdit() on the BindingGroup (which the Button and TextBox inherit from the Grid):
private void button1_Click(object sender, RoutedEventArgs e)
{
(sender as FrameworkElement).BindingGroup.CommitEdit();
}
The Button.Click event fires before the CommandBinding, so any databound TextBox or any other databound controls within that BindingGroup should be updated before your view model command gets executed.
I've had the exact scenario you just mentioned. The trick I use is an attached behavior that sits on a control and listens for the PreviewKeyDown event. It checks if enter is being pressed. If so it forces the control to lose focus, thus causing the binding to fire before the command executes.
A simpler approach (rather than using a binding group) is to use the default button's click event to set the focus to itself. As this happens before the command is executed it means the ViewModel is updated in time.
private void button1_Click(object sender, RoutedEventArgs e)
{
(sender as Button).Focus()
}
And if you really hate code behind, you could always write an attached property...

How to capture a mouse click on an Item in a ListBox in WPF?

I want to get notified when an item in a ListBox gets clicked by the mouse, whether it is already selected or not.
I searched and found this: (http://kevin-berridge.blogspot.com/2008/06/wpf-listboxitem-double-click.html see the comments)
private void AddDoubleClickEventStyle(ListBox listBox, MouseButtonEventHandler mouseButtonEventHandler)
{
if (listBox.ItemContainerStyle == null)
listBox.ItemContainerStyle = new Style(typeof(ListBoxItem));
listBox.ItemContainerStyle.Setters.Add(new EventSetter()
{
Event = MouseDoubleClickEvent,
Handler = mouseButtonEventHandler
});
}
//Usage:
AddDoubleClickEventStyle(listView1, new MouseButtonEventHandler(listView1_MouseDoubleClick));
This works, but it does it for a DoubleClick. I can't get it working for a single click though. I tried MouseLeftButtonDownEvent - as there doesn't seem to be a MouseClick event, but it's not being called.
A bit more general side question: How can I see what events do exist and which handlers correspond to them and when they actually do something? For example, what tells me that for a MouseDoubleClickEvent I need a MouseButtonEventHandler? Maybe for a MouseLeftButtonDownEvent I need some other handler and that's why it's not working?
I also tried subclassing ListBoxItem and override OnMouseLeftButtonDown - but it doesn't get called either.
Marc
I believe that your MouseLeftButtonDown handler is not called because the ListBox uses this event internally to fire its SelectionChanged event (with the thought being that in the vast majority of cases, SelectionChanged is all you need). That said, you have a couple of options.
First, you could subscribe to the PreviewLeftButtonDown event instead. Most routed events have a routing strategy of Bubbling, which means that the control that generated the event gets it first, and if not handled, the event works its way up the visual tree giving each control a chance at handling the event. The Preview events, on the other hand, are Tunneling. This means that they start at the root of the visual tree (generally Window), and work their way down to the control that generated the event. Since your code would get the chance to handle the event prior to the ListBoxItem, this will get fired (and not be handled) so your event handler will be called. You can implement this option by replacing MouseDoubleClickEvent in your sample with PreviewMouseLeftButtonDown.
The other option is to register a class handler that will be notified whenever a ListBoxItem fires the MouseLeftButtonDown event. That is done like this:
EventManager.RegisterClassHandler(typeof(ListBoxItem),
ListBoxItem.MouseLeftButtonDownEvent,
new RoutedEventHandler(this.MouseLeftButtonDownClassHandler));
private void OnMouseLeftButtonDown(object sender, RoutedEventArgs e)
{
}
Class Handlers are called before any other event handlers, but they're called for all controls of the specified type in your entire application. So if you have two ListBoxes, then whenever any ListBoxItem is clicked in either of them, this event handler will be called.
As for your second question, the best way to know what type of event handler you need for a given event, and to see the list of events available to a given control, is to use the MSDN documentation. For example, the list of all events handled by ListBoxItem is at http://msdn.microsoft.com/en-us/library/system.windows.controls.listboxitem_events.aspx. If you click on the link for an event, it includes the type of the event handler for that event.
There is also another way - to handle PreviewMouseDown event and check if it was triggered by the list item:
In XAML:
<ListBox PreviewMouseDown="PlaceholdersListBox_OnPreviewMouseDown"/>
In codebehind:
private void PlaceholdersListBox_OnPreviewMouseDown(object sender, MouseButtonEventArgs e)
{
var item = ItemsControl.ContainerFromElement(sender as ListBox, e.OriginalSource as DependencyObject) as ListBoxItem;
if (item != null)
{
// ListBox item clicked - do some cool things here
}
}
Was inspired by this answer, but it uses listbox by name, I propose to use sender argument to avoid unnecessary dependencies.
I think the first option in Andy's answer, of using PreviewMouseLeftButtonDown, is the way to go about this. In XAML it would look like this:
<ListBox Name="testListBox">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<EventSetter
Event="PreviewMouseLeftButtonDown"
Handler="ListBox_MouseLeftButtonDown" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
There is another way to get MouseDown event in ListBox. You can add event handler for events that are marked as handled by using handledEventsToo signature of AddHandler method:
myListBox.AddHandler(UIElement.MouseDownEvent,
new MouseButtonEventHandler(ListBox_MouseDown), true);
Third parameter above is handledEventsToo which ensures that this handler will be invoked no matter if it is already marked as Handled (which ListBoxItem does in ListBox).
See Marking Routed Events as Handled, and Class Handling for explanation.
See How to Attach to MouseDown Event on ListBox for example.
You can use Event="MouseLeftButtonUp"
Unlike "PreviewLeftButtonDown" it will get the ListBoxItem handled too.
You can use the SelectionChangedEventArgs argument of the SelectionChanged event to find what item is add or removed through AddedItems and RemovedItems, usually only have the latest clicked on, or if not, then look at the last item which is the count-1.

Resources