WPF 数据网格和 Tab 键

发布于 2024-10-31 23:14:46 字数 821 浏览 4 评论 0原文

另一个数据网格键绑定问题

我有一个数据网格。它的选择模式设置为 FullRow 和 KeyboardNavigation.TabNavigation="Once" 我希望能得到我想要的结果,但事实并非如此。

当数据网格获得焦点时按下 Tab 键,它将一一地遍历网格中的每一列。因此,如果我按 Tab 键进入有 4 列的网格,我将必须按 Tab 键 4 次才能转到下一个 tabindex。

我想要的是让 tab 键在第一次按下时直接从数据网格中跳出,并将焦点集中到下一个 tabindex...如果这有意义的话。

我尝试像这样覆盖 keydown 事件处理程序中的 tab 键。

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 键除了写入控制台或引发异常之外什么也不做。
让我觉得不可能覆盖 Tab 键的行为。

希望得到一些有用的意见。
一如既往,提前致谢。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

于我来说 2024-11-07 23:14:46

我希望我的业务线软件能够做到这一点,而我发现解决这个问题的唯一方法是通过代码隐藏,使用数据网格的 PreviewKeyDown、GotKeyboardFocus 和 LostKeyboardFocus 事件。我已将这些事件处理程序放入 WPF 装饰器中,以避免对每个 DataGrid 重复它。可能可以对 DataGrid 进行子类化,但我还没有尝试过。

处理程序的代码如下(对于此示例代码,DataGrid 为 x:Name="grid"):

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

使用此代码,您可以使用光标键在网格内导航,然后使用 Tab 键和 Shift-Tab 键即可退出数据网格的。如果您按 Tab 键退出网格并返回到网格,您也会到达您离开的同一单元格。这就是我和我的用户想要的,恕我直言,这就是 DataGrid 控件应该提供的默认行为。

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.

唐婉 2024-11-07 23:14:46

我也在寻找这种行为。虽然 Guge 提出的解决方案是一个好的开始,但我不喜欢它如何保存以前存储的元素,也不喜欢它的整体复杂性。最糟糕的是,无论我如何调整它,我都无法让它按照预期持续工作。最终,我决定从头开始编写自己的解决方案。通过跳出框框(字面意思)思考,我想出了一个不同的、更简单的解决方案。

在 XAML 文件中,在 DataGrid 之前和之后创建一个空控件,如下所示:

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

然后在代码隐藏中,为 DataGrid 的 PreviewKeyDown 事件添加一个函数,如下所示:

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

第一个 if 和 else-if 通过从 Tab 键切换来覆盖默认选项卡行为空控件而不是来自数据网格。下一个 else-if 语句在使用箭头键移动之前更新当前单元格。有时,当焦点移入和移出网格时,当前单元格会与所选单元格不同步。这是之前提出的解决方案以及这个解决方案的一个问题,我还没有找到解决它的方法,但是通过这样做,我可以确保在使用箭头键导航时,它相对于所选单元格进行导航,而不是当前单元格。

此方法的一些注意事项:

  • 当允许选择多行时,它不会很好地工作
  • 如前所述,有时当前单元格可能与所选单元格不同。这可能会导致视觉问题,但不会影响所选项目或导航。
  • 我仅使用具有完整行选择的数据网格对此进行了彻底测试。所选列可能无法正确保留或可能存在导航问题。

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.
新人笑 2024-11-07 23:14:46

这可能会对某人有所帮助...我想使用 Tab 键移动到下一行,但也选择或传递包含两个按钮的自定义单元格。解决方案是扩展 DataGridCellStyle 并将每个列/单元格的 Focusable 设置为 False。这是我的代码:

<DataGrid Grid.Row="1" Name="TutorialsDataGrid" AutoGenerateColumns="False" CanUserAddRows="False" IsReadOnly="True"
        EnableRowVirtualization="True" SelectionUnit="FullRow">
<DataGrid.Columns>
    <DataGridTextColumn Header="Title" Width="0.5*" MinWidth="380" Binding="{Binding Title}">
        <DataGridTextColumn.CellStyle>
            <Style TargetType="DataGridCell" BasedOn="{StaticResource MetroDataGridCell}">
                <Setter Property="Margin" Value="5,0"/>
                <!-- SOLUTION -->
                <Setter Property="Focusable" Value="False"/>
            </Style>
        </DataGridTextColumn.CellStyle>
    </DataGridTextColumn>
    <DataGridTemplateColumn Header="Format" Width="*" MinWidth="200">
        <DataGridTemplateColumn.CellStyle>
            <Style TargetType="DataGridCell" BasedOn="{StaticResource MetroDataGridCell}">
                <Setter Property="Margin" Value="5,0"/>
                <!-- SOLUTION -->
                <Setter Property="Focusable" Value="False"/>
            </Style>
        </DataGridTemplateColumn.CellStyle>
        <DataGridTemplateColumn.CellTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal" >
                    <Button Name="OpenVideo" ToolTip="Open Video" Margin="5,3" Width="80" Height="28" Tag="{Binding VideoPath}" Click="OpenVideo_Click">
                        <Path Height="16" Stretch="Uniform" Data="{StaticResource UniconsCirclePlay}" Fill="{Binding Foreground, RelativeSource={RelativeSource AncestorType=Button}}"/>
                    </Button>
                    <Button Name="OpenPDF" ToolTip="Open PDF" Margin="5,3" Width="80" Height="28" Tag="{Binding PdfPath}" Click="OpenPDF_Click">
                        <Path Height="16" Stretch="Uniform" Data="{StaticResource FilePdf}" Fill="{Binding Foreground, RelativeSource={RelativeSource AncestorType=Button}}"/>
                    </Button>
                </StackPanel>
            </DataTemplate>
        </DataGridTemplateColumn.CellTemplate>
    </DataGridTemplateColumn>
</DataGrid.Columns>

This might help someone... I wanted to move to the next row using the Tab key, but also select or pass through my custom cell which contains two buttons. The solution was to extend the DataGrid column CellStyle and set Focusable to False for each column/cell. Here is my code:

<DataGrid Grid.Row="1" Name="TutorialsDataGrid" AutoGenerateColumns="False" CanUserAddRows="False" IsReadOnly="True"
        EnableRowVirtualization="True" SelectionUnit="FullRow">
<DataGrid.Columns>
    <DataGridTextColumn Header="Title" Width="0.5*" MinWidth="380" Binding="{Binding Title}">
        <DataGridTextColumn.CellStyle>
            <Style TargetType="DataGridCell" BasedOn="{StaticResource MetroDataGridCell}">
                <Setter Property="Margin" Value="5,0"/>
                <!-- SOLUTION -->
                <Setter Property="Focusable" Value="False"/>
            </Style>
        </DataGridTextColumn.CellStyle>
    </DataGridTextColumn>
    <DataGridTemplateColumn Header="Format" Width="*" MinWidth="200">
        <DataGridTemplateColumn.CellStyle>
            <Style TargetType="DataGridCell" BasedOn="{StaticResource MetroDataGridCell}">
                <Setter Property="Margin" Value="5,0"/>
                <!-- SOLUTION -->
                <Setter Property="Focusable" Value="False"/>
            </Style>
        </DataGridTemplateColumn.CellStyle>
        <DataGridTemplateColumn.CellTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal" >
                    <Button Name="OpenVideo" ToolTip="Open Video" Margin="5,3" Width="80" Height="28" Tag="{Binding VideoPath}" Click="OpenVideo_Click">
                        <Path Height="16" Stretch="Uniform" Data="{StaticResource UniconsCirclePlay}" Fill="{Binding Foreground, RelativeSource={RelativeSource AncestorType=Button}}"/>
                    </Button>
                    <Button Name="OpenPDF" ToolTip="Open PDF" Margin="5,3" Width="80" Height="28" Tag="{Binding PdfPath}" Click="OpenPDF_Click">
                        <Path Height="16" Stretch="Uniform" Data="{StaticResource FilePdf}" Fill="{Binding Foreground, RelativeSource={RelativeSource AncestorType=Button}}"/>
                    </Button>
                </StackPanel>
            </DataTemplate>
        </DataGridTemplateColumn.CellTemplate>
    </DataGridTemplateColumn>
</DataGrid.Columns>
墟烟 2024-11-07 23:14:46

这个困扰我很久的问题终于得到了最好的答案。
在某些时候,我确认在 DataGrid 中按 Ctrl+Tab 会产生所需的行为。
由此,我认为我可以交换 DataGrid 中 KeyboardNavigation.TabNavigation 附加属性和 KeyboardNavigation.ControlTabNavigation 附加属性的值。
然而,这并没有奏效。
在使用 ReferenceSource 检查 DataGrid 子元素的代码后,我进一步认为应该交换 DataGridCellsPanel 中 KeyboardNavigation.TabNavigation 附加属性和 KeyboardNavigation.ControlTabNavigation 附加属性的值。
我运行它并得到了理想的行为。
更重要的是,这很棒,重新进入焦点后的当前单元格就是失去焦点时的当前单元格。

<DataGrid KeyboardNavigation.TabNavigation="Once"
          KeyboardNavigation.ControlTabNavigation="Continue">
    <DataGrid.RowStyle>
        <Style TargetType="{x:Type DataGridRow}" BasedOn="{StaticResource {x:Type DataGridRow}}">
            <Setter Property="ItemsPanel">
                <Setter.Value>
                    <ItemsPanelTemplate>
                        <DataGridCellsPanel KeyboardNavigation.TabNavigation="Continue"
                                            KeyboardNavigation.ControlTabNavigation="Local"/>
                    </ItemsPanelTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </DataGrid.RowStyle>
</DataGrid>

也可以通过以下附加属性来实现,参考WinForms中DataGridView的StandardTab属性。

对于 VB.net

Public Class DataGridHelper

    Public Shared Function GetStandardTab(element As DataGrid) As Boolean
        If element Is Nothing Then Throw New ArgumentNullException(NameOf(element))
        Return CBool(element.GetValue(StandardTabProperty))
    End Function

    Public Shared Sub SetStandardTab(element As DataGrid, value As Boolean)
        If element Is Nothing Then Throw New ArgumentNullException(NameOf(element))
        element.SetValue(StandardTabProperty, value)
    End Sub

    Public Shared ReadOnly StandardTabProperty As DependencyProperty =
                           DependencyProperty.RegisterAttached("StandardTab",
                           GetType(Boolean), GetType(DataGridHelper),
                           New FrameworkPropertyMetadata(False, FrameworkPropertyMetadataOptions.Inherits, New PropertyChangedCallback(AddressOf OnStandardTabPropertyChanged)))

    Private Shared Sub OnStandardTabPropertyChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
        If TypeOf d Is DataGrid OrElse TypeOf d Is DataGridCellsPanel Then
            If CBool(e.NewValue) Then
                If TypeOf d Is DataGridCellsPanel Then
                    d.SetValue(KeyboardNavigation.TabNavigationProperty, KeyboardNavigationMode.Continue)
                    d.SetValue(KeyboardNavigation.ControlTabNavigationProperty, KeyboardNavigationMode.Local)
                Else
                    d.SetValue(KeyboardNavigation.TabNavigationProperty, KeyboardNavigationMode.Once)
                    d.SetValue(KeyboardNavigation.ControlTabNavigationProperty, KeyboardNavigationMode.Continue)
                End If
            Else
                d.ClearValue(KeyboardNavigation.TabNavigationProperty)
                d.ClearValue(KeyboardNavigation.ControlTabNavigationProperty)
            End If
        End If
    End Sub

End Class

对于 C#(未测试)

public class DataGridHelper
{
    public static bool GetStandardTab(DataGrid element)
    {
        if (element == null)
        {
            throw new ArgumentNullException(nameof(element));
        }
        return (bool)element.GetValue(StandardTabProperty);
    }

    public static void SetStandardTab(DataGrid element, bool value)
    {
        if (element == null)
        {
            throw new ArgumentNullException(nameof(element));
        }
        element.SetValue(StandardTabProperty, value);
    }

    public static readonly DependencyProperty StandardTabProperty = 
                                              DependencyProperty.RegisterAttached("StandardTab", 
                                              typeof(bool), typeof(DataGridHelper), 
                                              new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.Inherits, new PropertyChangedCallback(OnStandardTabPropertyChanged)));

    private static void OnStandardTabPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is DataGrid || d is DataGridCellsPanel)
        {
            if ((bool) e.NewValue)
            {
                if (d is DataGridCellsPanel)
                {
                    d.SetValue(KeyboardNavigation.TabNavigationProperty, KeyboardNavigationMode.Continue);
                    d.SetValue(KeyboardNavigation.ControlTabNavigationProperty, KeyboardNavigationMode.Local);
                }
                else
                {
                    d.SetValue(KeyboardNavigation.TabNavigationProperty, KeyboardNavigationMode.Once);
                    d.SetValue(KeyboardNavigation.ControlTabNavigationProperty, KeyboardNavigationMode.Continue);
                }
            }
            else
            {
                d.ClearValue(KeyboardNavigation.TabNavigationProperty);
                d.ClearValue(KeyboardNavigation.ControlTabNavigationProperty);
            }
        }
    }
}

I finally got the best answer to this problem that I have been struggling with for a long time.
At some point, I confirmed that pressing Ctrl+Tab in the DataGrid would result in the desired behavior.
From this, I thought I could just swap the values of KeyboardNavigation.TabNavigation attached property and KeyboardNavigation.ControlTabNavigation attached property in the DataGrid.
However, it did not work.
After checking the code of the DataGrid's child elements with ReferenceSource, further, I thought that I should swap the values of KeyboardNavigation.TabNavigation attached property and KeyboardNavigation.ControlTabNavigation attached property in DataGridCellsPanel.
I ran it and got the ideal behavior.
What's more, and this is great, the current cell after focus is reentered is the current cell when the focus is lost.

<DataGrid KeyboardNavigation.TabNavigation="Once"
          KeyboardNavigation.ControlTabNavigation="Continue">
    <DataGrid.RowStyle>
        <Style TargetType="{x:Type DataGridRow}" BasedOn="{StaticResource {x:Type DataGridRow}}">
            <Setter Property="ItemsPanel">
                <Setter.Value>
                    <ItemsPanelTemplate>
                        <DataGridCellsPanel KeyboardNavigation.TabNavigation="Continue"
                                            KeyboardNavigation.ControlTabNavigation="Local"/>
                    </ItemsPanelTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </DataGrid.RowStyle>
</DataGrid>

It can also be realized with the following attached properties, referring to the StandardTab property of DataGridView in WinForms.

For VB.net

Public Class DataGridHelper

    Public Shared Function GetStandardTab(element As DataGrid) As Boolean
        If element Is Nothing Then Throw New ArgumentNullException(NameOf(element))
        Return CBool(element.GetValue(StandardTabProperty))
    End Function

    Public Shared Sub SetStandardTab(element As DataGrid, value As Boolean)
        If element Is Nothing Then Throw New ArgumentNullException(NameOf(element))
        element.SetValue(StandardTabProperty, value)
    End Sub

    Public Shared ReadOnly StandardTabProperty As DependencyProperty =
                           DependencyProperty.RegisterAttached("StandardTab",
                           GetType(Boolean), GetType(DataGridHelper),
                           New FrameworkPropertyMetadata(False, FrameworkPropertyMetadataOptions.Inherits, New PropertyChangedCallback(AddressOf OnStandardTabPropertyChanged)))

    Private Shared Sub OnStandardTabPropertyChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
        If TypeOf d Is DataGrid OrElse TypeOf d Is DataGridCellsPanel Then
            If CBool(e.NewValue) Then
                If TypeOf d Is DataGridCellsPanel Then
                    d.SetValue(KeyboardNavigation.TabNavigationProperty, KeyboardNavigationMode.Continue)
                    d.SetValue(KeyboardNavigation.ControlTabNavigationProperty, KeyboardNavigationMode.Local)
                Else
                    d.SetValue(KeyboardNavigation.TabNavigationProperty, KeyboardNavigationMode.Once)
                    d.SetValue(KeyboardNavigation.ControlTabNavigationProperty, KeyboardNavigationMode.Continue)
                End If
            Else
                d.ClearValue(KeyboardNavigation.TabNavigationProperty)
                d.ClearValue(KeyboardNavigation.ControlTabNavigationProperty)
            End If
        End If
    End Sub

End Class

For C# (not tested)

public class DataGridHelper
{
    public static bool GetStandardTab(DataGrid element)
    {
        if (element == null)
        {
            throw new ArgumentNullException(nameof(element));
        }
        return (bool)element.GetValue(StandardTabProperty);
    }

    public static void SetStandardTab(DataGrid element, bool value)
    {
        if (element == null)
        {
            throw new ArgumentNullException(nameof(element));
        }
        element.SetValue(StandardTabProperty, value);
    }

    public static readonly DependencyProperty StandardTabProperty = 
                                              DependencyProperty.RegisterAttached("StandardTab", 
                                              typeof(bool), typeof(DataGridHelper), 
                                              new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.Inherits, new PropertyChangedCallback(OnStandardTabPropertyChanged)));

    private static void OnStandardTabPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is DataGrid || d is DataGridCellsPanel)
        {
            if ((bool) e.NewValue)
            {
                if (d is DataGridCellsPanel)
                {
                    d.SetValue(KeyboardNavigation.TabNavigationProperty, KeyboardNavigationMode.Continue);
                    d.SetValue(KeyboardNavigation.ControlTabNavigationProperty, KeyboardNavigationMode.Local);
                }
                else
                {
                    d.SetValue(KeyboardNavigation.TabNavigationProperty, KeyboardNavigationMode.Once);
                    d.SetValue(KeyboardNavigation.ControlTabNavigationProperty, KeyboardNavigationMode.Continue);
                }
            }
            else
            {
                d.ClearValue(KeyboardNavigation.TabNavigationProperty);
                d.ClearValue(KeyboardNavigation.ControlTabNavigationProperty);
            }
        }
    }
}
ぇ气 2024-11-07 23:14:46

您想要实现的不是正确的行为,每个人都期望在 DataGrid 聚焦时按 Tab 键时像 Excel 一样导航。如果您不希望用户在 DataGrid 中导航,最好通过在 DataGrid 上设置 IsTabStop="False" 来防止 DataGrid 上出现制表位。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文