Domanda

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:

<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 i binding funzionano alla grande tranne i binding contenuti nel ContextMenu. Stampano un errore nella finestra Output durante il runtime.

Qualcuno sa di eventuali soluzioni? Cosa sta succedendo qui?

È stato utile?

Soluzione

Ho trovato una soluzione molto più semplice.

Nel codice dietro UserControl:

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

Altri suggerimenti

Come detto da altri, il 'ContextMenu' non è contenuto nella struttura ad albero visivo e un'associazione 'ElementName' non funzionerà. L'impostazione del "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 {x: Reference} Markup-Extension che è simile all'associazione "ElementName" ma risolve l'associazione in modo diverso, ignorando l'albero visivo. Ritengo che ciò sia molto più leggibile rispetto all'uso 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 la documentazione MSDN

  

x: Il riferimento è un costrutto definito in XAML 2009. In WPF, è possibile utilizzare   Funzionalità di XAML 2009, ma solo per XAML che non è compilato con markup WPF.   XAML compilato con markup e il modulo BAML di XAML attualmente non lo sono   supportare le parole chiave e le funzioni della lingua XAML 2009.

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

Ecco un'altra soluzione solo per xaml. (Ciò presuppone anche che desideri ciò che è all'interno del DataContext , ad esempio, stai MVVMing )

Opzione 1, in cui l'elemento genitore del ContextMenu non si trova in un DataTemplate :

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

Questo funzionerebbe per la domanda di OP. Questo non funzionerà se ti trovi all'interno di un DataTemplate . In questi casi, il DataContext è spesso uno dei tanti in una raccolta e il ICommand che si desidera associare è una proprietà di pari livello della raccolta all'interno dello stesso ViewModel (il DataContext della finestra, diciamo).

In questi casi, puoi sfruttare il Tag per conservare temporaneamente il DataContext principale che contiene sia la raccolta che il tuo ICommand:

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

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

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

Prova a impostare il datacontesto del menu contestuale sulla destinazione del posizionamento. Devi usare RelativeSource.

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

Dopo aver sperimentato un po ', ho scoperto un modo per aggirare:

Crea la finestra di livello superiore / UserControl implementa INameScope e imposta NameScope su 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
}

Ciò consente al menu di scelta rapida di trovare gli elementi con nome all'interno della Window . Altre opzioni?

Non sono sicuro del motivo per cui ricorrere a trucchi magici solo per evitare una riga di codice all'interno del gestore eventi per il clic del mouse che gestisci già:

    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;
    }
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top