Сетка данных WPF и клавиша табуляции

#c# #wpf #datagrid #key-bindings

#c# #wpf #сетка данных #привязки клавиш

Вопрос:

Еще один вопрос о привязках клавиш datagrid

У меня есть datagrid. Для режима выбора установлено значение FullRow и KeyboardNavigation.TabNavigation=»Once», который, как я надеялся, даст желаемый результат, но этого не происходит.

При нажатии клавиши tab, когда сетка данных находится в фокусе, она будет табулировать каждый столбец в таблице один за другим. Итак, если я перейду в таблицу с 4 столбцами, мне придется нажать tab 4 раза, чтобы перейти к следующему tabindex.

Я хочу, чтобы клавиша tab выводила вкладку прямо из datagrid при первом нажатии и фокусировалась на следующем tabindex … если это имеет смысл.

Я попытался переопределить клавишу tab в обработчике событий keydown следующим образом.

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

Он записывает «TAB» в консоль, но вкладка по-прежнему сохраняет поведение по умолчанию. Не уверен, что это правильный путь для перехода к следующему tabindex, но тогда это должно заставить клавишу tab ничего не делать, кроме записи в консоль или вызвать исключение.
Заставляет меня думать, что невозможно переопределить поведение клавиши табуляции.

Надеюсь на какой-нибудь полезный ввод.
Как всегда, заранее благодарю.

Ответ №1:

Я хотел это для своего программного обеспечения для бизнеса, и единственный способ, который я нашел для решения этой проблемы, — это с помощью codebehind, используя события PreviewKeyDown, GotKeyboardFocus и LostKeyboardFocus сетки данных. Я поместил эти обработчики событий в декоратор WPF, чтобы избежать повторения этого для каждой отдельной DataGrid. Вероятно, можно было бы создать подкласс DataGrid, но я этого не пробовал.

Код для обработчиков выглядит следующим образом (DataGrid — это x:Name=»сетка» для этого примера кода):

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

    void grid_GotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
    {
        if (grid.Items.Count > 0 amp;amp; (e.NewFocus is DataGrid || (e.NewFocus is DataGridCell amp;amp; !(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 amp;amp; e.Key == Key.Tab)
        {
            lastDataGridFocus = Keyboard.FocusedElement;
            grid.MoveFocus(new TraversalRequest(FocusNavigationDirection.Previous));
            e.Handled = true;
        }
        else if (Keyboard.Modifiers == ModifierKeys.None amp;amp; 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;
        }
    }
  

С помощью этого кода вы можете перемещаться внутри сетки с помощью клавиш курсора, а клавиши tab и shift-tab выводят вас из сетки данных. Если вы выводите табуляцию из сетки и возвращаетесь к сетке, вы также попадаете в ту же ячейку, из которой вышли. Это то, чего хотим мои пользователи и я, и это, ИМХО, то, что элемент управления DataGrid должен предоставлять в качестве поведения по умолчанию.

Комментарии:

1. Извините за позднее принятие, у меня не было возможности протестировать это, но это хорошо. Спасибо

2. Использование FocusManager.SetFocusedElement вызывает две проблемы: 1. он повторно отправит ‘ t’ в качестве входных данных в выбранную ячейку 2. иногда не удается установить фокус клавиатуры и / или выбрать следующую ячейку. Использование Dispatcher.BeginInvoke(new Action(() => cell.Focus())); должно устранить обе проблемы.

Ответ №2:

Я также искал такое поведение. Хотя решение, предложенное Guge, было хорошим началом, мне не понравилось ни то, как оно сохраняет ранее сохраненный элемент, ни его общая сложность. Хуже всего то, что я просто не мог заставить его стабильно работать должным образом, независимо от того, насколько сильно я его настраивал. В конце концов, я решил написать свое собственное решение с нуля. Нестандартным мышлением (буквально) Я придумал другое, более простое решение.

В файле XAML создайте пустой элемент управления до и после вашей DataGrid следующим образом:

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

Затем в коде, лежащем в основе, добавьте функцию для события PreviewKeyDown сетки данных, например:

 private void DataGrid_PreviewKeyDown(object sender, KeyEventArgs e)
{
    if (Keyboard.Modifiers == ModifierKeys.Shift amp;amp; e.Key == Key.Tab)
    {
        PreControl.MoveFocus(new TraversalRequest(FocusNavigationDirection.Previous));
        e.Handled = true;
    }
    else if (Keyboard.Modifiers == ModifierKeys.None amp;amp; 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);
    }
}
  

Первое if и else-if переопределяют поведение табуляции по умолчанию, вводя табуляцию из пустых элементов управления, а не из datagrid. Следующий оператор else-if обновляет текущую ячейку перед перемещением с помощью клавиш со стрелками. Иногда текущая ячейка не синхронизируется с выбранной ячейкой при переключении фокуса в сетке и за ее пределами. Это проблема с ранее предложенным решением, а также с этим, и я не нашел способа ее исправить, но, делая это, я могу убедиться, что при навигации с помощью клавиш со стрелками она перемещается относительно выбранной ячейки, а не текущей ячейки.

Некоторые оговорки к этому подходу:

  • Это не будет хорошо работать, если разрешить выбор нескольких строк
  • Как упоминалось ранее, иногда текущая ячейка может отличаться от выбранной ячейки. Это может вызвать визуальные проблемы, но не влияет на выбранный элемент или навигацию.
  • Я тщательно протестировал это только с сеткой данных, в которой есть полный выбор строк. Возможно, выбранный столбец не сохранен должным образом или имеет проблемы с навигацией.

Ответ №3:

То, чего вы пытаетесь достичь, — это неправильное поведение, все ожидают навигации, подобной Excel, при нажатии клавиши Tab, когда сетка данных сфокусирована. Лучше запретить остановку табуляции в DataGrid, установив IsTabStop="False" в DataGrid, если вы не хотите, чтобы пользователь перемещался по DataGrid.

Комментарии:

1. Почему за голосование «против»?! это просто мое мнение с точки зрения UX!!

2. Да, я согласен с вами, мистер Фадил, лучше установить IsTabStop=»False»

3. Извините за отрицательный ответ, но ваш ответ на самом деле совсем не помог. IsTabStop= false удаляет табуляцию в сетке, которую я хотел бы сохранить, и не приводит к желаемому поведению, когда оно сфокусировано. Конечно, во многих случаях навигация в стиле Excel работает хорошо, но в данном случае она просто избыточна, сетка доступна только для чтения и имеет выбор в виде полной строки.

4. Если это доступно только для чтения DataGrid , я изо всех сил пытаюсь понять, какой смысл имеет остановка табуляции…

5. @Hohinhime. Вы вводите вкладку в DataGrid, затем перемещаетесь внутри DataGrid с помощью клавиш со стрелками, а когда вы хотите выйти из DataGrid, вы снова используете клавишу tab. Я думаю, что это лучший способ. И важно, чтобы при выводе табуляции из DataGrid и возвращении в нее вы возвращались в ту же ячейку, в которой находились в прошлый раз. Эта ячейка будет иметь логический фокус для DataGrid. Если у вас есть DataGrid с парой сотен ячеек, вы бы не хотели нажимать tab один раз для каждой ячейки, чтобы покинуть DataGrid.