WPF - 为什么 ContextMenu 项目适用于 ListBox 而不是 ItemsControl?
列表中的项目有上下文菜单。 上下文菜单项绑定到路由命令。
如果列表控件是 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;
}
}
只是重申一下问题 - 那是什么 < code>ListBox 提供了允许正确绑定其上下文菜单项命令的功能,是否有某种方法可以使其适用于 ItemsControl
?
Items in a list have context menus. The context menu items are bound to routed commands.
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}" />
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; }
}
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;
}
}
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
?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
好的,我看到的主要问题是 ItemsControl 没有所选项目的概念,因此您无法选择要绑定到的 DataTemplate 的项目。
我不记得在哪里看到过它,但是编写 WPF 时要遵循的一个好规则是使用为您提供所需行为的控件,然后将其设置为您想要的样式。
因此,考虑一下这一点,您想要 ListBox 的行为,而不是 ItemsControl 的外观,那么为什么不将 ListBoxItems 的样式设置为不显示选定和未选定之间的差异。
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.
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.
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.
这可能与以下事实有关:ContextMenu 弹出窗口中的项目与 UserControl 的其余部分不在同一视觉树中(基本上,弹出窗口是一个单独的窗口)。 这就是 CommandBindings 不起作用的原因。
但目前我不知道如何在不指定 ContextMenu 中的 CommandBindings 的情况下解决此问题。
It's probably related to the fact that items in the ContextMenu popup are not int the same visual tree as the rest of your UserControl (basically, popup is a separate window). That's why the CommandBindings don't work.
But for now I don't have an idea how to fix this without specifing CommandBindings within the ContextMenu.