2009-06-18 22 views
59

Qualcun altro ha notato che i binding con ElementName non si risolvono correttamente per gli oggetti MenuItem contenuti negli oggetti ContextMenu? Dai un'occhiata a questo esempio:ElementName Binding da 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> 

Tutti gli attacchi funzionano ottimo, tranne per gli attacchi contenuti nel ContextMenu. Stampano un errore nella finestra Output durante il runtime.

Qualcuno sa di eventuali problemi? Cosa sta succedendo qui?

+0

Il problema ovviamente ha qualcosa a che fare con i cannocchiali ... –

+0

Do ContextMenus definisce il proprio namescope di default? –

risposta

51

ho trovato una soluzione molto più semplice.

Nel codice dietro per l'UserControl:

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

Questo non sembra funzionare più nel framework 4.0. –

+0

Spiacente, non l'ho provato su 4.0 –

+5

In realtà, funziona per me in 4.0. – esylvestre

4

Dopo aver sperimentato un po ', ho scoperto un lavoro intorno:

Fai livello superiore Window/UserControl attuare INameScope e impostare NameScope del ContextMenu al controllo di livello superiore.

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 
} 

In questo modo il menu di scelta rapida per trovare elementi denominati all'interno del Window. Altre opzioni?

5

I menu di scelta rapida sono difficili da rispettare. Esistono al di fuori dell'albero visivo del tuo controllo, quindi non riescono a trovare il nome dell'elemento.

Prova a impostare il datacontext del menu contestuale sul target di posizionamento. Devi usare RelativeSource.

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

L'impostazione di DataContext in PlacementTarget ha effetto sui collegamenti ElementName? Penso che DataContext sia usato solo per Bindings che non hanno set di proprietà Source, RelativeSource o ElementName. –

+0

L'impostazione di una proprietà ElementName funzionerà solo se il gestore di layout può trovare l'elemento associato navigando nell'albero visivo. I menu contestuali non esistono all'interno dell'albero visivo del controllo a cui sono aggiunti. È necessario impostare il datacontext del menu di scelta rapida in modo che il gestore di layout possa risalire l'albero visivo del target di posizionamento per trovare l'elemento associato. – Josh

+0

L'aggiunta di DataContext all'esempio precedente non ha risolto il problema. Ho ancora il seguente errore nella finestra di output: "System.Windows.Data Errore: 4: Impossibile trovare l'origine per l'associazione con riferimento 'ElementName = window'. BindingExpression: (nessun percorso); DataItem = null; l'elemento di destinazione è 'MenuItem '(Name =' menuItem '); la proprietà target è' Tag '(tipo' Object ') " –

19

Ecco un'altra soluzione XAML-only. (Ciò presuppone anche che si desidera cosa c'è dentro il DataContext, ad esempio, sei MVVMing esso)

Opzione uno, in cui l'elemento principale del ContextMenu non è in una DataTemplate:

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

Questo funzionerebbe per la domanda dell'OP. Ciò non funzionerà se ci si trova all'interno di un DataTemplate . In questi casi, il DataContext è spesso uno dei tanti in una collezione, e la iCommand si desidera associare a è una proprietà dei fratelli della collezione all'interno dello stesso ViewModel (la DataContext della finestra, dicono).

In questi casi, è possibile usufruire del Tag per memorizzare temporaneamente il genitore DataContext che contiene sia la raccolta e la tua ICommand:

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

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

Penso che il punto rilevante che stai facendo qui è che è possibile utilizzare i tag e relativi collegamenti di origine per ottenere dati in un'altra posizione nell'albero visivo. –

+0

Questo non è realmente correlato a MVVM. Io uso solo i binding ElementName quando sto cercando di legare due controlli relativi alla vista insieme al di fuori della VM. Questa è una buona soluzione per legare le voci del menu contestuale ai comandi su una VM. Una buona alternativa è usare un comando indirizzato che si collega alla VM. Un buon esempio di questo è la classe CommandSink di Josh Smith. –

1

Non sono sicuro del perché ricorrere a trucchi magici solo per evitare una riga di codice all'interno dell'handler di eventi per il clic del mouse che già gestisci:

private void MenuItem_Click(object sender, System.Windows.RoutedEventArgs e) 
    { 
     // this would be your tag - whatever control can be put as string intot he tag 
     UIElement elm = Window.GetWindow(sender as MenuItem).FindName("whatever control") as UIElement; 
    } 
+0

In questo modo non consente di disattivare automaticamente la voce di menu in base al comando associato. Quindi mentre funziona per l'esecuzione, dovresti aggiungere altro codice per disabilitare/abilitare la voce di menu di conseguenza quando viene caricata. Non che questo sia male, porta solo brutti ricordi di spaghetti codice UI WinFoms per molte persone. – jpierson

16

Come detto da altri, il "ContextMenu" non è contenuto nell'albero visivo e un binding "ElementName" non funzionerà. L'impostazione 'NameScope' del menu di scelta rapida come suggerito dalla risposta accettata funziona solo se il menu di scelta rapida non è definito in un 'DataTemplate'. Ho risolto questo problema utilizzando lo {x:Reference} Markup-Extension che è simile al binding 'ElementName' ma risolve il binding in modo diverso, ignorando l'albero visivo. Ritengo che questo sia molto più leggibile rispetto all'utilizzo di "PlacementTarget". Ecco un esempio:

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

Secondo MSDN documentazione

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.

qualunque cosa significhi ... funziona per me, però.

+4

In realtà il suo commento più utile qui –

+1

Questo funziona davvero meglio, ho provato tutte le risposte e questo è corretto per .Net 4.5.2 di WPF. Grazie @ Marc –

Problemi correlati