Search code examples
.netuwpwinui-3windows-community-toolkitwinui

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

  • 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