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
属性 设置正确,分别具有值 1
和 2
。
<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;
}
}
我有一个 DataGrid
(https://github.com/CommunityToolkit/WindowsCommunityToolkit),一个 TemplateColumn
和一个 TextBox
。不幸的是,我的 TabIndex
被忽略了,因此无法通过 Tab
TextBox
TabIndex
属性 设置正确,分别具有值 1
和 2
。
<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;
}
}