Monday, June 30, 2008

WPF ListBoxItem Double Click

The WPF ListBox does not have an event which fires when an item in the list is double clicked. As far as I can tell, there is no simple mechanism to accomplish this.

The best solution I've found is using the ListBox.MouseDoubleClick event. This event fires every time the mouse is double clicked anywhere in the listbox. This includes the background (if your items don't completely fill your list) and the scroll bar.

What you have to do is use the ListBox.InputHitTest method to get the element in the ListBox's Visual Tree which was clicked. Then you have to loop up the VisualTree until you find either a ListBoxItem (which means an item was double clicked), or the ListBox (which means an item was not double clicked).

Here's the code where ctlList is the ListBox:
void ctlList_MouseDoubleClick( object sender, MouseButtonEventArgs e )
{
UIElement elem = (UIElement)ctlList.InputHitTest( e.GetPosition( ctlList ) );
while ( elem != ctlList )
{
if ( elem is ListBoxItem )
{
object selectedItem = ( (ListBoxItem)elem ).Content;
// Handle the double click here
return;
}
elem = (UIElement)VisualTreeHelper.GetParent( elem );
}
}

I haven't been able to find another way to do this yet. There may be one flaw with this: If you use a DataTemplate in your list box, and a control in that template handles the MouseDoubleClick, I think it's possible the ListBox's event wont fire. Again, I haven't verified this, so it might work just fine.

If you know of a better way to get the double click on a list box item in WPF, please let me know!

UPDATE: Some commenters found a possible problem with the approach demonstrated in this post and offered an alternative method.

The problem is that it's possible the InputHitTest method could return something that is not a UIElement. This might happen if you had a Span element in your list box item template, for example.

The alternative method is to define a style for your list box's item container than includes an event hook for MouseDoubleClick.

<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<EventSetter Event="MouseDoubleClick" Handler="listBoxItem_DoubleClick" />
</Style>
</ListBox.ItemContainerStyle>

Thanks to Steve and Mark for pointing this out!


7 comments:

  1. Hey Kevin -

    I stumbled across your post while trying to find the same answer, and luckily found it somewhere else.

    Here's an excerpt:
    <ListBox.ItemContainerStyle>
    <Style TargetType="{x:Type ListBoxItem}">
    <EventSetter Event="MouseDoubleClick" Handler="listBoxItem_DoubleClick" />
    </Style>
    </ListBox.ItemContainerStyle>

    Here's the link to the MSDN blog that answered the question:

    http://blogs.msdn.com/wpfsdk/archive/2008/04/30/itemcontrols-don-t-properly-select-items-or-raise-events-when-bound-to-a-data-source.aspx

    ReplyDelete
  2. The approach Steve listed is better. There potentially is an issue where InputHitTest may not return a UIElement. For example if you had a Span element buried in the list box item. That will throw an exception.

    I've switched to the other approach and this issue goes away.

    ReplyDelete
  3. Thanks for the post Kevin. I have translated your XAML code into C# and it works nicely with ListBoxes as well as ListViews. Here it is:

    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));

    Cheers

    ReplyDelete
  4. Just wanted to say thanks for the post! Saved me some hassle.

    ReplyDelete
  5. Another very simple solution :

    Private Sub ListBox_MouseDoubleClick(ByVal sender As Object, ByVal e As System.Windows.Input.MouseButtonEventArgs) Handles ListBox.MouseDoubleClick
    ' If mouse is over ScrollBar then exit
    If e.MouseDevice.DirectlyOver.GetType Is GetType(System.Windows.Controls.Primitives.Thumb) _
    Or e.MouseDevice.DirectlyOver.GetType Is GetType(System.Windows.Controls.Primitives.RepeatButton) Then Exit Sub

    ' do the job here
    End Sub

    ReplyDelete
  6. Other possibility, i built based on an example of handling a doubleclick in an datagrid

    private MyClass ListBox_MouseDoubleClick(object sender,
    System.Windows.Input.MouseButtonEventArgs e)
    {
    IInputElement element = e.MouseDevice.DirectlyOver;
    if (element != null && element is FrameworkElement)
    {
    if (((System.Windows.FrameworkElement)element).TemplatedParent is ContentPresenter)
    return (((System.Windows.FrameworkElement)element).TemplatedParent as ContentPresenter).Content as MyClass;
    else if (((System.Windows.FrameworkElement)element).TemplatedParent is ListBoxItem)
    return (((System.Windows.FrameworkElement)element).TemplatedParent as ListBoxItem).Content as MyClass;
    }
    return null;
    } }

    ReplyDelete