Click Events for ListView item (aka ListView cell)
Suppose the user clicks a cell in a ListView. Is there an event that I can handle that will give me the row and column indexes of the cell that was clicked?
It sounds like such a simple thing, but a couple of us have looked at the problem and came up empty handed.
Answers
I came up with this approach, hopefully it will work in your situation.
Code Snippet<
ListView x:Name="listView" ItemsSource="{Binding}"><
ListView.View><
GridView AllowsColumnReorder="False"><
GridViewColumn Header="Name"><
GridViewColumn.CellTemplate><
DataTemplate><
Border Name="bdrIdx" Grid.Column="0"><
TextBlock Text="{Binding Name}" /></
Border></
DataTemplate></
GridViewColumn.CellTemplate></
GridViewColumn><
GridViewColumn Header="Age"><
GridViewColumn.CellTemplate><
DataTemplate><
Border Name="bdrIdx" Grid.Column="1"><
TextBlock Text="{Binding Age}" /></
Border></
DataTemplate></
GridViewColumn.CellTemplate></
GridViewColumn></
GridView></ListView.View>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<!-- Stretch the item content so that hit testing works for the
entire viewable surface of the ListViewItem. -->
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
</ListView>
void
listView_PreviewMouseDown(object sender, MouseButtonEventArgs e){
Border border; ListViewItem lvItem; if (FindBorderAndListViewItem(e.OriginalSource
as DependencyObject, this.listView, out border, out lvItem)){
ItemContainerGenerator generator = this.listView.ItemContainerGenerator; int rowIndex = generator.IndexFromContainer(lvItem);int columnIndex = Grid.GetColumn(border);
Debug.WriteLine("Row #: " + rowIndex);
Debug.WriteLine("Col #: " + columnIndex);
}
}
static
bool FindBorderAndListViewItem( DependencyObject elementUnderCursor, ListView listVw, out Border border, out ListViewItem lvItem){
border =
null;lvItem =
null;DependencyObject depObj = elementUnderCursor;
while (depObj != listVw)
{
if (border == null && depObj is Border)
{
border = depObj as Border;
// Only reference the named Border.
if (border.Name != "bdrIdx")border =
null;}
else if (depObj is ListViewItem){
lvItem = depObj
as ListViewItem;}
depObj =
VisualTreeHelper.GetParent(depObj);}
return border != null && lvItem != null;}
HTH!
All Replies
I came up with this approach, hopefully it will work in your situation.
Code Snippet<
ListView x:Name="listView" ItemsSource="{Binding}"><
ListView.View><
GridView AllowsColumnReorder="False"><
GridViewColumn Header="Name"><
GridViewColumn.CellTemplate><
DataTemplate><
Border Name="bdrIdx" Grid.Column="0"><
TextBlock Text="{Binding Name}" /></
Border></
DataTemplate></
GridViewColumn.CellTemplate></
GridViewColumn><
GridViewColumn Header="Age"><
GridViewColumn.CellTemplate><
DataTemplate><
Border Name="bdrIdx" Grid.Column="1"><
TextBlock Text="{Binding Age}" /></
Border></
DataTemplate></
GridViewColumn.CellTemplate></
GridViewColumn></
GridView></ListView.View>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<!-- Stretch the item content so that hit testing works for the
entire viewable surface of the ListViewItem. -->
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
</ListView>
void
listView_PreviewMouseDown(object sender, MouseButtonEventArgs e){
Border border; ListViewItem lvItem; if (FindBorderAndListViewItem(e.OriginalSource
as DependencyObject, this.listView, out border, out lvItem)){
ItemContainerGenerator generator = this.listView.ItemContainerGenerator; int rowIndex = generator.IndexFromContainer(lvItem);int columnIndex = Grid.GetColumn(border);
Debug.WriteLine("Row #: " + rowIndex);
Debug.WriteLine("Col #: " + columnIndex);
}
}
static
bool FindBorderAndListViewItem( DependencyObject elementUnderCursor, ListView listVw, out Border border, out ListViewItem lvItem){
border =
null;lvItem =
null;DependencyObject depObj = elementUnderCursor;
while (depObj != listVw)
{
if (border == null && depObj is Border)
{
border = depObj as Border;
// Only reference the named Border.
if (border.Name != "bdrIdx")border =
null;}
else if (depObj is ListViewItem){
lvItem = depObj
as ListViewItem;}
depObj =
VisualTreeHelper.GetParent(depObj);}
return border != null && lvItem != null;}
HTH!
- Thanks Josh. This works great an I think I can use the same technique to do some other things I want to do.
Jack,
Just out of curiosity, why do you need to gain access to the row and column indexes for the "cell" in which the user clicked? What functionality are you creating which requires that information? Thanks.
Josh,
We want the user to be able to multiply select cells. But not all combinations are valid. The rows represent batches. Some of the columns represent counts of errors in various categories for the batch. Other columns represent the name of the batch, the date, type of batch, etc. The user should be able to multiply select error count cells, but not cells with other kinds of information. Also they shouldn’t be able to multiply select counts from batches of different types.
When the user selects error count cells we show the individual errors that make up the count in another grid.
Jack,
Very interesting. Thanks for the explanation. Good luck with the project.
Hello,
I've tried to convert it to VB but it doesn't work

I couldn't find a substitute for the "out" keyword in C#,
and returning both values if not null ...
The two tests in the While loop simply never happens.
Basically what i'm trying to do is,
I implemented a Drag & Drop feature however it does Drag everytime,
even when i'm not clicking a cell and for example, clicking a scrollbar.
Do you have any idea about what could be wrong with it ?
Thanks for your help !
Private Sub ListViewMain_PreviewMouseDown(ByVal sender As Object, ByVal e As System.Windows.Input.MouseButtonEventArgs) Handles ListViewMain.PreviewMouseDown Dim border As Border Dim lvitem As ListViewItem If (FindBorderAndListViewItem(e.OriginalSource, ListViewMain, border, lvitem)) Then Dim generator As ItemContainerGenerator = ListViewMain.ItemContainerGenerator Dim rowindex As Integer = generator.IndexFromContainer(lvitem) Dim columnindex As Integer = Grid.GetColumn(border)Console.WriteLine(
"Row #: " + rowindex)Console.WriteLine(
"Col #: " + columnindex) End If
End Sub
Private Function FindBorderAndListViewItem(ByVal elementUnderCursor As DependencyObject, ByVal listVw As ListView, ByVal border As Border, ByVal lvitem As ListViewItem) As Booleanborder =
Nothinglvitem =
Nothing Dim depobj As DependencyObject = elementUnderCursor
' Find parent While depobj IsNot listVw
If border Is Nothing AndAlso depobj Is border Thenborder = depobj
If border.Name <> "bdrIdx" Then border = Nothing ElseIf depobj Is lvitem Thenlvitem = depobj
End If ' Find next parentdepobj = VisualTreeHelper.GetParent(depobj)
End While 'Return True End Function- Hi,
Can we access the each cell from ListView without clicking on the particular Item or cell?
I want change the cellTemplate for individual cells. Please tell me how to access the individual cell and apply the style for that particular cell without affecting other cells.
Warm Regards,
Manjunath V - Hi, I read your conservation. I am having a question, thought anybody of you solve my problem,
the sceneario is I have a tree contol and a Listview control. In list view control i have three columns.
out of three one will be empty. what i need to do is to drag the item from tree and drop it in the third blank column in the listview control? is this possible ? i need to devlope in C# 2005. help me :(
thanks in adv.
S/W Devep - I have the below solution.I have a ListView, has 5 columns, Name, OfficeNumber, MobileNumber, HomeNumber,Email. I want to determine which number(office,mobile,home) that a user has clicked.The ContactEntry is an object holds the above properties.GetContactEntryList returns a ObservableCollection of ContactEntry).
<ListView Name="contactsListView" Grid.Row="0" Grid.Column="0" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" Width="320" Height="320" Visibility="Collapsed" ItemsSource="{Binding ElementName=This, Path=GetContactEntryList}" VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling" IsSynchronizedWithCurrentItem="True" MouseDoubleClick="contactsListView_MouseDoubleClick"> <ListView.ItemContainerStyle> <Style TargetType="ListViewItem"> <Setter Property="Height" Value="20" /> </Style> </ListView.ItemContainerStyle> <ListView.View> <GridView AllowsColumnReorder="False"> <GridViewColumn Width="95" DisplayMemberBinding="{Binding FullName}"> <GridViewColumnHeader Tag="FullName" Content="{StaticResource TEXT_NAME}"/> </GridViewColumn> <GridViewColumn Width="85" DisplayMemberBinding="{Binding OfficePhone}"> <GridViewColumnHeader Tag="OfficePhone" Content="{StaticResource TEXT_OFFICE}"/> </GridViewColumn> <GridViewColumn Width="80" DisplayMemberBinding="{Binding MobilePhone}"> <GridViewColumnHeader Tag="MobilePhone" Content="{StaticResource TEXT_MOBILE}"/> </GridViewColumn> <GridViewColumn Width="Auto" DisplayMemberBinding="{Binding HomePhone}"> <GridViewColumnHeader Tag="HomePhone" Content="{StaticResource TEXT_HOME}"/> </GridViewColumn> <GridViewColumn Width="Auto" DisplayMemberBinding="{Binding Email}"> <GridViewColumnHeader Tag="Email" Content="{StaticResource TEXT_EMAIL}"/> </GridViewColumn> </GridView> </ListView.View> </ListView>
private void contactsListView_MouseDoubleClick(object sender, MouseButtonEventArgs e) { object obj = this.contactsListView.SelectedItem; if (obj is ContactEntry) { NumberType type = GetCallNumberType(this.contactsListView, e.GetPosition(this.contactsListView)); if (type != NumberType.UNKNOWN) { //we know which number user clicked } } }public NumberType GetCallNumberType(ListView listView, Point point) { Decorator border = VisualTreeHelper.GetChild(listView, 0) as Decorator;//Border is the first child of a Listview ScrollViewer scroll = border.Child as ScrollViewer; point.X = point.X + scroll.ContentHorizontalOffset; scroll = null; border = null; double pStart = 0; double pEnd = 0; GridView gv = listView.View as GridView; for (int i = 0; i < gv.Columns.Count; i++) { pEnd = pStart + gv.Columns[i].ActualWidth; if (point.X > pStart && point.X < pEnd) { try { GridViewColumnHeader header = gv.Columns[i].Header as GridViewColumnHeader; if (header != null && header.Tag is string) { string tag = header.Tag as string; header = null; gv = null; if (tag.Equals("OfficePhone")) { return NumberType.OFFICE_PHONE; } else if (tag.Equals("MobilePhone")) { return NumberType.MOBILE_PHONE; } else if (tag.Equals("HomePhone")) { return NumberType.HOME_PHONE; } else { return NumberType.UNKNOWN; } } } catch (Exception) { return NumberType.UNKNOWN; } } pStart = pEnd; } return NumberType.UNKNOWN; }
The important section isDecorator border = VisualTreeHelper.GetChild(listView, 0) as Decorator;//Border is the first child of a Listview ScrollViewer scroll = border.Child as ScrollViewer; point.X = point.X + scroll.ContentHorizontalOffset; scroll = null; border = null; double pStart = 0; double pEnd = 0; GridView gv = listView.View as GridView; for (int i = 0; i < gv.Columns.Count; i++) { pEnd = pStart + gv.Columns[i]. ActualWidth;<br/> if (point.X > pStart && point.X < pEnd) { //this was clicked } }If you don't use the code below, when the column resize too wide, you will not able to detect the correct column.Decorator border = VisualTreeHelper.GetChild(listView, 0) as Decorator;//Border is the first child of a Listview ScrollViewer scroll = border.Child as ScrollViewer; point.X = point.X + scroll.ContentHorizontalOffset;Hope this can help anyone out there need a solution.


