3 Stimmen

WPF - Warum funktionieren ContextMenu Elemente für ListBox aber nicht ItemsControl?

Elemente in einer Liste haben Kontextmenüs. Die Kontextmenüpunkte sind an geroutete Befehle gebunden.

Die Kontextmenüeinträge funktionieren korrekt, wenn das Listensteuerelement ein ListBox aber sobald ich es auf eine ItemsControl es funktioniert nicht mehr. Insbesondere sind die Menüpunkte immer ausgegraut. Die CanExecute Rückruf in meinem CommandBinding wird ebenfalls nicht aufgerufen.

Worum geht es? ListBox die es ermöglicht, Kontextmenüeinträge mit Befehlen korrekt zu binden?

Hier sind einige Auszüge aus einer Beispielanwendung, die ich zusammengestellt habe, um das Problem zu verdeutlichen:

<!-- 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}" />

Hier ist der C#-Code für das Ansichtsmodell und das Datenelement:

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

Hier ist der C# Code-Behind für das Steuerelement:

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

Nur um die Frage zu wiederholen - was ist das? ListBox zur Verfügung stellt, die es ermöglicht, dass die Befehle des Kontextmenüs korrekt gebunden werden, und gibt es eine Möglichkeit, wie ich dies für ItemsControl ?

5voto

Cameron MacFarland Punkte 67889

Ok, das Hauptproblem, das ich sehe, ist, dass ein ItemsControl kein Konzept des ausgewählten Elements hat, so dass Sie nicht ein Element für das DataTemplate auswählen können, um gebunden zu werden.

Ich kann mich nicht erinnern, wo ich es gesehen habe, aber eine gute Regel zu folgen, wenn das Schreiben von WPF ist das Steuerelement zu verwenden, die Ihnen das Verhalten, das Sie benötigen und dann Stil es zu schauen, wie was Sie wollen.

Wenn man also darüber nachdenkt, möchte man das Verhalten einer ListBox, aber das Aussehen eines ItemsControl, also warum nicht die ListBoxItems so gestalten, dass sie den Unterschied zwischen ausgewählt und nicht ausgewählt nicht anzeigen.

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

0voto

arconaut Punkte 3157

Es hängt wahrscheinlich damit zusammen, dass die Elemente im ContextMenu-Popup nicht im selben visuellen Baum wie der Rest des UserControls liegen (im Grunde ist das Popup ein separates Fenster). Das ist, warum die CommandBindings nicht funktionieren.

Aber im Moment habe ich keine Idee, wie man das beheben kann, ohne CommandBindings im ContextMenu zu spezifizieren.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X