Wednesday, December 12, 2007

WPF ListView - Getting the Clicked Item

I recently had the need to figure out which item in a WPF ListView was under the mouse when it was clicked. The solution wasn't completely straightforward, so I thought I'd post it here.

First, obviously you need to register a mouse button event hander in your control initialization. In this case, I'm using a double-click event:
MyListView.MouseDoubleClick += new MouseButtonEventHandler(MyListView_MouseDoubleClick);
and then implement the event handler like such:
void MyListView_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
DependencyObject dep = (DependencyObject)e.OriginalSource;

while ((dep != null) && !(dep is ListViewItem))
{
dep = VisualTreeHelper.GetParent(dep);
}

if (dep == null)
return;

MyDataItemType item = (MyDataItemType)MyListView.ItemContainerGenerator.ItemFromContainer(dep);

// Do something with the item...
}
What we are doing is walking up the visual tree starting at the element the generated the mouse event. We stop when we find a ListViewItem, which we can then use to get the corresponding data item. If all you want is the index of the item, use IndexFromContainer() instead.

22 comments:

  1. I saw how to get row or item, but how can I get column information from the mouse click position?

    ReplyDelete
  2. Try this and see how it works...
    System.Data.DataRowView value = (System.Data.DataRowView)ListViewEmployeeDetails.SelectedValue;
    MessageBox.Show(value.Row[0].ToString());

    ReplyDelete
  3. Thanks much, you just saved me some time figuring it out. Here is a VB version.
    Dim clickedToDoItem As ToDoItemInfo
    Dim dep As DependencyObject = CType(e.OriginalSource, DependencyObject)
    Do While dep IsNot Nothing AndAlso Not TypeOf (dep) Is ListViewItem
    dep = VisualTreeHelper.GetParent(dep)
    Loop
    If dep Is Nothing Then
    Return
    Else
    clickedToDoItem = CType(Me.lvToDo.ItemContainerGenerator.ItemFromContainer(dep), ToDoItemInfo)
    End If
    MessageBox.Show(String.Format("You clicked item: date={0},description={1}", clickedToDoItem.Due, clickedToDoItem.Description))

    ReplyDelete
  4. Thanks for the info. It worked great in my project and helped me solve two issues I was having. I really am starting to like WPF, but it is still taking a long time to figure it all out.

    ReplyDelete
  5. This code fails if the OriginalSource is derived from FrameworkContentElemnt (e.g. anything in a FlowDocument, such as Document.Run). Unfortunately, VisualTreeHelper deals with Visual and FrameworkElement objects, but not FrameworkContentElement objects. 8-|

    ReplyDelete
  6. As the last comment indicated, you need to handle ContentElement's differently. If you hit a content element then you should get the logical parent.

    ReplyDelete
  7. Thanks a lot for this. Was searching around for ages and found yours to be the most concise method. Thanks

    ReplyDelete
  8. Using

    MyDataItemType item = (MyDataItemType)((ListViewItem)dep).DataContext

    instead of

    MyDataItemType item = (MyDataItemType)MyListView.ItemContainerGenerator.ItemFromContainer(dep);

    is a bit more straight forward but may only work, if data source binding is used for the list view.

    ReplyDelete
  9. That's overly complex.

    Why would you do that?

    If you have a listview, just do

    <ListView SelectedItem="TheSelectedItem" ... >

    and in the setter for TheSelectedItem do what you want with it.

    ReplyDelete
  10. I'm assuming you mean to use a binding to TheSelectedItem, but anyway that wouldn't work in my particular case for at least two reasons:

    1. I'm using a multi-selected ListView (SelectionMode of Multiple), so SelectedItem is meaningless.

    2. I only want to act in response to mouse double-clicks, not selection.

    Responding to a selection is easy, but it isn't what I'm trying to do. In any case, if you want take action on a selection change, a better way to do that is to use the SelectionChanged event.

    ReplyDelete
  11. You can use this as well:

    http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/681dea50-6bd2-4676-8161-14416386778d

    ReplyDelete
  12. Or..


    private void appList_MouseDoubleClick(object sender, RoutedEventArgs e) {
    ListViewItem app = (ListViewItem)appList.Items[appList.SelectedIndex];
    MessageBox.Show("you selected: " + app.Content);
    }

    ReplyDelete
  13. I'm not sure if it would work in your case, Mike, but this worked for me:
    private void ListViewItem_MouseDoubleClick(object sender, MouseButtonEventArgs e)
    {
    ListViewItem listViewItemSender = sender as ListViewItem;
    MyBusinessObject doubleClickedBO = myListView.ItemContainerGenerator.ItemFromContainer(listViewItemSender) as MyBusinessObject;
    }

    ReplyDelete
  14. Nice dude! That worked perfectly. :)
    I am trying to learn WPF and I had been struggling with this for days!
    Thanks!

    ReplyDelete
  15. No one is having a problem with the error "expression of type 'System.Windows.DependencyObject' can never be of type 'System.Windows.Forms.ListViewItem'"??? I'm not sure what I'm doing wrong here...

    ReplyDelete
  16. Thank you for this post!

    ReplyDelete
  17. This is really useful. Thanks for the tip about getting the index number from IndexFromContainer().

    ReplyDelete