开发者

WPF datagrid and the tab key

Another datagrid keybindings question

I have a datagrid. It has selection mode set to FullRow and KeyboardNavigation.TabNavigation="Once" which I was hoping would get my desired result but it doesn't.

When the tab key is pressed when the datagrid has focus it will tab over each column in the grid one by one. So if I tab into the grid which has 4 columns, I will have to press tab 4 times to go to the next tabindex.

What I want is for the tab key to tab right out of the datagrid on first press and give focus to the next tabindex... if that makes sense.

I have tried overriding the tab key in the keydown event handler like so.

class BetterDataGrid : DataGrid
{
  ..............
  protected override void OnKeyDown(System.Windows.Input.KeyEventArgs e)
  {
    ..............
    if (e.Key == Key.Tab)
    {
        Console.WriteLine("TAB");
        MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
    }
    .........
  }

It does write "TAB" to the console but the tab still keeps it's default behavior. Not sure if this is the right way to go the next tabindex, but then this should make the tab key do nothing but write to the console or cause an exception.

Makes me think it's impossible to override the tab key behavior.

Hoping for some helpful input.

开发者_开发技巧 As always, thanks in advance.


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

The code for the handlers are as follows (DataGrid 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 this code you can navigate inside the grid using the cursor keys, and the tab key and shift-tab key gets you out of the datagrid. If you tab out of the grid and come back to the grid, you also get to the same cell that you left. This is what my users and I want, and this is IMHO what the DataGrid control should provide as default behaviour.


I was also looking for this behaviour. While the solution proposed by Guge was a good start, I did not like how it saves the previously stored element nor it's overall complexity. Worst of all, I simply couldn't get it to consistently work as expected no matter how much I tweaked it. Eventually, I decided to write my own solution from scratch. By thinking outside the box (literally) I've come up with a different, simpler solution.

In the XAML file, create an empty control before and after your DataGrid like so:

<DockPanel>
  <Control IsTabStop="False" x:Name="PreControl" />
  <DataGrid PreviewKeyDown="DataGrid_PreviewKeyDown">...</DataGrid>
  <Control IsTabStop="False" x:Name="PostControl" />
</DockPanel>

Then in the code-behind, add a function for the DataGrid's PreviewKeyDown event like so:

private void DataGrid_PreviewKeyDown(object sender, KeyEventArgs e)
{
    if (Keyboard.Modifiers == ModifierKeys.Shift && e.Key == Key.Tab)
    {
        PreControl.MoveFocus(new TraversalRequest(FocusNavigationDirection.Previous));
        e.Handled = true;
    }
    else if (Keyboard.Modifiers == ModifierKeys.None && e.Key == Key.Tab)
    {
        PostControl.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
        e.Handled = true;
    }
    else if (new[] { Key.Up, Key.Down, Key.Left, Key.Right }.Contains(e.Key))
    {
        var grid = (DataGrid)sender;
        grid.CurrentCell = new DataGridCellInfo(grid.SelectedItem, grid.CurrentColumn);
    }
}

The first if and else-if override the default tab behavior by tabbing from the empty controls instead of from the datagrid. The next else-if statement updates the current cell before moving with the arrow keys. Sometimes the current cell becomes out of sync with the selected cell when switching focus in and out of the grid. This is an issue with the previously proposed solution as well as this one and I have not found a way to fix it, but by doing this, I can make sure that when navigating using the arrow keys, it navigates relative to the selected cell, rather than the current cell.

Some caveats to this approach:

  • It won't work well when allowing multiple rows to be selected
  • As previously mentioned, sometimes the current cell can be different than the selected cell. This can cause visual issues but does not affect the selected item or navigation.
  • I have only thoroughly tested this with a data grid that has full row selection. The selected column may not be properly preserved or may have navigation issues.


What you are trying to achive is not the right behavior, everyone expect Excel like navigation when pressing the Tab-Key while the DataGrid is focused. It's better to prevent tab stops on the DataGrid by setting IsTabStop="False" on the DataGrid if you don't want the user to navigate through the DataGrid.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜