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.

16 comments:

K.V said...

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

Anonymous said...

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

Andreas said...

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

Chester said...

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.

Anonymous said...

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

Andrew Smith said...

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.

David said...

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

Anonymous said...

hi
great code tnx!

Anonymous said...

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.

Anonymous said...

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.

Mike said...

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.

Anonymous said...

You can use this as well:

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

Anonymous said...

Or..


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

Anonymous said...

Great post!

Anonymous said...

Very Good post!

René said...

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

Post a Comment