How to capture a mouse click on an Item in a ListBox in WPF? - 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.

Related

Is there a possibility to programmatically trigger item action in ListView, like when you dobuleclick on it?

I was searching all over the place but I couldn't find an answer. I need to fire up an ListView item action, so it would rise the ItemActivate event. For now, it's only possible using ENTER key or double click... I would like to know if I could programmatically do that, something like :
listView.Items[int].Activate();
This doesn't work of course, because that function Activate() is not implemented there. For example, I couldn't find how to trigger buttons programmatically, but there it was, in the context menu which appears while you type:
buttonX.PerformClick();
...and it would trigger the button_click event. I wonder if there's something similar in the ListView control for triggering items inside of it ? I want it to raise this event programmatically and not by mouse doubleclick or Enter key on the keyboard...
private void myListView_ItemActivate(object sender, EventArgs e)
{
MessageBox.Show("hello");
}
According to the documentation, the EventHandler in the ListView should be public. So you can raise the event with:
myListView.ItemActivate(myListView, EventArgs.Empty);
This way everyone who subscribed to this event will get notified.
Another way, of course, is to directly call your method:
myListView_ItemActivate(this, EventArgs.Empty);
But this doesn't really classify as "raising the event", because you actually don't raise an event. You just call a method.

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>

How do I hook an event to a control that comes in dynamically as part of a DataTemplate?

I have a ListBox that uses DataTemplateSelector to dynamically decide what
template to use based on the type of the item in the list. I now want to hook
the events that could be fired by controls within the DataTemplate. For example,
if one of the templates has a checkbox in it, I want the application using the
control to be notified when the checkbox is checked. If a different template has
a button within it, I want to be notified when the button is clicked.
Also, since its a ListBox, many of the items could have the same template. So
I will need some kind of RoutedEventArgs so I can walk up from OriginalSource to get
some context information to handle the event.
My solution was to use MouseLeftButtonUp. This works fine for TextBlocks, but it looks like CheckBox and Button controls set handled to true, so the event doesnt bubble up. How can I address these
events so I can assign handlers to them in my calling application?
(Also, Silverlight doesn't actually support DataTemplateSelector, so i followed this example to implement it)
If you are defining the templates in the Xaml for the user control where your event handlers are placed then you should simply be able to assign the event handlers in the Xaml.
However in the specific scenario you outline you can also listen for the MouseLeftButtonUp event via the AddHandler method:-
myListBox.AddHandler(UIElement.MouseLeftButtonUpEvent, myListBox_MouseLeftButtonUp, true);
...
private void myListBox_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
//e.OriginalSource available for your inspection
}
Note by using AddHandler and passing true in the third parameter you will get the event regardless of whether it has been handled.

Mouse interaction in ListBoxItem children (WPF)

I have a ListBox with an ItemTemplate that contains a control that interacts with the mouse. This interfers with the selection functionality of the ListBox, i.e. clicking a control does not select the item. This is because ListBoxItem sets the Handled property of the mouse event to true in OnMouseLeftButtonDown. I tried the following
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) {
base.OnMouseLeftButtonDown(e);
e.Handled = false;
}
but the ListBoxItem “takes over” the mouse and prevents the control from doing its own interaction. Then I had another idea
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) {
base.OnMouseLeftButtonDown(e);
((ListBoxItem)VisualTreeHelper.GetParent(VisualTreeHelper.GetParent(VisualTreeHelper.GetParent(this)))).IsSelected = true;
}
which actually works, but feels more like an ugly kludge than an elegant solution. Are there any better solutions that don't rely on the exact contents of the visual tree?
I've found a way that is less of a kludge:
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) {
base.OnMouseLeftButtonDown(e);
Selector.SetIsSelected(this, true);
}
For this to have any effect, the control in the ListBox' ItemTemplate needs the following XAML attribute:
Selector.IsSelected="{Binding IsSelected, Mode=OneWayToSource, RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}"
It raises two new questions:
Would it be better to define my own dependency property rather than finding an attached one that isn't currently in use?
Is there a way to achieve something similar in markup only?
I believe the MouseLeftButtonDown is a tunnelling event: you could try using PreviewMouseLeftButtonDown, doing your processing there, then ensuring e.Handled = false; as you tried already - that should do the trick!
Hope that helps.
Here is one simple solution, but unfortunately handler can be attached only in code, not in markup.
Event handler can be added 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.

Silverlight DataBinding Loading Animation

Is there an event somewhere in the Silverlight control model that is raised once an item is databound? I am binding at design time to a large amount of data and would like to display an animation until the databinding is complete.
There is no specific event that is fired when databinding is completed. Your best bet would probably be to key off of the FrameworkElement.LayoutUpdated event. This is the last event in the lifecycle before a control is ready for user interaction. However, this event will continue to be raised many more times due to property changes, size changes, and explicit calls to UpdateLayout() or InvalidateArrange(). Therefore you will have to add some extra logic to make sure that the LayoutUpdated event warrants stopping/hiding your animation, such as only doing it the first time or if you are sure the event was fired due to a change in databinding.
If the control is actually your own custom control and you are binding to custom DependencyProperties on that control then you could raise your own event on the PropertyChangedCallbacks for each of the properties to signal that they have been updated via databinding.
Here's what I do:
private object lastDataContext;
private void MyClass_Loaded(object sender, RoutedEventArgs e)
{
if (DataContext != lastDataContext)
{
perform_onetime_operation();
lastDataContext = DataContext;
}
}
That way perform_onetime_operation will get called not just the first time databinding happens, but any time that the DataContext changes meaning that data is re-bound.

Resources