web-dev-qa-db-fra.com

Liaison ElementName à partir de MenuItem dans ContextMenu

Est-ce que quelqu'un d'autre a remarqué que les liaisons avec ElementName ne se résolvent pas correctement pour les objets MenuItem qui sont contenus dans les objets ContextMenu? Découvrez cet exemple:

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

Toutes les liaisons fonctionnent très bien à l'exception des liaisons contenues dans le ContextMenu. Ils impriment une erreur dans la fenêtre de sortie pendant l'exécution.

Quelqu'un connaît-il des solutions de rechange? Que se passe t-il ici?

65
Josh G

J'ai trouvé une solution beaucoup plus simple.

Dans le code derrière pour UserControl:

NameScope.SetNameScope(contextMenu, NameScope.GetNameScope(this));
53
Josh G

Comme dit par d'autres, le "ContextMenu" n'est pas contenu dans l'arborescence visuelle et une liaison "ElementName" ne fonctionnera pas. La définition du "NameScope" du menu contextuel comme suggéré par la réponse acceptée ne fonctionne que si le menu contextuel n'est pas défini dans un "DataTemplate". J'ai résolu ce problème en utilisant {x: Reference} Markup-Extension qui est similaire à la liaison 'ElementName' mais résout la liaison différemment, en contournant l'arborescence visuelle. Je considère que c'est beaucoup plus lisible que d'utiliser "PlacementTarget". Voici un exemple:

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

Selon la documentation MSDN

x: Reference est une construction définie dans XAML 2009. Dans WPF, vous pouvez utiliser les fonctionnalités XAML 2009, mais uniquement pour XAML qui n'est pas compilé par balisage WPF. XAML compilé par balisage et la forme BAML de XAML ne prennent actuellement pas en charge les mots-clés et les fonctionnalités du langage XAML 2009.

quoi que cela signifie ... Fonctionne pour moi, cependant.

22
Marc

Voici une autre solution de contournement uniquement pour xaml. (Cela suppose également que vous voulez ce qu'il y a dans le DataContext , par exemple, vous êtes MVVMing it)

Première option, où l'élément parent du ContextMenu n'est pas dans un DataTemplate :

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

Cela fonctionnerait pour la question de OP. Cela ne fonctionnera pas si vous êtes à l'intérieur d'un DataTemplate . Dans ces cas, le DataContext est souvent l'un des nombreux éléments d'une collection, et le ICommand auquel vous souhaitez vous lier est une propriété sœur de la collection dans le même ViewModel (le DataContext de la fenêtre, par exemple).

Dans ces cas, vous pouvez profiter de la balise pour maintenir temporairement le parent DataContext qui contient à la fois la collection ET votre ICommand:

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

et dans le 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>
19
Will

Les menus contextuels sont difficiles à lier. Ils existent en dehors de l'arborescence visuelle de votre contrôle, ils ne peuvent donc pas trouver le nom de votre élément.

Essayez de définir le contexte de données de votre menu contextuel sur sa cible de placement. Vous devez utiliser RelativeSource.

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

Après avoir expérimenté un peu, j'ai découvert un travail autour:

Faites en sorte que le niveau supérieur Window/UserControl implémente INameScope et définissez NameScope de ContextMenu au contrôle de niveau supérieur.

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
}

Cela permet au menu contextuel de trouver des éléments nommés à l'intérieur de Window. D'autres options?

4
Josh G

Je ne sais pas pourquoi recourir à des tours de magie juste pour éviter une ligne de code dans le gestionnaire d'événements pour le clic de souris que vous gérez déjà:

    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;
    }
1
Marino Šimić