WPF - Почему элементы ContextMenu работают для ListBox, а не ItemsControl? - PullRequest
3 голосов
/ 07 мая 2009

Элементы в списке имеют контекстные меню. Элементы контекстного меню привязаны к перенаправленным командам.

Элементы контекстного меню работают правильно, если элемент управления списком - ListBox, но как только я понижаю его до ItemsControl, он больше не работает. В частности, пункты меню всегда серые. Обратный вызов CanExecute в моем CommandBinding также не вызывается.

Что такое 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?

Ответы [ 2 ]

5 голосов
/ 07 мая 2009

Хорошо, основная проблема, которую я вижу, состоит в том, что 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>
0 голосов
/ 07 мая 2009

Возможно, это связано с тем, что элементы во всплывающем окне ContextMenu не находятся в том же визуальном дереве, что и остальная часть вашего UserControl (в основном, всплывающее окно - это отдельное окно). Вот почему CommandBindings не работают.

Но пока у меня нет идеи, как это исправить без указания CommandBindings в ContextMenu.

...