WindowsCommunityToolkit.DataGrid TabIndex 在 DataGridTemplateColumn.TextBox 中被忽略

WindowsCommunityToolkit.DataGrid TabIndex is ignored in DataGridTemplateColumn.TextBox

我有一个 DataGrid (https://github.com/CommunityToolkit/WindowsCommunityToolkit),一个 TemplateColumn 和一个 TextBox。不幸的是,我的 TabIndex 被忽略了,因此无法通过 Tab

跳转到第二个 TextBox

TabIndex 属性 设置正确,分别具有值 12

<controls:DataGrid ItemsSource="{x:Bind ViewModel.LocalizedTexts}" AutoGenerateColumns="False">
    <controls:DataGrid.Columns>
      <controls:DataGridTextColumn Header="locale" Binding="{Binding Locale}" IsReadOnly="True"/>
      <controls:DataGridTextColumn Header="current value" Binding="{Binding OldText}" IsReadOnly="True"/>
      <controls:DataGridTemplateColumn Header="new value">
        <controls:DataGridTemplateColumn.CellTemplate>
          <DataTemplate>
            <TextBox Text="{Binding Path=Text, Mode=TwoWay}" IsTabStop="True" TabIndex="{Binding TabIndex}"/>
          </DataTemplate>
        </controls:DataGridTemplateColumn.CellTemplate>
      </controls:DataGridTemplateColumn>
    </controls:DataGrid.Columns>
</controls:DataGrid>

Datagridcell 的内容是一个 ContentPresenter 控件。您的文本框实际上位于两个不同的 ContentPresenter 控件中。因此您将无法通过 Tab 导航到另一个单元格。您可以在一个简单的 ListView 中进行测试,将多个文本框放入一个模板中,然后设置 TabIndex 属性。当你按TAB键时,你会发现只有同一个项目中的文本框会被聚焦。

DataGrid 这样的其他东西目前对我来说是不可能的。无法用简单的 ListView.

实现我的项目

我现在已经创建了一个解决方法,我可以完全控制所有要关注的 UIElement

此外,如果已经有文本,则 TextBoxes 会相应地设置光标。

private void DataGrid_OnLoaded(object sender, RoutedEventArgs e)
{
    // get parent ContentDialog
    var dialog = this.FindVisualParent<ContentDialog>();

    // get all children and sort them
    // https://github.com/microsoft/microsoft-ui-xaml/blob/548cc630f37eac2658332a5f808160b2cf9f8cef/dev/ContentDialog/ContentDialog_themeresources.xaml#L311
    var uiElements = new List<UIElement>();
    dialog.FindVisualChildren(uiElements);
    var sorted = uiElements.OfType<TextBox>().Where(box => box.TabIndex > 0).Cast<UIElement>().Concat(uiElements.OfType<Button>().Where(button => button.Name.Equals("PrimaryButton") || button.Name.Equals("SecondaryButton") || button.Name.Equals("CloseButton"))).ToList();

    // catch tab keyboard event
    dialog.PreviewKeyDown += (o, args) =>
    {
        if (args.Key == VirtualKey.Tab)
        {
            var currentFocus = sorted.FirstOrDefault(element => element.FocusState != FocusState.Unfocused);
            if (currentFocus != null)
            {
                var nextOf = currentFocus;
                n:
                var next = sorted.NextOrFirstOf(nextOf);
                if (Focus(next) == false) // can happen if a button is not visible
                {
                    nextOf = next;
                    goto n;
                }
            }
            else
            {
                Focus(sorted.First());
            }
            args.Handled = true;
        }
    };

    // focus the first empty TextBox if present
    DispatcherQueue.TryEnqueue(() =>
    {
        var textBox = sorted.OfType<TextBox>().OrderBy(box => box.Text).First();
        Focus(textBox);
    });
}

private bool Focus(UIElement element)
{
    if (element is TextBox textBox)
        textBox.SelectionStart = textBox.Text.Length;
    return element.Focus(FocusState.Programmatic);
}

DependencyObjectExtensions

public static class DependencyObjectExtensions
{
    /// <summary>
    /// Find all children by using the <see cref="VisualTreeHelper"/>
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="startNode"></param>
    /// <param name="results"></param>
    public static void FindVisualChildren<T>(this DependencyObject startNode, List<T> results)
        where T : DependencyObject
    {
        int count = VisualTreeHelper.GetChildrenCount(startNode);
        for (int i = 0; i < count; i++)
        {
            var current = VisualTreeHelper.GetChild(startNode, i);
            if (current.GetType() == typeof(T) || current.GetType().GetTypeInfo().IsSubclassOf(typeof(T)))
            {
                var asType = (T)current;
                results.Add(asType);
            }

            current.FindVisualChildren(results);
        }
    }

    /// <summary>
    /// Find the parent <see cref="DependencyObject"/> by using the <see cref="VisualTreeHelper"/>
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="startNode"></param>
    /// <returns></returns>
    public static T FindVisualParent<T>(this DependencyObject startNode) where T : DependencyObject
    {
        var parent = VisualTreeHelper.GetParent(startNode);
        if (parent != null)
        {
            if (parent.GetType() == typeof(T) || parent.GetType().GetTypeInfo().IsSubclassOf(typeof(T)))
            {
                return (T)parent;
            }
            else
            {
                return parent.FindVisualParent<T>();
            }
        }
        return null;
    }
}