简单的WPF ItemsControl的焦点行为

时间:2011-02-10 06:57:09

标签: wpf focus itemscontrol itemtemplate keyboard-navigation

在焦点和键盘导航方面,我看到了奇怪的行为。在下面的示例中,我有一个简化的ItemsControl,它已经模板化,以便显示绑定到ItemsSource的CheckBoxes列表。

<ItemsControl FocusManager.IsFocusScope="True"
              ItemsSource="{Binding ElementName=TheWindow, Path=ListOStrings}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <CheckBox Content="{Binding}" />
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

由于某些奇怪的原因, FocusManager.IsFocusScope =“True”分配导致在通过鼠标单击选中复选框时无法设置键盘焦点,并且当a时,焦点会跳出ItemsControl使用键盘上的空格键选中复选框。这两个症状似乎都指向选中复选框时发生了一些奇怪的导航,但我很难找到它的底部。

如果我使用此方法将可视树中的任何父元素设置为焦点范围,则会出现此问题。如果我删除了 FocusManager.IsFocusScope =“True”,那么问题就会消失。不幸的是,我在一个更大的项目中看到了这个问题,我不能仅仅删除这些焦点范围,而不必担心其他与焦点相关的后果。

有人可以向我解释我所看到的奇怪行为吗?这是一个错误还是我完全错过了什么?

2 个答案:

答案 0 :(得分:16)

本文非常好地解释了这一点:http://www.codeproject.com/KB/WPF/EnhancedFocusScope.aspx

  

FocusScope设计的内容是什么?

     

Microsoft在WPF中使用FocusScope   创建一个临时的辅助焦点。   WPF中的每个ToolBar和Menu都有它的   自己的重点范围。

     凭借这些知识,我们可以清楚地了解到   看看为什么我们遇到这些问题:

     

不应执行工具栏按钮   命令本身,但无论如何   在工具栏之前有焦点   点击。要做到这一点,请路由   命令忽略焦点的焦点   范围并使用'main'逻辑   转而关注。这解释了原因   路由命令在内部不起作用   重点范围。

     

为什么大文本框在   测试应用截图仍然   显示插入符号?我不知道   回答这个 - 但为什么不应该呢?   当然,文本框没有   键盘焦点(小文本框中)   WPF重点范围有);但它   仍然有主要的逻辑焦点   活动窗口,是接收器   所有路由命令。

这部分涵盖了您所看到的行为

  

为什么键盘焦点移动到   选中标签时的大文本框   WPF焦点范围内的CheckBox和   按空格切换它?

     

嗯,这正是您的期望   当您单击菜单项或   工具栏:键盘焦点应该   回到主要焦点。所有   ButtonBase派生的控件可以做到   此

答案 1 :(得分:8)

@Pleak很好地解释了这个问题。请阅读文章http://www.codeproject.com/KB/WPF/EnhancedFocusScope.aspx以充分了解问题所在以及如何解决问题。我将添加文章中提到的IsEnhancedFocusScope附加行为的完整实现:​​

public static class FocusExtensions
{
    private static bool SettingKeyboardFocus { get; set; }

    public static bool GetIsEnhancedFocusScope(DependencyObject element) {
        return (bool)element.GetValue(IsEnhancedFocusScopeProperty);
    }

    public static void SetIsEnhancedFocusScope(DependencyObject element, bool value) {
        element.SetValue(IsEnhancedFocusScopeProperty, value);
    }

    public static readonly DependencyProperty IsEnhancedFocusScopeProperty =
        DependencyProperty.RegisterAttached(
            "IsEnhancedFocusScope",
            typeof(bool),
            typeof(FocusExtensions),
            new UIPropertyMetadata(false, OnIsEnhancedFocusScopeChanged));

    private static void OnIsEnhancedFocusScopeChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e) {
        var item = depObj as UIElement;
        if (item == null)
            return;

        if ((bool)e.NewValue) {
            FocusManager.SetIsFocusScope(item, true);
            item.GotKeyboardFocus += OnGotKeyboardFocus;
        }
        else {
            FocusManager.SetIsFocusScope(item, false);
            item.GotKeyboardFocus -= OnGotKeyboardFocus;
        }
    }

    private static void OnGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e) {
        if (SettingKeyboardFocus) {
            return;
        }

        var focusedElement = e.NewFocus as Visual;

        for (var d = focusedElement; d != null; d = VisualTreeHelper.GetParent(d) as Visual) {
            if (FocusManager.GetIsFocusScope(d)) {
                SettingKeyboardFocus = true;

                try {
                    d.SetValue(FocusManager.FocusedElementProperty, focusedElement);
                }
                finally {
                    SettingKeyboardFocus = false;
                }

                if (!(bool)d.GetValue(IsEnhancedFocusScopeProperty)) {
                    break;
                }
            }
        }
    }
}

在您的XAML中,您只需要设置此附加属性而不是标准IsFocusScope属性:

<ItemsControl my:FocusExtensions.IsEnhancedFocusScope="True"
              ItemsSource="{Binding ElementName=TheWindow, Path=ListOStrings}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <CheckBox Content="{Binding}" />
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

它可以按预期的重点范围工作。