如何使作为 ListBox 的 ItemTemplate 的 Button 接收键盘事件?

How can I make a Button that is the ItemTemplate for a ListBox receive keyboard events?

我创建了一个 ListBox 并给了它一个定义如下的 ItemTemplate:

<DataTemplate x:Key="myDataTemplate">
    <Button Command="{Binding MyListItemButtonCommand}" CommandParameter="{Binding .}">
        <!-- detail omitted -->
    </Button>
</DataTemplate>

如果我单击其中一个列表项,它会按预期执行按钮的命令。但是,如果我使用键盘导航将键盘焦点提供给列表项并按 Enter 或 Space,它不会执行命令。这是因为,如 this question 中所述,它是具有键盘焦点的列表项而不是按钮本身。当列表项具有焦点时,我想对按键做出反应,就像它是具有焦点的按钮时一样。

这可以通过 XAML 实现,也可以通过代码隐藏实现,但它不能让我必须识别我需要监听的事件集,也不能让我必须识别我必须测试哪些键和组合键。例如,包含 if (e.Key == Key.Enter) 之类代码的解决方案是不可接受的(因为我不想负责所有可能用于激活本机 Windows按钮控制)。

这与链接的问题不同,因为在那个问题中,提问者编写了一个事件处理程序,他们需要帮助才能将其发送到 运行。我没有写过事件处理程序,也不想写;我希望按钮接收键盘事件(就好像它是具有键盘焦点的按钮,而不是列表项)并使用属于框架的一部分并且已经由某人为我编写的事件处理程序响应它们在微软。

您可以处理 ListBoxItem 容器的 PreviewKeyDown 事件并以编程方式聚焦按钮:

private void ListBoxItem_PreviewKeyDown(object sender, KeyEventArgs e)
{
    ListBoxItem lbi = (ListBoxItem)sender;
    Button btn = FindVisualChild<Button>(lbi);
    if (btn != null)
        btn.Focus();
}

private static T FindVisualChild<T>(DependencyObject obj) where T : DependencyObject
{
    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
    {
        DependencyObject child = VisualTreeHelper.GetChild(obj, i);
        if (child != null && child is T)
            return (T)child;
        else
        {
            T childOfChild = FindVisualChild<T>(child);
            if (childOfChild != null)
                return childOfChild;
        }
    }
    return null;
}

XAML:

<ListBox ... KeyboardNavigation.TabNavigation="Contained">
    <ListBox.Resources>
        <Style TargetType="ListBoxItem">
            <EventSetter Event="PreviewKeyDown" Handler="ListBoxItem_PreviewKeyDown" />
        </Style>
    </ListBox.Resources>
</ListBox>

您可以通过选择一个本身不支持任何用户交互的列表控件来解决这个问题。

您通常会通过 ItemsControl 执行此操作,可选择将其放入 Scrollviewer 中。

<ItemsControl ...>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Button Command="{Binding MyListItemButtonCommand}"
                    CommandParameter="{Binding .}" />
        </DataTemplate>
    </ItemsControl.ItemTemplate>
<ItemsControl>