WPF - 为什么文本菜单项列表框工作,但不是ItemsControl的?菜单项、文本、不是、列表

2023-09-07 08:34:13 作者:烟酒为伴

列表中的项目有上下文菜单。上下文菜单项绑定到路由命令。

Items in a list have context menus. The context menu items are bound to routed commands.

上下文菜单项正常工作,如果列表控件是列表框,但只要我降级到的ItemsControl 它不再起作用。具体的菜单项总是灰色的。该 CanExecute 回调在我的的CommandBinding 不被称为无论是。

The context menu items work correctly if the list control is a ListBox, but as soon as I downgrade it to an ItemsControl it no longer works. Specifically the menu items are always greyed out. The CanExecute callback in my CommandBinding is not being called either.

这是关于列表框,允许上下文菜单项与命令正确绑定?

What is it about ListBox that allows context menu items with commands to bind correctly?

下面是来自一个示例应用程序我放在一起,以突出问题的一些摘录:

Here are some excerpts from a sample app I put together to highlight the problem:

<!-- Data template for items -->
<DataTemplate DataType="{x:Type local:Widget}">
  <StackPanel Orientation="Horizontal">
    <StackPanel.ContextMenu>
      <ContextMenu>
        <MenuItem Header="UseWidget" 
                  Command="{x:Static l:WidgetListControl.UseWidgetCommand}"
                  CommandParameter="{Binding}" />
      </ContextMenu>
    </StackPanel.ContextMenu>
    <TextBlock Text="{Binding Path=Name}" />
    <TextBlock Text="{Binding Path=Price}" />
  </StackPanel>
</DataTemplate>

<!-- Binding -->
<UserControl.CommandBindings>
  <CommandBinding Command="{x:Static l:WidgetListControl.UseWidgetCommand}" 
                  Executed="OnUseWidgetExecuted" 
                  CanExecute="CanUseWidgetExecute" />
</UserControl.CommandBindings>

<!-- ItemsControl doesn't work... -->
<ItemsControl ItemsSource="{Binding Path=Widgets}" />

<!-- But change it to ListBox, and it works! -->
<ListBox ItemsSource="{Binding Path=Widgets}" />

下面是C#code的视图模型和数据项:

Here's the C# code for the view model and data item:

public sealed class WidgetListViewModel
{
    public ObservableCollection<Widget> Widgets { get; private set; }

    public WidgetViewModel()
    {
        Widgets = new ObservableCollection<Widget>
            {
                new Widget { Name = "Flopple", Price = 1.234 },
                new Widget { Name = "Fudge", Price = 4.321 }
            };
    }
}

public sealed class Widget
{
    public string Name { get; set; }
    public double Price { get; set; }
}

下面是C#code-背后控制:

Here's the C# code-behind for the control:

public partial class WidgetListControl
{
    public static readonly ICommand UseWidgetCommand 
        = new RoutedCommand("UseWidget", typeof(WidgetListWindow));

    public WidgetListControl()
    {
        InitializeComponent();
    }

    private void OnUseWidgetExecuted(object s, ExecutedRoutedEventArgs e)
    {
        var widget = (Widget)e.Parameter;
        MessageBox.Show("Widget used: " + widget.Name);
    }

    private void CanUseWidgetExecute(object s, CanExecuteRoutedEventArgs e)
    {
        e.CanExecute = true;
        e.Handled = true;
    }
}

只是重申了问题 - 什么是是,列表框规定,允许它的上下文菜单项命令正确绑定,并且有一些方法可以让我得到这个工作的ItemsControl

Just to reiterate the question -- what is is that ListBox provides that allows for it's context menu item commands to bind correctly, and is there some way I can get this working for ItemsControl?

推荐答案

好吧,我看到的主要问题是,一个ItemsControl没有选择的项目的概念,所以你不能选择项目的DataTemplate中要绑定到

Ok, the main issue I see is that an ItemsControl doesn't have a concept of the selected item, so you can't select an item for the DataTemplate to be bound to.

我不记得在那里我看到了它,但一个好的规则编写WPF是使用,让你你需要再样式它看起来像你想要的行为的控制时要遵循的。

I can't remember where I saw it, but a good rule to follow when writing WPF is to use the control that gives you the behavior you need and then style it to look like what you want.

所以想着这个,你想有一个列表框的行为,而是一个ItemsControl的样子,所以你为什么不风格的ListBoxItems不显示之间的选择和非选择的差异

So thinking about this, you want the behaviour of a ListBox, but the look of an ItemsControl, so why don't you style the ListBoxItems to not show the difference between selected and non-selected.

<Style TargetType="{x:Type ListBoxItem}">
    <Setter Property="Background" Value="Transparent"/>
    <Setter Property="HorizontalContentAlignment" Value="{Binding Path=HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
    <Setter Property="VerticalContentAlignment" Value="{Binding Path=VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
    <Setter Property="Padding" Value="2,0,0,0"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type ListBoxItem}">
                <Border SnapsToDevicePixels="true" x:Name="Bd" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}">
                    <ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                </Border>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsEnabled" Value="false">
                        <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>