ListBox 中奇怪的选项卡行为
Strange tab behavior in ListBox
我有一个简单的列表框:
<Style TargetType="ListBoxItem">
<Setter Property="IsTabStop" Value="False" />
</Style>
<ListBox ItemsSource="{Binding Items}" HorizontalAlignment="Stretch" KeyboardNavigation.TabNavigation="Local">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<RadioButton />
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
当我在它上面切换时,它有奇怪的(或不需要的)行为。我有 100 个项目,它们都不适合屏幕,所以有 ScrollViewer 和 VirtualizingStackPanel,Tab 键工作正常,直到到达列表末尾,然后跳回 20 个位置,下一次跳回 21 个位置,下一次跳回 22 个位置返回。
有什么方法可以强制它在到达末尾时跳转到列表中的第一项?我已经尝试了所有可能的 KeyboardNavigation.TabNavigation 值,但没有帮助。 Shift-Tab 的工作方式相同,从第一项跳到第 20 项,下一次跳到第 21 项,依此类推。
如果我使用 VirtualizingStackPanel.IsVirtualizing="False"
禁用虚拟化,Tab 键按预期工作,但我不能允许它被禁用,因为有些列表非常大。
更新:
我正在尝试手动处理它,但它仍然以同样的方式工作:
private void ListBox_OnPreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key != Key.Tab)
{
return;
}
var focusedItem = FindParent<ListBoxItem>(Keyboard.FocusedElement as DependencyObject);
if (focusedItem != null && focusedItem.Content == ListBox.Items[ListBox.Items.Count - 1])
{
ListBox.MoveFocus(new TraversalRequest(FocusNavigationDirection.First));
e.Handled = true;
}
}
我还尝试在 ListBox 中找到 ScrollViewer 并滚动到顶部,然后聚焦第一个项目,但效果不可靠(看起来滚动是异步发生的,因为有时它会跳到列表的中间)。
我终于找到了一个可行的解决方案,它不像我希望的那样优雅,但看起来可行。
如果有人有better/smaller可行的解决方案,请post回答。
public class FixVirtualizedTabbingBehavior : Behavior<ListBox>
{
protected override void OnAttached()
{
AssociatedObject.PreviewKeyDown += AssociatedObjectOnPreviewKeyDown;
AssociatedObject.GotKeyboardFocus += AssociatedObjectGotKeyboardFocus;
base.OnAttached();
}
void AssociatedObjectGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
var listBox = ((ListBox)sender);
if (e.OldFocus != null && ((DependencyObject)e.OldFocus).FindParent<ListBox>() != listBox)
{
var direction = Keyboard.Modifiers.HasFlag(ModifierKeys.Shift)
? FocusNavigationDirection.Last
: FocusNavigationDirection.First;
MoveFocus(listBox, direction);
}
}
private void AssociatedObjectOnPreviewKeyDown(object sender, KeyEventArgs keyEventArgs)
{
if (keyEventArgs.Key != Key.Tab)
{
return;
}
var listBox = ((ListBox)sender);
int index;
FocusNavigationDirection direction;
if (Keyboard.Modifiers.HasFlag(ModifierKeys.Shift))
{
index = 0;
direction = FocusNavigationDirection.Previous;
}
else
{
index = listBox.Items.Count - 1;
direction = FocusNavigationDirection.Previous;
}
var focusedItem = ((DependencyObject)Keyboard.FocusedElement).FindParent<ListBoxItem>();
if (focusedItem == null || focusedItem.Content != listBox.Items[index])
{
return;
}
keyEventArgs.Handled = true;
MoveFocus(listBox, direction);
}
private void MoveFocus(ListBox listBox, FocusNavigationDirection direction)
{
var scrollViewer = VisualTreeExtensions.FindVisualChildren<ScrollViewer>(listBox).First();
if (direction == FocusNavigationDirection.First)
{
scrollViewer.ScrollToTop();
}
else
{
scrollViewer.ScrollToBottom();
}
Dispatcher.Invoke(new Action(() => { listBox.MoveFocus(new TraversalRequest(direction)); }),
DispatcherPriority.ContextIdle, null);
}
protected override void OnDetaching()
{
AssociatedObject.PreviewKeyDown -= AssociatedObjectOnPreviewKeyDown;
AssociatedObject.GotKeyboardFocus -= AssociatedObjectGotKeyboardFocus;
base.OnDetaching();
}
}
KeyboardNavigation.TabNavigation="Cycle"
我有一个简单的列表框:
<Style TargetType="ListBoxItem">
<Setter Property="IsTabStop" Value="False" />
</Style>
<ListBox ItemsSource="{Binding Items}" HorizontalAlignment="Stretch" KeyboardNavigation.TabNavigation="Local">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<RadioButton />
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
当我在它上面切换时,它有奇怪的(或不需要的)行为。我有 100 个项目,它们都不适合屏幕,所以有 ScrollViewer 和 VirtualizingStackPanel,Tab 键工作正常,直到到达列表末尾,然后跳回 20 个位置,下一次跳回 21 个位置,下一次跳回 22 个位置返回。
有什么方法可以强制它在到达末尾时跳转到列表中的第一项?我已经尝试了所有可能的 KeyboardNavigation.TabNavigation 值,但没有帮助。 Shift-Tab 的工作方式相同,从第一项跳到第 20 项,下一次跳到第 21 项,依此类推。
如果我使用 VirtualizingStackPanel.IsVirtualizing="False"
禁用虚拟化,Tab 键按预期工作,但我不能允许它被禁用,因为有些列表非常大。
更新: 我正在尝试手动处理它,但它仍然以同样的方式工作:
private void ListBox_OnPreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key != Key.Tab)
{
return;
}
var focusedItem = FindParent<ListBoxItem>(Keyboard.FocusedElement as DependencyObject);
if (focusedItem != null && focusedItem.Content == ListBox.Items[ListBox.Items.Count - 1])
{
ListBox.MoveFocus(new TraversalRequest(FocusNavigationDirection.First));
e.Handled = true;
}
}
我还尝试在 ListBox 中找到 ScrollViewer 并滚动到顶部,然后聚焦第一个项目,但效果不可靠(看起来滚动是异步发生的,因为有时它会跳到列表的中间)。
我终于找到了一个可行的解决方案,它不像我希望的那样优雅,但看起来可行。
如果有人有better/smaller可行的解决方案,请post回答。
public class FixVirtualizedTabbingBehavior : Behavior<ListBox>
{
protected override void OnAttached()
{
AssociatedObject.PreviewKeyDown += AssociatedObjectOnPreviewKeyDown;
AssociatedObject.GotKeyboardFocus += AssociatedObjectGotKeyboardFocus;
base.OnAttached();
}
void AssociatedObjectGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
var listBox = ((ListBox)sender);
if (e.OldFocus != null && ((DependencyObject)e.OldFocus).FindParent<ListBox>() != listBox)
{
var direction = Keyboard.Modifiers.HasFlag(ModifierKeys.Shift)
? FocusNavigationDirection.Last
: FocusNavigationDirection.First;
MoveFocus(listBox, direction);
}
}
private void AssociatedObjectOnPreviewKeyDown(object sender, KeyEventArgs keyEventArgs)
{
if (keyEventArgs.Key != Key.Tab)
{
return;
}
var listBox = ((ListBox)sender);
int index;
FocusNavigationDirection direction;
if (Keyboard.Modifiers.HasFlag(ModifierKeys.Shift))
{
index = 0;
direction = FocusNavigationDirection.Previous;
}
else
{
index = listBox.Items.Count - 1;
direction = FocusNavigationDirection.Previous;
}
var focusedItem = ((DependencyObject)Keyboard.FocusedElement).FindParent<ListBoxItem>();
if (focusedItem == null || focusedItem.Content != listBox.Items[index])
{
return;
}
keyEventArgs.Handled = true;
MoveFocus(listBox, direction);
}
private void MoveFocus(ListBox listBox, FocusNavigationDirection direction)
{
var scrollViewer = VisualTreeExtensions.FindVisualChildren<ScrollViewer>(listBox).First();
if (direction == FocusNavigationDirection.First)
{
scrollViewer.ScrollToTop();
}
else
{
scrollViewer.ScrollToBottom();
}
Dispatcher.Invoke(new Action(() => { listBox.MoveFocus(new TraversalRequest(direction)); }),
DispatcherPriority.ContextIdle, null);
}
protected override void OnDetaching()
{
AssociatedObject.PreviewKeyDown -= AssociatedObjectOnPreviewKeyDown;
AssociatedObject.GotKeyboardFocus -= AssociatedObjectGotKeyboardFocus;
base.OnDetaching();
}
}
KeyboardNavigation.TabNavigation="Cycle"