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