My blog about application development on the .NET platform.

How to programmatically select and focus a row or cell in a DataGrid in WPF

You may have tried to select a row in a DataGrid in WPF programmatically by setting its SelectedItem property or SelectedIndex property only to find out that doing this doesn’t result in the exact same behaviour as when you select a row by clicking on it with the mouse.

Setting any of these properties in code does in fact select the row and give it some kind of focus but the behaviour is slightly different compared to when clicking on it. For example, the row doesn’t get highlighted the same way and if you try to use the arrow keys of the keyboard to navigate between the rows after you have set any of the mentioned properties in code the row will lose its focus.

It is however possible to select and focus a row in code and get the same behaviour as when using the mouse by accessing the visual user interface elements of the DataGrid control and calling the UIElement.Focus() method on a particular System.Windows.Controls.DataGridCell object as described in this post. You can also select an individual cell using almost the same approach.

DataGrid.SelectionUnit

The DataGrid control has a SelectionUnit property that decides whether rows, cells or both can be selected. For you to be able to set the SelectedItem or SelectedIndex properties of the DataGrid without an exception being thrown, this property must be set to its default value of System.Windows.Controls.DataGridSelectionUnit.FullRow.

There is also a SelectionMode property that specifies whether only a single item or multiple items can be selected in the DataGrid and these two properties together defines the selection behaviour for the DataGrid control.

The visual tree

The visual tree in a WPF application describes the structure of all visual elements that are part of the user interface, i.e. everything you see on the screen.

The DataGrid control renders a System.Windows.Controls.DataGridRow object for each data object in its Item collection and a System.Windows.Controls.DataGridCell for each cell of each row.

There is a built-in System.Windows.Media.VisualTreeHelper class that provides functionality for enumerating the members of a visual tree and this one is very useful whenever you need programmatic access to any object in the visual tree. The VisualTreeHelper class is for example used in the below generic method that recursively searches for a user interface element of a specific type among the descendants of a visual object of type System.Windows.DependencyObject – this is the base class for all visual elements that adds support for dependency properties – that is passed to the method as a parameter. The specific type to search for is specified by the type argument for the method.

public static T FindVisualChild<T>(DependencyObject obj) where T : DependencyObject
{
    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
    {
        DependencyObject child = VisualTreeHelper.GetChild(obj, i);
        if (child != null && child is T)
            return (T)child;
        else
        {
            T childOfChild = FindVisualChild<T>(child);
            if (childOfChild != null)
                return childOfChild;
        }
    }
    return null;
}

Selecting a single row

You can get hold of the generated DataGridRow element in the visual tree that corresponds to a given item or index within a DataGrid’s collection of items by using the ContainerFromItem or ContainerFromIndex methods of the System.Windows.Controls.ItemContainerGenerator of the DataGrid control respectively. The ItemContainerGenerator object is responsible for generating the DataGridRow objects on behalf of the DataGrid. Each ItemsControl, such as the DataGrid control, has its own instance of an ItemContainerGenerator object which is exposed through a property with the same name:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        /* initialize a DataGrid with a number of Product objects */
        List<Product> products = new List<Product>();
        products.Add(new Product(1, "Product A"));
        products.Add(new Product(2, "Product B"));
        products.Add(new Product(3, "Product C"));
        products.Add(new Product(4, "Product D"));
        products.Add(new Product(5, "Product E"));
        dataGrid.ItemsSource = products;

        this.Loaded += MainWindow_Loaded;
    }

    private void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        /* select "Product C" */
        SelectRowByIndex(dataGrid, 2);
    }

    public static void SelectRowByIndex(DataGrid dataGrid, int rowIndex)
    {
        if (!dataGrid.SelectionUnit.Equals(DataGridSelectionUnit.FullRow))
            throw new ArgumentException("The SelectionUnit of the DataGrid must be set to FullRow.");

        if (rowIndex < 0 || rowIndex > (dataGrid.Items.Count - 1))
            throw new ArgumentException(string.Format("{0} is an invalid row index.", rowIndex));

        dataGrid.SelectedItems.Clear();
        /* set the SelectedItem property */
        object item = dataGrid.Items[rowIndex]; // = Product X
        dataGrid.SelectedItem = item;

        DataGridRow row = dataGrid.ItemContainerGenerator.ContainerFromIndex(rowIndex) as DataGridRow;
        if (row == null)
        {
            /* bring the data item (Product object) into view
             * in case it has been virtualized away */
            dataGrid.ScrollIntoView(item);
            row = dataGrid.ItemContainerGenerator.ContainerFromIndex(rowIndex) as DataGridRow;
        }
        //TODO: Retrieve and focus a DataGridCell object
    }
    ...
}
public class Product
{
    public Product(int id, string name)
    {
        this.Id = id;
        this.Name = name;
    }

    public int Id { get; private set; }
    public string Name { get; set; }
}
<Window x:Class="Mm.DataGridFocus.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <DataGrid x:Name="dataGrid" />
    </Grid>
</Window>

User interface virtualization

Note that the ContainerFromIndex method of the ItemContainerGenerator object will return a null reference if the visual DataGridRow object for the data item has been virtualized away. The DataGrid control in WPF 4.5 by default uses user interface virtualization for performance reasons. This means that the item container generation and associated layout computation for a data bound item is deferred until the item is visible and only a subset of the controls for the data bound items is displayed on the screen at the same time. As the user scrolls through the list, elements are added and removed from the visual tree.

If you for example bind the DataGrid to a collection of a hundred (100) Product objects, only the ones that are currently visible on the screen and a few more will actually be present in the visual tree. You can bring a virtualized item into view by using the ScrollIntoView method of the DataGrid control as shown in the SelectRowByIndex method in the code snippet above. After the call to this method, you will be able to get the DataGridRow object for the item that was passed to the method as a parameter.

Once you have obtained a reference to the particular DataGridRow element for the data object or row index you want to focus, the next step is to use the generic FindVisualChild method to find the System.Windows.Controls.Primitives.DataGridCellsPresenter element in the visual tree. A DataGridCellsPresenter object is used within the template of a DataGrid control to specify the location in the control’s visual tree where the cells are to be added. It has its own instance of an ItemContainerGenerator object that is responsible for generating the actual DataGridCell objects to be added to the visual tree. Remember that a DataGridCell object represents a cell of a DataGrid control in the same way as a DataGridRow represents a row.

Below is another method that returns a DataGridCell object from a specific DataGridRow that is passed as a parameter to the method. The particular cell of the row that will be returned from the method is decided by the column parameter. If you for example pass a value of 0 as the column parameter, the leftmost cell will be returned. Also note that the FindVisualChild method is used to find the DataGridCellsPresenter object.

For a virtualized row, the FindVisualChild method won’t be able to find a DataGridCellsPresenter object because it hasn’t been added to the visual tree yet. You can then call the ApplyTemplate() method of the DataGridRow to build its visual tree, causing both the DataGridCellsPresenter and consequently the DataGridCell objects to be created.

public static DataGridCell GetCell(DataGrid dataGrid, DataGridRow rowContainer, int column)
{
    if (rowContainer != null)
    {
        DataGridCellsPresenter presenter = FindVisualChild<DataGridCellsPresenter>(rowContainer);
        if (presenter == null)
        {
            /* if the row has been virtualized away, call its ApplyTemplate() method 
             * to build its visual tree in order for the DataGridCellsPresenter
             * and the DataGridCells to be created */
            rowContainer.ApplyTemplate();
            presenter = FindVisualChild<DataGridCellsPresenter>(rowContainer);
        }
        if (presenter != null)
        {
            DataGridCell cell = presenter.ItemContainerGenerator.ContainerFromIndex(column) as DataGridCell;
            if (cell == null)
            {
                /* bring the column into view
                 * in case it has been virtualized away */
                dataGrid.ScrollIntoView(rowContainer, dataGrid.Columns[column]);
                cell = presenter.ItemContainerGenerator.ContainerFromIndex(column) as DataGridCell;
            }
            return cell;
        }
    }
    return null;
}

To select and focus the row, you can then simply call this method from the SelectRowByIndex method in the sample code above and then call the UIElement.Focus() method on the returned DataGridCell element:

public static void SelectRowByIndex(DataGrid dataGrid, int rowIndex)
{
    ...
    DataGridRow row = dataGrid.ItemContainerGenerator.ContainerFromIndex(rowIndex) as DataGridRow;
    ...
    if (row != null)
    {
        DataGridCell cell = GetCell(dataGrid, row, 0);
        if(cell != null)
            cell.Focus();
    }
}

This will cause the row to get selected, highlighted and focused in the exact same way as when you are selecting it using the mouse:

datagrid

Selecting multiple rows

By making some slight modifications to the SelectRowByIndex method, or by creating another method, you could also select several rows provided that the SelectionMode property of the DataGrid control is set to DataGridSelectionMode.Extended, which it is by default. The following sample code selects both the “Product B” and “Product D” objects:

public partial class MainWindow : Window
{
    ...
    private void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        /* select "Product B" and "Product D" */
        SelectRowByIndexes(dataGrid, 1, 3);
    }

    public static void SelectRowByIndexes(DataGrid dataGrid, params int[] rowIndexes)
    {
        if (!dataGrid.SelectionUnit.Equals(DataGridSelectionUnit.FullRow))
            throw new ArgumentException("The SelectionUnit of the DataGrid must be set to FullRow.");

        if (!dataGrid.SelectionMode.Equals(DataGridSelectionMode.Extended))
            throw new ArgumentException("The SelectionMode of the DataGrid must be set to Extended.");

        if (rowIndexes.Length.Equals(0) || rowIndexes.Length > dataGrid.Items.Count)
            throw new ArgumentException("Invalid number of indexes.");

        dataGrid.SelectedItems.Clear();
        foreach (int rowIndex in rowIndexes)
        {
            if (rowIndex < 0 || rowIndex > (dataGrid.Items.Count - 1))
                throw new ArgumentException(string.Format("{0} is an invalid row index.", rowIndex));

            object item = dataGrid.Items[rowIndex]; //=Product X
            dataGrid.SelectedItems.Add(item);

            DataGridRow row = dataGrid.ItemContainerGenerator.ContainerFromIndex(rowIndex) as DataGridRow;
            if (row == null)
            {
                dataGrid.ScrollIntoView(item);
                row = dataGrid.ItemContainerGenerator.ContainerFromIndex(rowIndex) as DataGridRow;
            }
            if (row != null)
            {
                DataGridCell cell = GetCell(dataGrid, row, 0);
                if (cell != null)
                    cell.Focus();
            }
        }
    }
    ...
}

The params keyword in C# lets you specify a method parameter that takes a variable number of arguments. In the above method you can pass a variable number of indexes of rows in the DataGrid control to be selected.

Selecting a single cell

If you have set the SelectionUnit property of the DataGrid control to DataGridSelectionUnit.Cell and want to select and focus an individual cell of the DataGrid control rather than an entire row, the solution is similar to the previously described one.

The difference is that you add a System.Windows.Controls.DataGridCellInfo object to the DataGrid’s SelectedCells collection for each cell that you want to select, instead of setting its SelectedItem property or adding items to its SelectedItems collection.

A DataGridCellInfo object provides information about a cell and the data item that is associated with the cell. It is used instead of a reference to the actual DataGridCell object when the DataGrid control gets a cell, for example in the CurrentCell or SelectedCells properties. If you have a reference to a DataGridCell object and want to select this one, you can simply create a new DataGridCellInfo object that takes this DataGridCell object as a constructor argument and then add it to the SelectedCells collection of the DataGrid control:

public partial class MainWindow : Window
{
    ...
    private void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        dataGrid.SelectionUnit = DataGridSelectionUnit.Cell;
        /* select the second cell (index = 1) of the fourth row (index = 3) */
        SelectCellByIndex(dataGrid, 3, 1);
    }

    public static void SelectCellByIndex(DataGrid dataGrid, int rowIndex, int columnIndex)
    {
        if (!dataGrid.SelectionUnit.Equals(DataGridSelectionUnit.Cell))
            throw new ArgumentException("The SelectionUnit of the DataGrid must be set to Cell.");

        if (rowIndex < 0 || rowIndex > (dataGrid.Items.Count - 1))
            throw new ArgumentException(string.Format("{0} is an invalid row index.", rowIndex));

        if (columnIndex < 0 || columnIndex > (dataGrid.Columns.Count - 1))
            throw new ArgumentException(string.Format("{0} is an invalid column index.", columnIndex));

        dataGrid.SelectedCells.Clear();

        object item = dataGrid.Items[rowIndex]; //=Product X
        DataGridRow row = dataGrid.ItemContainerGenerator.ContainerFromIndex(rowIndex) as DataGridRow;
        if (row == null)
        {
            dataGrid.ScrollIntoView(item);
            row = dataGrid.ItemContainerGenerator.ContainerFromIndex(rowIndex) as DataGridRow;
        }
        if (row != null)
        {
            DataGridCell cell = GetCell(dataGrid, row, columnIndex);
            if (cell != null)
            {
                DataGridCellInfo dataGridCellInfo = new DataGridCellInfo(cell);
                dataGrid.SelectedCells.Add(dataGridCellInfo);
                cell.Focus();
            }
        }
    }
    ...
}

datagridcell

Selecting multiple cells

To be able to select more than one cell, you could use a variation of the SelectCellByIndex method above that takes a list of row and cell combinations as a parameter in the form of a System.Collections.Generic.IList collection with System.Collections.Generic.KeyValuePair objects where the Key property of the KeyValuePair specifies the row index and the Value property specifies the column index:

public static void SelectCellsByIndexes(DataGrid dataGrid, IList<KeyValuePair<int,int>> cellIndexes)
{
    if (!dataGrid.SelectionUnit.Equals(DataGridSelectionUnit.Cell))
        throw new ArgumentException("The SelectionUnit of the DataGrid must be set to Cell.");

    if (!dataGrid.SelectionMode.Equals(DataGridSelectionMode.Extended))
        throw new ArgumentException("The SelectionMode of the DataGrid must be set to Extended.");

    dataGrid.SelectedCells.Clear();
    foreach (KeyValuePair<int, int> cellIndex in cellIndexes)
    {
        int rowIndex = cellIndex.Key;
        int columnIndex = cellIndex.Value;

        if (rowIndex < 0 || rowIndex > (dataGrid.Items.Count - 1))
            throw new ArgumentException(string.Format("{0} is an invalid row index.", rowIndex));

        if (columnIndex < 0 || columnIndex > (dataGrid.Columns.Count - 1))
            throw new ArgumentException(string.Format("{0} is an invalid column index.", columnIndex));

        object item = dataGrid.Items[rowIndex]; //= Product X
        DataGridRow row = dataGrid.ItemContainerGenerator.ContainerFromIndex(rowIndex) as DataGridRow;
        if (row == null)
        {
            dataGrid.ScrollIntoView(item);
            row = dataGrid.ItemContainerGenerator.ContainerFromIndex(rowIndex) as DataGridRow;
        }
        if (row != null)
        {
            DataGridCell cell = GetCell(dataGrid, row, columnIndex);
            if (cell != null)
            {
        	DataGridCellInfo dataGridCellInfo = new DataGridCellInfo(cell);
        	dataGrid.SelectedCells.Add(dataGridCellInfo);
        	cell.Focus();
            }
        }
    }
}

private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
    dataGrid.SelectionUnit = DataGridSelectionUnit.Cell;

    List<KeyValuePair<int, int>> cellIndexes = new List<KeyValuePair<int, int>>();
    /* select the first cell of the first row */
    cellIndexes.Add(new KeyValuePair<int, int>(0, 0));
    /* select the second cell of the second row */
    cellIndexes.Add(new KeyValuePair<int, int>(1, 1));
    /* select the first cell of the third row */
    cellIndexes.Add(new KeyValuePair<int, int>(2, 0));
    /* select the second cell of the fourth row */
    cellIndexes.Add(new KeyValuePair<int, int>(3, 1));
    /* select the first cell of the fifth row */
    cellIndexes.Add(new KeyValuePair<int, int>(4, 0));
    SelectCellsByIndexes(dataGrid, cellIndexes);
}

datagridcells


6 Comments on “How to programmatically select and focus a row or cell in a DataGrid in WPF”

  1. Mark says:

    “Selecting Single Cell” section blows up on line: dataGrid.ScrollIntoView(item); with the following error: “The current value of the SelectionUnit property on the parent DataGrid prevents rows from being selected.”

  2. Marschal says:

    You have to set the datagrid editable by user. Set IsReadOnly rpoperty to true. And don’t forget to disable ColumnVirtualization.

  3. Chirag Bhagat says:

    how can you do this using WPF MVVM?

  4. Giacomo Galletto says:

    Hi,

    it’s a pretty old discussion but I found it very useful, thanks!
    Anyway, I am encountering a strange behaviour and I just can’t get why it is happening.
    This is my scenario: I have two datagrid, both data bounded, with the second one bounded to the selected item of the first one.
    I am trying to:

    – automatically select the next item in the first datagrid when the user hits Enter key when the last row in the second datagrid is selected

    – automatically focus a specific cell (index = 4) of the second datagrid.

    I used your solution (tranlsated into vb.net) and everything is going fine except for the fact that when the second datagrid has more than one row, always the second row is selected. And this happens even if, debugging, I verify that the datagridrow got from the instruction:

    Dim row As DataGridRow = dgContenitoriSbarchi.ItemContainerGenerator.ContainerFromIndex(0)

    is correctly the first row in the second datagrid after data bounding is completed

    Here the piece of code I use (“dgPolizzeSbarchi” is the first datagrid and “dgContenitoriSbarchi” is the second one):

    If e.Key = Key.Enter Then ‘ Enter su ultima riga (per passaggio a polizza successiva)

    If Not _editing Then

    Dim rowIndex As Integer = dgContenitoriSbarchi.SelectedIndex
    Dim lastRowIndex As Integer = dgContenitoriSbarchi.Items.Count – 1

    If rowIndex = lastRowIndex Then

    If dgPolizzeSbarchi.SelectedIndex < dgPolizzeSbarchi.Items.Count – 1 Then

    dgPolizzeSbarchi.SelectedIndex += 1

    Else
    dgPolizzeSbarchi.SelectedIndex = 0
    End If

    dgPolizzeSbarchi.ScrollIntoView(dgPolizzeSbarchi.SelectedItem)

    dgContenitoriSbarchi.ScrollIntoView(dgContenitoriSbarchi.Items(0))

    'dgContenitoriSbarchi.Focus()
    'dgContenitoriSbarchi.BeginEdit()

    Dim row As DataGridRow = dgContenitoriSbarchi.ItemContainerGenerator.ContainerFromIndex(0)

    If row IsNot Nothing Then

    Dim cell As DataGridCell = GetCell(dgContenitoriSbarchi, row, 4)
    If cell IsNot Nothing Then cell.Focus()

    'dgContenitoriSbarchi.SelectedItem = dgContenitoriSbarchi.Items(0)

    End If
    End If
    End If
    End If

    Any ideas?

    Cheers
    Giacomo

  5. Dan says:

    I think this would not work if your using datatemplates to define columns in the datagrid.

    Also, a more useful example would be determining the current cell to highlight it when focused.

  6. Vova Sipen says:

    Magnus, I enjoyed the post, but could not find one thing I was looking for. Perhaps you might be able to help. I am struggling with differentiating 2 cases in DataGrid: 1. when a row is being explicitly selected/highlighted using mouse and 2. when a row is being selected by default. In both cases all related properties in DataGrid are the same as far as I could tell. Is there a way to programmatically identify which case is being dealt with?


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s