5
votes

.Net 4 WPF DataGrid MVVM

User clicks add button which triggers command on viewmodel. In the viewmodel command execute, I add a new object to the viewcollection of the viewmodel that the grid is bound to. The new row does appear in my grid. However, I also want to send the focus to the first editable cell in that new row.

I even "cheated" mvvm, added an event on my viewmodel that the view listens to, to know when to focus the new row.

I've searched but no luck. I was hopeful when I came across this:

Datagrid Set focus on newly added row

which leads to

http://social.msdn.microsoft.com/forums/en-US/wpf/thread/63974f4f-d9ee-45af-8499-42f29cbc22ae

But the problem that others have reported and no one has answered is how to deal with the virtualizing behaviour of the grid. The newly added row has not yet been created. So that GetCells call fails frequently. And if ScrollIntoView is required, then it's that much more likely to fail.

I've hooked a ton of events including LoadingRow and RequestBringIntoView with no luck. Depending on which event I hook, I have managed to be able to get a reference to the cell. But then I get an error "Cannot call StartAt when content generation is in progress". But I checked the status of the ItemContainerGenerator and it was ContainersGenerated when I made the call to the cell's BeginEdit.

3

3 Answers

1
votes

Here is one way to set focus to a particular cell programmatically:

DataGridCell cell = GetCell(rowIndex, colIndex);
cell.Focus;

Please see the following article for more information on GetCell().

0
votes

This seemed to work for me:

    using System.Windows.Controls;
    using System.Windows.Controls.Primitives;
    using System.Windows.Media;

    private void SetFocusOnNewRow(DataGrid theDataGrid, Int32 columnIndex)
    {
        theDataGrid.UnselectAll();
        theDataGrid.UpdateLayout();

        Int32 newRowIndex = theDataGrid.Items.Count - 1;
        theDataGrid.ScrollIntoView(theDataGrid.Items[newRowIndex]);
        DataGridRow newDataGridRow = theDataGrid.ItemContainerGenerator.ContainerFromIndex(newRowIndex) as DataGridRow;

        DataGridCellsPresenter newDataGridCellsPresenter = GetVisualChild<DataGridCellsPresenter>(newDataGridRow);
        if (newDataGridCellsPresenter != null)
        {
            DataGridCell newDataGridCell = newDataGridCellsPresenter.ItemContainerGenerator.ContainerFromIndex(columnIndex) as DataGridCell;
            if (newDataGridCell != null)
                newDataGridCell.Focus();
        }
    }

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

This is work for me:

private void button_Click(object sender, RoutedEventArgs e)
{
    //Scroll to the last row
    var border = VisualTreeHelper.GetChild(dataGrid, 0) as Decorator;
    if (border != null)
    {
        var scroll = border.Child as ScrollViewer;
        if (scroll != null) scroll.ScrollToEnd();
    }

    //Edit the first cell of the last row
    int lastRow = dataGrid.Items.Count - 1;
    DataGridCell cell = GetCell(lastRow, 0);
    cell.Focus();
    dataGrid.BeginEdit();
}


public DataGridCell GetCell(int row, int column)
{
    DataGridRow rowContainer = GetRow(row);

    if (rowContainer != null)
    {
        DataGridCellsPresenter presenter = GetVisualChild<DataGridCellsPresenter>(rowContainer);

        // try to get the cell but it may possibly be virtualized
        DataGridCell cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(column);
        if (cell == null)
        {
            // now try to bring into view and retreive the cell
            dataGrid.ScrollIntoView(rowContainer, dataGrid.Columns[column]);
            cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(column);
        }
        return cell;
    }
    return null;
}

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

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