【问题标题】:WPF - Why do ContextMenu items work for ListBox but not ItemsControl?WPF - 为什么 ContextMenu 项目适用于 ListBox 而不是 ItemsControl?
【发布时间】:2009-05-07 09:06:18
【问题描述】:

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

如果列表控件是ListBox,则上下文菜单项可以正常工作,但是一旦我将其降级为ItemsControl,它就不再工作了。具体来说,菜单项始终是灰色的。我的CommandBinding 中的CanExecute 回调也没有被调用。

ListBox 是什么让带有命令的上下文菜单项正确绑定?

以下是我为突出问题而整理的示例应用程序的一些摘录:

<!-- 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# 代码:

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# 代码隐藏:

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;
    }
}

只是重申一下这个问题——ListBox 提供了什么允许其上下文菜单项命令正确绑定,有什么方法可以让ItemsControl 工作吗?

【问题讨论】:

  • 请注意,当使用 ItemsControl 时,如果我直接在 ContextMenu 中定义 CommandBindings,那么它会起作用,但这首先违背了使用命令的目的。我的猜测是,这与 ListBox 将其项目包装在 ListBoxItem 对象中有关......

标签: .net wpf listbox itemscontrol routed-commands


【解决方案1】:

好的,我看到的主要问题是 ItemsControl 没有所选项目的概念,因此您无法选择要绑定到 DataTemplate 的项目。

我不记得我在哪里看到的了,但是在编写 WPF 时要遵循的一个很好的规则是使用可以为您提供所需行为的控件,然后将其样式设置为您想要的样式。

考虑到这一点,您需要 ListBox 的行为,但需要 ItemsControl 的外观,那么为什么不设置 ListBoxItems 的样式以不显示选定和非选定之间的区别。

<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>

【讨论】:

  • 谢谢 Cameron,我今天就试一试。我仍然想知道 ListBox 究竟添加了什么以使路由命令能够从其项目中向上隧道,因为我确信 ItemsControl 的项目仍然是逻辑树的一部分,并且路由事件应该可以正常传播。
  • 是的,我在凌晨 2 点之前一直在挖掘 Reflector,试图找出 ListBox 如此特别的原因。今天早上我意识到这将是一个更好的解决方案:P
【解决方案2】:

这可能与 ContextMenu 弹出窗口中的项目与 UserControl 的其余部分不同的可视化树(基本上,弹出窗口是一个单独的窗口)有关。这就是 CommandBindings 不起作用的原因。

但现在我不知道如何在不指定 ContextMenu 中的 CommandBindings 的情况下解决此问题。

【讨论】:

  • 我在stackoverflow.com/questions/662164/… 读到过,但它没有解释为什么 ListBox 可以工作,但 ItemsControl 却不能。 ListBox 做了什么使它的上下文菜单项命令可绑定?我查看了 .NET Reflector,但没有发现任何问题。
猜你喜欢
  • 2017-03-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多