问题
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
?
回答1:
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.
<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>
回答2:
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.
来源:https://stackoverflow.com/questions/833607/wpf-why-do-contextmenu-items-work-for-listbox-but-not-itemscontrol