2009-06-18 5 views
59

Hat jemand anderes bemerkt, dass Bindungen mit ElementName für MenuItem Objekte, die in ContextMenu Objekte enthalten sind, nicht richtig aufgelöst werden? Sehen Sie sich diese Probe aus:ElementName Bindung von MenuItem in ContextMenu

<Window x:Class="EmptyWPF.Window1" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Title="Window1" Height="300" Width="300" 
    x:Name="window"> 
    <Grid x:Name="grid" Background="Wheat"> 
     <Grid.ContextMenu> 
      <ContextMenu x:Name="menu"> 
       <MenuItem x:Name="menuItem" Header="Window" Tag="{Binding ElementName=window}" Click="MenuItem_Click"/> 
       <MenuItem Header="Grid" Tag="{Binding ElementName=grid}" Click="MenuItem_Click"/> 
       <MenuItem Header="Menu" Tag="{Binding ElementName=menu}" Click="MenuItem_Click"/> 
       <MenuItem Header="Menu Item" Tag="{Binding ElementName=menuItem}" Click="MenuItem_Click"/> 
      </ContextMenu> 
     </Grid.ContextMenu> 
     <Button Content="Menu" 
       HorizontalAlignment="Center" VerticalAlignment="Center" 
       Click="MenuItem_Click" Tag="{Binding ElementName=menu}"/> 
     <Menu HorizontalAlignment="Center" VerticalAlignment="Bottom"> 
      <MenuItem x:Name="anotherMenuItem" Header="Window" Tag="{Binding ElementName=window}" Click="MenuItem_Click"/> 
      <MenuItem Header="Grid" Tag="{Binding ElementName=grid}" Click="MenuItem_Click"/> 
      <MenuItem Header="Menu" Tag="{Binding ElementName=menu}" Click="MenuItem_Click"/> 
      <MenuItem Header="Menu Item" Tag="{Binding ElementName=anotherMenuItem}" Click="MenuItem_Click"/> 
     </Menu> 
    </Grid> 
</Window> 

alle Bindungen große Arbeit mit Ausnahme der Bindungen innerhalb der ContextMenu enthalten. Sie drucken während der Laufzeit einen Fehler in das Ausgabefenster.

Jeder weiß von irgendwelchen Arbeitsumgebungen? Was ist denn hier los?

+0

Das Problem hat offensichtlich etwas mit Namescopes zu tun ... –

+0

Definiert ContextMenus standardmäßig ein eigenes Namescope? –

Antwort

51

Ich fand eine viel einfachere Lösung.

In den Code hinter der Usercontrol:

NameScope.SetNameScope(contextMenu, NameScope.GetNameScope(this)); 
+0

Dies scheint in Framework 4.0 nicht mehr zu funktionieren. –

+0

Sorry, ich habe es nicht auf 4,0 –

+5

versucht Wirklich funktioniert es für mich in 4.0. – esylvestre

4

Nach ein wenig experimentieren, entdeckte ich eine Arbeit um:

Top-Level-Make Window/UserControlINameScope implementieren und setzen NameScope von ContextMenu auf die oberste Ebene Kontrolle.

public class Window1 : Window, INameScope 
{ 
    public Window1() 
    { 
     InitializeComponent(); 
     NameScope.SetNameScope(contextMenu, this); 
    } 

    // Event handlers and etc... 

    // Implement INameScope similar to this: 
    #region INameScope Members 

    Dictionary<string, object> items = new Dictionary<string, object>(); 

    object INameScope.FindName(string name) 
    { 
     return items[name]; 
    } 

    void INameScope.RegisterName(string name, object scopedElement) 
    { 
     items.Add(name, scopedElement); 
    } 

    void INameScope.UnregisterName(string name) 
    { 
     items.Remove(name); 
    } 

    #endregion 
} 

Dies ermöglicht das Kontextmenü benannten Elemente innerhalb des Window zu finden. Irgendwelche anderen Wahlen?

5

Kontextmenüs sind schwierig zu binden. Sie existieren außerhalb des visuellen Baums Ihres Controls und können daher Ihren Elementnamen nicht finden.

Versuchen Sie, den Datenkontext Ihres Kontextmenüs auf das Platzierungsziel zu setzen. Sie müssen RelativeSource verwenden.

<ContextMenu 
    DataContext="{Binding PlacementTarget, RelativeSource={RelativeSource Self}}"> ... 
+0

Wenn Sie den DataContext auf PlacementTarget setzen, wirkt sich dies auf ElementName-Bindungen aus. Ich denke, der DataContext wird nur für Bindungen verwendet, für die keine Source-, RelativeSource- oder ElementName-Eigenschaften festgelegt sind. –

+0

Das Festlegen einer ElementName-Eigenschaft funktioniert nur, wenn der Layout-Manager das zugeordnete Element durch Navigieren in der visuellen Struktur finden kann. Kontextmenüs sind nicht in der visuellen Struktur des Steuerelements vorhanden, zu dem sie hinzugefügt wurden. Sie müssen den Datenkontext des Kontextmenüs festlegen, damit der Layout-Manager den visuellen Baum seines Placement-Ziels nach oben navigieren kann, um das zugehörige Element zu finden. – Josh

+0

Das Hinzufügen des DataContext zu dem obigen Beispiel behebt das Problem nicht. Ich habe immer noch den folgenden Fehler im Ausgabefenster: "System.Windows.Data Error: 4: Die Quelle für die Bindung mit der Referenz 'ElementName = window' kann nicht gefunden werden. BindingExpression: (kein Pfad); DataItem = null; Zielelement ist 'MenuItem '(Name =' menuItem '); Zieleigenschaft ist' Tag '(Typ' Objekt ') " –

19

Hier ist eine andere XAML-einzige Abhilfe. (Dies setzt voraus, Sie auch wollen, was im Innern des Datacontext, zum Beispiel, sind Sie MVVMing it)

Option ein, wenn das Mutterelement der ContextMenu ist nicht in einem Datatemplate:

Command="{Binding PlacementTarget.DataContext.MyCommand, 
     RelativeSource={RelativeSource AncestorType=ContextMenu}}" 

Dies würde für OP-Frage funktionieren. Dies funktioniert nicht, wenn Sie sich in einem DataTemplate befinden. In diesen Fällen ist die Datacontext oft einer von vielen in einer Sammlung und die ICommand Sie binden möchten, ist ein Geschwister Eigenschaft der Auflistung innerhalb des gleichen Ansichtsmodell (die Datacontext des Fensters, sagen).

In diesen Fällen können Sie die Vorteile der Tag nehmen vorübergehend die Eltern Datacontext, die sowohl die Sammlung und Ihre ICommand enthält zu halten:

class ViewModel 
{ 
    public ObservableCollection<Derp> Derps { get;set;} 
    public ICommand DeleteDerp {get; set;} 
} 

und in der XAML

<!-- ItemsSource binds to Derps in the DataContext --> 
<StackPanel 
    Tag="{Binding DataContext, ElementName=root}"> 
    <StackPanel.ContextMenu> 
     <ContextMenu> 
      <MenuItem 
       Header="Derp"      
       Command="{Binding PlacementTarget.Tag.DeleteDerp, 
       RelativeSource={RelativeSource 
            AncestorType=ContextMenu}}" 
       CommandParameter="{Binding PlacementTarget.DataContext, 
       RelativeSource={RelativeSource AncestorType=ContextMenu}}"> 
      </MenuItem> 
+0

Ich denke, der relevante Punkt, den Sie hier machen, ist, dass Sie Tags verwenden können und relative Quellbindungen, um Daten an einer anderen Stelle im visuellen Baum zu erhalten. –

+0

Dies ist nicht wirklich mit MVVM verwandt. Ich verwende nur ElementName-Bindungen, wenn ich versuche, zwei viewbezogene Steuerelemente außerhalb der VM zu verknüpfen. Dies ist eine gute Lösung zum Verknüpfen von Kontextmenüelementen mit Befehlen auf einer VM. Eine gute Alternative ist die Verwendung eines gerouteten Befehls, der an die VM gebunden ist. Ein gutes Beispiel dafür ist Josh Smiths CommandSink-Klasse. –

1

Ich bin nicht sicher, warum greifen Sie auf magische Tricks, nur um eine Zeile Code im Eventhandler für den Mausklick zu vermeiden, den Sie bereits behandeln:

+0

Auf diese Weise kann der Menüeintrag nicht automatisch entsprechend dem gebundenen Befehl deaktiviert werden. Während es für die Ausführung funktioniert, müssten Sie mehr Code hinzufügen, um den Menüeintrag beim Laden ebenfalls zu aktivieren/deaktivieren. Nicht dass das schlecht ist, es bringt nur schlechte Erinnerungen an WinFoms UI Code Spaghetti für viele Leute zurück. – jpierson

16

Wie von anderen gesagt, ist das 'ContextMenu' nicht im visuellen Baum enthalten und eine Bindung 'ElementName' funktioniert nicht. Das Festlegen des Namensbereichs des Kontextmenüs, wie von der angenommenen Antwort vorgeschlagen, funktioniert nur, wenn das Kontextmenü nicht in einem 'DataTemplate' definiert ist. Ich habe dies gelöst, indem ich die {x:Reference} Markup-Extension verwendet, die der "ElementName" -Bindung ähnlich ist, aber die Bindung unter Umgehung der visuellen Struktur anders löst. Ich halte dies für viel besser lesbar als die Verwendung von PlacementTarget. Hier ein Beispiel:

<Image Source="{Binding Image}">  
    <Image.ContextMenu> 
     <ContextMenu> 
      <MenuItem Header="Delete" 
         Command="{Binding Source={x:Reference Name=Root}, Path=DataContext.RemoveImage}" 
         CommandParameter="{Binding}" /> 
     </ContextMenu> 
    </Image.ContextMenu> 
</Image> 

Nach der MSDN-Dokumentation

x:Reference is a construct defined in XAML 2009. In WPF, you can use XAML 2009 features, but only for XAML that is not WPF markup-compiled. Markup-compiled XAML and the BAML form of XAML do not currently support the XAML 2009 language keywords and features.

was immer das heißt ... Arbeiten für mich, though.

+4

Eigentlich ist es der nützlichste Kommentar hier –

+1

Das funktioniert tatsächlich am besten, ich habe alle Antworten ausprobiert und dieses ist korrekt für WPF .Net 4.5.2. Danke @Marc –

Verwandte Themen