Sunday, May 2, 2010

Get WPF DataGrid row and cell

There are no simple built-in methods for accessing individual rows or columns in WPF DataGrid. The following code samples will provide simple methods for accessing these items.

First of all we need a helper function for selecting a visual child:

public static T GetVisualChild<T>(Visual parent) where T : Visual
{
    T child = default(T);
    int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
    for (int i = 0; i < numVisuals; i++)
    {
        Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
        child = v as T;
        if (child == null)
        {
            child = GetVisualChild<T>(v);
        }
       if (child != null)
       {
           break;
       }
   }
       return child;
}

There is a simple method for getting the current (selected) row of the DataGrid:

public static DataGridRow GetSelectedRow(this DataGrid grid)
{
    return (DataGridRow)grid.ItemContainerGenerator.ContainerFromItem(grid.SelectedItem);
}

We can also get a row by its indices:

public static DataGridRow GetRow(this DataGrid grid, int index)
{
    DataGridRow row = (DataGridRow)grid.ItemContainerGenerator.ContainerFromIndex(index);
    if (row == null)
    {
        // May be virtualized, bring into view and try again.
        grid.UpdateLayout();
        grid.ScrollIntoView(grid.Items[index]);
        row = (DataGridRow)grid.ItemContainerGenerator.ContainerFromIndex(index);
    }
    return row;
}

Now we can get a cell of a DataGrid by an existing row:

public static DataGridCell GetCell(this DataGrid grid, DataGridRow row, int column)
{
    if (row != null)
    {
        DataGridCellsPresenter presenter = GetVisualChild<DataGridCellsPresenter>(row);

        if (presenter == null)
        {
            grid.ScrollIntoView(row, grid.Columns[column]);
            presenter = GetVisualChild<DataGridCellsPresenter>(row);
        }

        DataGridCell cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(column);
        return cell;
    }
    return null;
}

Or we can simply select a row by its indices:

public static DataGridCell GetCell(this DataGrid grid, int row, int column)
{
    DataGridRow rowContainer = grid.GetRow(row);
    return grid.GetCell(rowContainer, column);
}

The functions above are extension methods. Their use is simple:

var selectedRow = grid.GetSelectedRow();
var columnCell = grid.GetCell(selectedRow, 0);

View source on Google Code (unformatted selection possible)

25 comments:

  1. Is there a way to get the column selected ?

    ReplyDelete
  2. methods return null always

    ReplyDelete
  3. It works for me. The reference implementation is on Google Code, see link above. That should work.

    Please try to identify the line that causes the null value.

    ReplyDelete
  4. Thanks for this class. Great job!
    2 small comments:
    1) the namespace DataGrid.Helpers cause a compiler error since "DataGrid" is a type.
    Solution: Change the namespace to something
    else (like DataGrids.Helpers)

    2) "Methods return null always" : on my side the problem came from GetSelectedRow and GetRow(int index).
    These functions return null if they are called BEFORE the grid have been (re-)displayed.
    I was :
    - adding a new item to an existing grid from code.
    - set grid.SelectedItem=TheNewItem
    - a call to grid.GetSelectedRow returned null
    - a call to grid.GetRow returned null

    I handle what I wanted to do by using grid.GetCell(Grid.SelectedIndex,ThecolumnNumber).

    Thanks again for your code. Very helpful:)
    Jacques

    ReplyDelete
  5. Thanks, but some questions:

    When using loadingRow event, I can't get the cell (null) because there is no cell build yet. How can I set this (to be build) cell to change the color, font etc ? Any ideas?

    ReplyDelete
  6. this post really helped me thanks a ton!

    ReplyDelete
  7. dude.. you are the master!!!

    ReplyDelete
  8. Great article helped alot. With this technique i was able to get all cells in my datagrid and custom style them the way i needed them. Thanks!

    ReplyDelete
  9. Hallo,

    I have the same problem like the person before:

    Iam using loadingRow event on datagrid. Here the return value of the GetCell() Methode is always null!
    How can i solve that?

    Thanks

    ReplyDelete
  10. Being a bit more specific than my last question I would ask how do you not reference the static class with the extension method in it. In this case it would be "MyStaticClass.GetCell". My static class is in the same namespace as the rest of the code so I have no namespace reference but there is no "GetCell" method available without first referencing the static class.

    Thanks

    ReplyDelete
  11. The signature of the method:
    public static DataGridCell GetCell(this DataGrid grid, DataGridRow row, int column)

    The *this DataGrid* part indicates you should call the extension method on the DataGrid instance you want to work with. So you should just call *myDataGridInstance.GetCell(row, column)*.

    I hope I didn't misunderstand you question and it helps.

    ReplyDelete
  12. It helps very much. The error message I was seeing said there was no "GetCell" however a little farther into the text it referenced the extension method so I was a little mixed up. Now that I have the static class and methods I have the following in my code to use the class.

    " this.tbWeight.Text = TravelerdataGrid.GetCell(row, 1).ToString();'

    I ma getting the following in my text box:

    "System.Windows.Controls.DataGridCell"

    I was expecting to see the value in the cell. Am I still missing something?

    ReplyDelete
  13. This is the way .NET FW works. If you don't override the ToString() function, it returns the class name.

    You should try calling TravelerdataGrid.GetCell(row, 1).Content() to get the content of the DataGridCell. See http://msdn.microsoft.com/en-us/library/system.windows.controls.contentcontrol.content.aspx

    ReplyDelete
  14. Hello,

    One question I cannot find property:

    "ItemContainerGenerator" in my data grid object. Can you please tell me what wrong I am doing.

    Thanks in advance.

    Sonal Savaratkar

    ReplyDelete
  15. ItemContainerGenerator is a method to be found in objects of type DataGridCellsPresenter. However, If it is your intent to extract data from a cell in a DataGrid you will discover this can't be done with a WPF DataGrid. There are many partial demonstrations of how to do this but none actually assign the content of a cell to a text box or other variable. I would suggest exploring other commercially available controls.

    ReplyDelete
  16. This is great, any way you could do a getSelectedCells function?

    ReplyDelete
  17. If you examine Arturs' response about getting the content of the cell, it becomes evident that all of these functions produce the class if there is no reference to the content. I have found this to be true with all the functions I have tried even when the content is referenced. I believe I tried getSelectedCells with the same result but you definitely should try it for yourself. I don't recall the syntax or I would share it with you. I am looking into a product named Telerik that supposedly can handle this.

    ReplyDelete
  18. I was able to do this with my traveler object very easlly using WPF tools from Telerik and the following code ( I don't work for Telerik):

    private void TravelerDetail_SelectionChanged(object sender, SelectionChangeEventArgs e)
    {

    Traveler row = this.TravelerDetail.SelectedItem as Traveler;

    if(row.WeightCodeId != null)
    this.tbWeight.Text = row.WeightCodeId.ToString();

    if(row.RangeHigh != null)
    this.tbRangeHigh.Text = row.RangeHigh.ToString();

    if(row.RangeLow != null)
    this.tbRangeLow.Text = row.RangeLow.ToString();

    }

    ReplyDelete
  19. Thank you for your wonderful posting...

    If i want to load a Datagrid with my given (X,Y) coordinate value for the datagrid & a given value to show in Datagrid cell then how to..?

    ReplyDelete
  20. Use Grid.Column[x].GetCellContent(Row[y]) method
    It is much easier

    ReplyDelete
  21. I'm using .NET 3.5 [ thats my requirement ] and I dont find DataGridCellsPresenter in .NET 3.5. What is the alternative?

    ReplyDelete
  22. great article....

    ReplyDelete
  23. Really good article, especially the cell selection part I was struggling with that.
    Keep it up.

    ReplyDelete
  24. if the methods return null
    put this code before you use them.

    #region start the generator manually
    IItemContainerGenerator generator = (YourDataGrid).ItemContainerGenerator;
    GeneratorPosition position = generator.GeneratorPositionFromIndex(0);
    using (generator.StartAt(position, GeneratorDirection.Forward, true))
    {
    foreach (object o in (YourDataGrid).Items)
    {
    DependencyObject dp = generator.GenerateNext();
    generator.PrepareItemContainer(dp);
    }
    }
    #endregion

    ReplyDelete