'WindowsCommunityToolkit.DataGrid TabIndex is ignored in DataGridTemplateColumn.TextBox

I have a DataGrid (https://github.com/CommunityToolkit/WindowsCommunityToolkit) with a TemplateColumn and a TextBox. Unfortunately, my TabIndex is ignored and it is therefore not possible to jump to the second TextBox via Tab

The TabIndex property is set correctly and has the values 1 and 2 respectively.

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

enter image description here



Solution 1:[1]

Something else like the DataGrid is currently out of the question for me. Could not implement my project with a simple ListView.

I have now created a workaround where I have full control over all UIElement that are to be focused at all.

In addition, the cursor is set accordingly for the TextBoxes, should there already be text.

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

enter image description here

Solution 2:[2]

The Datagridcell's content is a ContentPresenter control. Your TextBoxes are actually in two different ContentPresenter controls. So you won't be able to navigate to another cell via Tab. You could test in a simple ListView, put multiple TextBoxes inside a template, and set the TabIndex property. When you press the TAB key, you could find that only the TextBoxes inside the same item will be focused.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Dominic Jonas
Solution 2 Roy Li - MSFT