WPF DataGrid – detecting the column, cell and row that has been clicked

December 2nd, 2008 by Colin Eberhardt

The WPF DataGrid is a very flexible tool, however in its current state certain simple tasks can prove to be rather tricky. A fairly common task when working with DataGrid is detecting which row, or cell a user has clicked on, or whether they clicked a column header. You might expect this information to be readily available in the form of events, after all, the Windows Forms DataGridView has CellClicked and ColumnHeaderMouseClick events (among many others). However, sadly this is not the case. In order to implement this behaviour you have to understand the visual tree of the DataGrid and how it can be navigated.

Let’s say for example you wanted to find the DataGrid item (cell, row, header) that was clicked when the right mouse button is released. Firstly, we add an event handler for the mouse click in our code-behind:

XAML:

<dg:DataGrid Name="DataGrid"
             MouseRightButtonUp="DataGrid_MouseRightButtonUp"/>

C#:

private void DataGrid_MouseRightButtonUp(object sender,
                                        MouseButtonEventArgs e)
{
}

The above event is a ‘bubbling’ event, which means that it it started on the element that was originally clicked (for example a TextBlock which renders the cell’s value within a DataGridCell), then bubbled up the logical tree until it reaches our event handler in the Window. The e.OriginalSource property gives us access to the element that initiated this event.

The problem is that while we have access to the lement which was clicked on, this element is part of the control or data template of the element that we are really interested, the cell or header. The WPF rich-content model means that our cells could contain all sorts of visual element, therefore we have no way of guessing exactly what e.OriginalSource will be. However, the one thing of which we can be certain is that this element is a child of the element which we are interested in.

If you place a bearkpoint within yoru event handler, you can then use the excellent Mole debug visualiser to locate the clicked element within the visual tree as illustrated below:

dgvisualtree

As you can see, the visual tree is a complex beast! I have highlighted the items of interest:

  1. The TextBlock, which is the element I clicked on, which is e.OriginalSource parameter.
  2. The DataGridCell, the cell which was clicked on.
  3. The DataGridRow which the cell belongs to.
  4. And finally, the DataGrid.

Therefore, in order to locate the cell and row that was clicked on we must navigate up the Visual Tree, searching by type:

private void DataGrid_MouseRightButtonUp(object sender,
                                                  MouseButtonEventArgs e)
{
    DependencyObject dep = (DependencyObject)e.OriginalSource;
 
    // iteratively traverse the visual tree
    while ((dep != null) &amp;&amp;
            !(dep is DataGridCell) &amp;&amp;
            !(dep is DataGridColumnHeader))
    {
        dep = VisualTreeHelper.GetParent(dep);
    }
 
    if (dep == null)
        return;
 
    if (dep is DataGridColumnHeader)
    {
        DataGridColumnHeader columnHeader = dep as DataGridColumnHeader;
        // do something
    }
 
    if (dep is DataGridCell)
    {
        DataGridCell cell = dep as DataGridCell;
        // do something
    }
}

Fantastic, we have now have our header and cell. All that’s left to do is extract the cell’s row and column indices, and cell value. Wait a minute … where are the cell.RowIndex and cell.ColumnIndex properties? It looks like there’s more work to be done.

Once we have navigated up the tree to the DataGridCell, we can continue our journey upwards to obtain the DataGridRow:

if (dep is DataGridCell)
{
    DataGridCell cell = dep as DataGridCell;
 
    // navigate further up the tree
    while ((dep != null) &amp;&amp; !(dep is DataGridRow))
    {
        dep = VisualTreeHelper.GetParent(dep);
    }
 
    DataGridRow row = dep as DataGridRow;
}

Does the DataGridRow have a RowIndex property? I think you can guess the answer to that question.

The DataGrid is an ItemsControl – WPF users are probably most familiar with the ListView which is also an ItemsControl whcih has a number of similarities with the DataGrid. In the ItemsControl terminology, the DataGridRow is an ItemContainer and the DataGrid has an ItemContainerGenerator associated with it for generating the rows. I don’t want to go into the details of how ItemContainers work, Dr. WPF has a good series on the ItemsControl for those who are interested. The following code can be used to determine the index of a row:

private int FindRowIndex(DataGridRow row)
{
    DataGrid dataGrid =
        ItemsControl.ItemsControlFromItemContainer(row)
        as DataGrid;
 
    int index = dataGrid.ItemContainerGenerator.
        IndexFromContainer(row);
 
    return index;
}

Now that we have the row index, the column index is thankfully a little easier to locate, cell.Column.DisplayIndex does the trick. The final piece of information which we might like is the cell value. Is there a cell.Value properly? don’t make me laugh!

The following method determines the property binding for the cells column, then extracts the value from the data items associated with the row:

private object ExtractBoundValue(DataGridRow row,
                                 DataGridCell cell)
{
    // find the column that this cell belongs to
    DataGridBoundColumn col =
       cell.Column as DataGridBoundColumn;
 
    // find the property that this column is bound to
    Binding binding = col.Binding as Binding;
    string boundPropertyName = binding.Path.Path;
 
    // find the object that is related to this row
    object data = row.Item;
 
    // extract the property value
    PropertyDescriptorCollection properties =
        TypeDescriptor.GetProperties(data);
 
    PropertyDescriptor property = properties[boundPropertyName];
    object value = property.GetValue(data);
 
    return value;
}

Putting it all together, this blog post has a small smaple application which displays the header index and value, or cell’s row/column indices and value in response to a right mouse click:

clickedvalue

The sample project can be download, wpfdatagridmouseclicks, changing the file extension from .doc to .zip.

Regards, Colin E.

Tags: ,

54 Responses to “WPF DataGrid – detecting the column, cell and row that has been clicked”

  1. Andy P says:

    Thanks for this.
    I was suprised to not find an example of this more easily on-line.
    I had a problem in a customised DataGrid where I could select a whole row in the grid but then still click a button on a different row … hence making the use of DataGrid.SelectedIndex useless when trying to access the button’s DataGridRow in the Button_Click event.
    Using your example code I can now get to the relevant DataGridRow.
    cheers
    Andy

  2. Morgann says:

    Thanks a lot Colin,
    This is very clear and saves me hours.

    Best regards
    Morgann

  3. pk says:

    Thank you very much Colin. It was difficult to find help on this

    ciao

  4. Stewbob says:

    Thanks. This post is still solving people’s problems 3 1/2 years later. Saved me a ton of work.

  5. Magesh_LB says:

    Hi Collin,

    By using the following simple code we can achieve it,

    We can get column index and Row index
    and We can get the Selected Cell value too.

    To get Column Index: gridER.CurrentCell.Column.DisplayIndex
    TO Get the Row Index : gridER.SelectedIndex

    To Get Cell Value:

    DataRowView drv = gridER.SelectedItem as DataRowView;
    DataRow dr = drv.Row;
    MessageBox.Show(drv.ItemArray[gridER.CurrentCell.Column.DisplayIndex].ToString());

    • Max says:

      DataRowView drv = dataGrid.SelectedItem as DataRowView;
      if (drv != null)
      {
      MessageBox.Show(drv.Row.ItemArray[dataGrid.CurrentCell.Column.DisplayIndex].ToString());
      }

  6. Bhushan says:

    Nice article.Helped me to get row from mouse down e.original source

  7. Need a SL4 datagrid for each statement to loop thru my grid.

  8. David says:

    Thanks Colin for a great article. Here’s a VB.net version for anyone who finds this article from your favorite search engine:

    Private Sub DataGrid_MouseRightButtonUp(ByVal sender As Object, ByVal e As System.Windows.Input.MouseButtonEventArgs)
    Dim dep As DependencyObject = DirectCast(e.OriginalSource, DependencyObject)

    ‘ iteratively traverse the visual tree
    While dep IsNot Nothing And Not TypeOf dep Is DataGridCell And Not TypeOf dep Is Primitives.DataGridColumnHeader
    dep = VisualTreeHelper.GetParent(dep)
    End While

    If dep IsNot Nothing Then
    If TypeOf dep Is Primitives.DataGridColumnHeader Then
    Dim columnHeader As Primitives.DataGridColumnHeader = DirectCast(dep, Primitives.DataGridColumnHeader)
    ‘ do something
    End If

    If TypeOf dep Is DataGridCell Then
    Dim cell As DataGridCell = DirectCast(dep, DataGridCell)
    ‘ do something
    End If
    End If
    End Sub

    If TypeOf dep Is DataGridCell Then
    Dim cell As DataGridCell = DirectCast(dep, DataGridCell)

    ‘ navigate further up the tree
    While dep IsNot Nothing And Not TypeOf dep Is DataGridRow
    dep = VisualTreeHelper.GetParent(dep)
    End While

    Dim row As DataGridRow = DirectCast(dep, DataGridRow)
    End If

    Private Function FindRowIndex(ByVal row As DataGridRow) As Integer
    Dim dataGrid As DataGrid = DirectCast(ItemsControl.ItemsControlFromItemContainer(row), DataGrid)

    Dim index As Integer = dataGrid.ItemContainerGenerator.IndexFromContainer(row)

    Return index
    End Function

    Private Function ExtractBoundValue(ByVal row As DataGridRow, ByVal cell As DataGridCell) As Object
    ‘ find the column that this cell belongs to.
    Dim col As DataGridBoundColumn = DirectCast(cell.Column, DataGridBoundColumn)

    ‘ find the property that this column is bound to.
    Dim binding As Binding = DirectCast(col.Binding, Binding)
    Dim boundPropertyName As String = binding.Path.Path

    ‘ find the object that is related to this row.
    Dim data As Object = row.Item

    ‘ extract the property value
    Dim properties As PropertyDescriptorCollection = TypeDescriptor.GetProperties(data)

    Dim prop As PropertyDescriptor = properties(boundPropertyName)
    Dim value As Object = prop.GetValue(data)

    Return value
    End Function

  9. Ripu says:

    private int FindRowIndex(DataGridRow row)
    {
    DataGrid dataGrid =
    ItemsControl.ItemsControlFromItemContainer(row)
    as DataGrid;

    int index = dataGrid.ItemContainerGenerator.
    IndexFromContainer(row);

    return index;
    }
    ————-hey this is giving me error that is:- Cannot convert type ‘System.Windows.Controls.ItemsControl to ‘System.Windows.Controls.DataGrid via a reference conversion,boxing conversion,unboxing conversion,wrapping conversion,or null type conversion—————–
    plz help me
    thanks ——

  10. Raju says:

    Hi,

    I created datagrid and if i select any one row i can get selected cell value. But i need to get all the cell value of that particular row. I tried with above code it will work for only selected cell. How to do for getting all the cell value of that particular row.
    Thanks in advance.

  11. shasha says:

    Hi, I try to use your code because I want to be like this. If I click on any row under Column 1, I will trigger a message box. If I click on any row under Column 2, different messagebox will appear. But why ClickedItemDisplay is consider error in my code?

  12. dominic says:

    Nice and clear article thanks! Weird how this wasnt expressed well elsewhere on the net.

  13. Mark says:

    Very helpful post. Thanks. Given these glaring deficiencies, why not use, say, ComponentOne’s WPF DataGrid instead?

    http://helpcentral.componentone.com/nethelp/c1datagridwpf/

    Obv. not cost, assuming your time is worth $100+/hour (don’t know what that is in pounds).

  14. brix says:

    Hi,
    Need to detect editing a cell and bind to a command in a mvvm pattern.
    Can be done?
    Thanks for your time

  15. George says:

    I was trying to find the row number of the selected DataGrid item. This article was the closest thing I could find by searching. The problem was I use a context menu (not a mouse click) to initiate the routine that needs to know the row number. After a day of experimenting, I finally came up with this code that seems to be the simplest method to determine the row number.

    DataRowView prop = (DataRowView)myDataGrid.SelectedValue;
    DataRow rowData =(DataRow)prop.Row;
    int selectedRow = _myDataTable.Rows.IndexOf(rowData);

    myDataGrid is the grid name. _myDataTable is the name of the datatable to fills the grid.

    Hope this helps someone else.

  16. Why can’t you do it similar to the .Net way?
    see:
    http://blog.bitlinkit.com/post/Right-Click-and-Auto-Row-Select-with-a-
    ContextMenuStrip.aspx

    extract:
    foreach (DataGridViewRow s in dataGridView1.SelectedRows)

    s.Selected = false;

    DataGridView.HitTestInfo hit = dataGridView1.HitTest(e.X, e.Y);

    dataGridView1.Rows[hit.RowIndex].Selected = true;

    dataGridView1.CurrentCell= dataGridView1.Rows[hit.RowIndex].Cells[hit.ColumnIndex];

    contextMenuStrip1.Show(dataGridView1, new Point(e.X,e.Y) );

  17. Sorry Sukumar, I do not have time to answer queries such as this. However, if you post to the WPF Toolkit forums I am sure someone will help you out:

    http://wpf.codeplex.com/Thread/List.aspx

    Colin E.

  18. Sukumar says:

    I am using your code for getting row value from datagrid, but I would like the same thing happening with mouse doubleclick event, not with mouse right click event.

    the dep value after execution of this statement “DependencyObject dep = (DependencyObject)e.OriginalSource;” shows ‘system.windows.controls.textblock’ for both events that when I doubleclick the row and when I right click the row

    But after execution of following statement:

    dep = VisualTreeHelper.GetParent(dep);

    dep value is ‘null for mouse doubleclick event and

    dep value is presenter(it’s parent element) for mouse right click event.

    Can you help me? Thanks.

  19. Devi says:

    hi colin,

    I want to add Image as a column to the DataGrid dynamically.I coded like this,But its not working am unable to see the Image in the datagrid column.Can u plz help me out..

    string backgroundImagePath=”Resources\Hand.jpg”;
    BitmapImage bitmapImage = new BitmapImage();
    Uri objUri = new Uri(backgroundImagePath);
    bitmapImage.SetValue(BitmapImage.UriSourceProperty, objUri);

    FrameworkElementFactory factory1 = new FrameworkElementFactory(typeof(Image));
    factory1.SetValue(Image.SourceProperty, bitmapImage);

    DataTemplate cellTemplate1 = new DataTemplate();
    cellTemplate1.VisualTree = factory1;

    DataGridTemplateColumn col1 = new DataGridTemplateColumn();
    col1.Header = “MyHeader”;
    col1.CellTemplate = cellTemplate1;

    dgDynamicColumns.Columns.Add(col1);

    Thanks In advance,
    Devi

  20. sachin says:

    Hi Colin,

    I dont get default context menu (copy, cut, paste) to show up on datagrid row mouse right click. Context menu shows up when grid cell is double click which puts cell in edit mode. Any help would be greatly appreciated.

    Thanks,
    sachin

  21. saru says:

    hi colin,

    i want to change Border style to Datagrid with corner radius and all how can i do that.
    Thanks in advance….

    Saru

  22. saru says:

    hi,
    colin with your source code support i learned and implemented a lot in wpf datagrid.
    Now my new problem is in my datagrid i am binding data dynamically i want to set RowBackground of each row with different color from code like for example in my datagrid will look like this
    —————————————————————–
    Category | Name | Year | Enter Value|
    ————————————-
    Cat1 | | | |
    ————————————–
    | S1 |2001 | |
    ————————————–
    | s2 |2002 | |
    ————————————–
    Cat2 | | | |
    ————————————–

    i want to highlight Row with only Category and Want to give different Background color for EnterValue column how can we approach this

  23. saru says:

    Hi ur style property for datagrid worked fine for old version of Datagrid but now i downloaded newer version and there is no “DataGridColumnHeader” property. SO how to Allign Header text in center in new version datagrid.

    And also one more question is for my requirement i want to show sun columns in a column like for example i have a column called “Share Of Market” in this column we have to show 2 sub columns SME and LME like think the below as column
    —————–
    Share Of Market |
    —————-
    SME | LME |
    ————-
    | |
    ———————

  24. Kelvin says:

    Great article.

    Is there any way to loop through all of the cells in a datagrid? I’m trying to make an Excel XML export routine for the WPF Toolkit datagrid similar to the Xceed Excel export routine.

  25. Jing Boxian says:

    And to get the DataGridRow of a DataGridCell

    maybe we can use

    DataGridRow row = DataGridRow.GetRowContainingElement(cell);

    Regards, Jing

    • @Jing,

      Thanks for pointing those two methods out. Whether they are new additions, or I just missed them I am not sure!

      I had a quick look at the implementation, and internally they use the same implementation i present here, walking the visual tree, and using ItemsControl.ItemsControlFromItemContainer.

      Thanks, Colin E.

  26. Jing Boxian says:

    Hi, I think in the current version of the toolkit, we can get row index by calling the row.GetIndex().

    Regards, Jing

  27. saru says:

    hi,thanks for ur reply but its not working for me i dont have property like u gave in style x:Key so i modified like this but its not wrking

    please correct me

    Thank you,
    sarada

  28. saru says:

    hi,
    ur post is very useful. i have one question i’m using the WPF Datagrid in my application by default the column header text is alligned left but i want to show the column header text in center.How to do this any ideas plz help me out its urgent.

    Thanks,
    Saru.

    • Try the following style:

      <Style x:Key="HeaderStyle" TargetType="{x:Type dg:DataGridColumnHeader}">
          <Setter Property="HorizontalContentAlignment" Value="Center"/>
      </Style>
      
      <dg:DataGrid ColumnHeaderStyle="{StaticResource HeaderStyle}"/>
      

      Regards, Colin E.

  29. ceberhardt says:

    Tom, Sorry – I haven’t played much with the combo box column. I would try directing this comment to the WPF toolkit forums:

    http://www.codeplex.com/wpf/Thread/List.aspx

    Regards, Colin E.

  30. ceberhardt says:

    Tullio,

    To extract values for a single column, I presume this value is bound to a specific property of your dataobject? In ExtractBoundValue notice the following lines:

    // find the object that is related to this row
    object data = row.Item;

    Once you have the bound object, you can do what you like. You can cast it to the correct type and manipulate it directly:

    Person person = data as Person;
    string voiced = person.VoicedBy;

    i.e. this will always extract the VoicedByProperty of the item bound to the row that was clicked.

    Hope this makes sense!
    Regards, Colin E.

  31. Tullio says:

    Hi Colin,

    I am studing your example … great but a bit difficult for me (I am a beginner).

    I need a method to obtain data just for one particular column, if I select the row 3 I want to see the data in cell [3, 3], in the same way, if I select the row 4 I want to see the data in cell [4, 3].

    Exist a easier way to obtain my data? I have to modify your example?

    Anyway, great example
    Tullio

  32. Insyst_Tom says:

    Colin,

    Your ExtractBoundValue method works for the DataGridTextColumn which inherits from DataGridBoundColumn which implements the Binding property. But the DataGridComboBoxColumn inherits from DataGridColumn which does not have the Binding property. Instead the DataGridComboBoxColumn implements SelectedValueBinding, SelectedItemBinding, and TextBinding. Yikes.

    Short of hard codeing as switch statement based on the type of column (which will only get harder with the DataGridTemplateColumn), do you know a way to find the bound property name on these other columns?

    Thanks,
    Tom

  33. Hey Colin,

    Really great stuff! Keep it up.

  34. klauswiesel says:

    Colin,

    another one that falls into this context: how can I catch the event that is fired when a checkbox in a checkbox column is clicked (with left button/or by pressing space or other accelerators) ?

    Regards
    Klaus

  35. klauswiesel says:

    yepp, thanks, thats what I meant

    Regards
    Klaus

  36. klauswiesel says:

    Hi Colin

    could you help me out to get the same info for right clicking on a column header?

    Thanks
    Klaus

  37. sreeraj says:

    Very use ful article . I was on research in this for past few days . To get the controls inside datatemplate in a grid cell, i think we can use this way
    ContentPresenter objPresent = Mygrid.Columns[ColumnNumber].GetCellContent(e.Row) as ContentPresenter;

    DependencyObject objdep = objPresent.ContentTemplate.LoadContent();
    from this dependancy object , we can access the control

  38. ceberhardt says:

    Hi Klaus,

    Have you tried the sample application associated with this post? When you right click on a column header it should output the column index and the property which the column is bound to. e.g.

    “Header clicked [1] = Forename”

    Is that what you wanted?

    [ ---- edit ----]

    Hang on … did you mean the row header?

    If so, simply navigate up the visual tree to the row in the same way that you do for the cell. Try the following code snippet in the sample application:

    DependencyObject dep = (DependencyObject)e.OriginalSource;

    while ((dep != null) && !(dep is DataGridCell) &&
    !(dep is DataGridColumnHeader) && !(dep is DataGridRowHeader))
    {
    dep = VisualTreeHelper.GetParent(dep);
    }

    if (dep == null)
    return;

    if (dep is DataGridRowHeader)
    {
    DataGridRowHeader rowHeader = dep as DataGridRowHeader;

    // navigate further up the tree
    while ((dep != null) && !(dep is DataGridRow))
    {
    dep = VisualTreeHelper.GetParent(dep);
    }

    if (dep == null)
    return;

    DataGridRow row = dep as DataGridRow;

    int rowIndex = FindRowIndex(row);

    ClickedItemDisplay.Text = string.Format(
    “Row header clicked [{0}]“,
    rowIndex);
    }

    Colin E.

Leave a Reply