[ACCEPTED]-WPF datagrid and the tab key-key-bindings

Accepted answer
Score: 11

I wanted this for my line-of-business software, and 17 the only way I have found to solve it is 16 by codebehind, using the PreviewKeyDown, GotKeyboardFocus 15 and LostKeyboardFocus events of the datagrid. I 14 have put these eventhandlers in a WPF decorator, to 13 avoid repeating it for every single DataGrid. It 12 would probably be possible to subclass the 11 DataGrid, but I haven't tried that.

The code 10 for the handlers are as follows (DataGrid 9 is x:Name="grid" for this sample code):

        private IInputElement lastDataGridFocus = null;
    private int selectedcolumnindex = 0;

    void grid_GotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
    {
        if (grid.Items.Count > 0 && (e.NewFocus is DataGrid || (e.NewFocus is DataGridCell && !(e.OldFocus is DataGridCell))))
        {
            DataGridCell cell = null;

            if (lastDataGridFocus != null)
            {
                FocusManager.SetFocusedElement(grid, lastDataGridFocus);
                lastDataGridFocus = null;
                e.Handled = true;
                return;
            }

            if (grid.SelectedCells.Count == 0)
            {
                DataGridRow rowContainer = (DataGridRow)grid.ItemContainerGenerator.ContainerFromIndex(0);
                if (rowContainer != null)
                {
                    DataGridCellsPresenter presenter = GetVisualChild<DataGridCellsPresenter>(rowContainer);
                    cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex((selectedcolumnindex < 0) ? 0 : selectedcolumnindex);
                }
            }
            else
            {
                DataGridCellInfo selectedDataGridCellInfo = (grid.SelectedCells[0] as DataGridCellInfo?).Value;
                DataGridRow rowContainer = (DataGridRow)grid.ItemContainerGenerator.ContainerFromItem(selectedDataGridCellInfo.Item);
                if (rowContainer != null)
                {
                    DataGridCellsPresenter presenter = GetVisualChild<DataGridCellsPresenter>(rowContainer);
                    cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex((selectedcolumnindex < 0) ? 0 : selectedcolumnindex);
                }
            }
            if (null != cell)
            {
                FocusManager.SetFocusedElement(grid, cell as IInputElement);
                e.Handled = true;
            }
        }
    }

    void grid_LostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
    {
        if (!(e.NewFocus is DataGridCell))
        {
            if (grid.CurrentCell != null)
            {
                selectedcolumnindex = grid.Columns.IndexOf(grid.CurrentCell.Column);
            }
        }
    }

    void grid_PreviewKeyDown(object sender, KeyEventArgs e)
    {
        if (Keyboard.Modifiers == ModifierKeys.Shift && e.Key == Key.Tab)
        {
            lastDataGridFocus = Keyboard.FocusedElement;
            grid.MoveFocus(new TraversalRequest(FocusNavigationDirection.Previous));
            e.Handled = true;
        }
        else if (Keyboard.Modifiers == ModifierKeys.None && e.Key == Key.Tab)
        {
            lastDataGridFocus = Keyboard.FocusedElement;
            grid.MoveFocus(new TraversalRequest(FocusNavigationDirection.Last));
            (Keyboard.FocusedElement as FrameworkElement).MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
            e.Handled = true;
        }
    }

With 8 this code you can navigate inside the grid 7 using the cursor keys, and the tab key and 6 shift-tab key gets you out of the datagrid. If 5 you tab out of the grid and come back to 4 the grid, you also get to the same cell 3 that you left. This is what my users and 2 I want, and this is IMHO what the DataGrid 1 control should provide as default behaviour.

More Related questions