Question

Quelqu'un a-t-il remarqué que les liaisons avec ElementName ne sont pas résolues correctement pour les objets MenuItem contenus dans des 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 parfaitement, à l'exception des liaisons contenues dans le ContextMenu. Ils impriment une erreur dans la fenêtre de sortie lors de l'exécution.

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

Était-ce utile?

La solution

J'ai trouvé une solution beaucoup plus simple.

Dans le code derrière UserControl:

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

Autres conseils

Comme l'ont dit d'autres personnes, le "ContextMenu" n'est pas contenu dans l'arborescence visuelle et une liaison "ElementName" ne fonctionnera pas. Le paramétrage de 'NameScope' dans le 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 le {x: Référence} Markup-Extension . qui est similaire à la liaison 'NomElément' mais résout la liaison différemment, en contournant l'arborescence visuelle. Je considère que cela 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   Fonctionnalités XAML 2009, mais uniquement pour XAML qui n’est pas compilé en balises WPF.   XAML compilé par balisage et la forme BAML de XAML ne sont pas actuellement disponibles.   prendre en charge les mots-clés et fonctionnalités de la langue XAML 2009.

peu importe ce que cela signifie ... Ça marche pour moi, cependant.

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

Première option, où l'élément parent du ContextMenu ne se trouve 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, DataContext est souvent l’un des nombreux éléments d’une collection, et la ICommand à laquelle vous souhaitez lier est une propriété sœur de la collection située dans le même ViewModel (le même objet). DataContext de la fenêtre, par exemple).

Dans ce cas, vous pouvez tirer parti de la balise pour conserver temporairement le DataContext parent qui contient à la fois la collection ET votre commande IC:

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>

Les menus contextuels sont difficiles à lier. Ils existent en dehors de l'arborescence visuelle de votre contrôle et 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}}"> ...

Après quelques expériences, j'ai découvert un moyen de contourner le problème:

Créez la fenêtre / de niveau supérieur> UserControl implémentez INameScope et définissez NameScope sur 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
}

Ceci permet au menu contextuel de rechercher des éléments nommés à l'intérieur de la Fenêtre . Toutes les autres options?

Je ne suis pas sûr de savoir pourquoi recourir aux tours de magie pour éviter une ligne de code dans le gestionnaire d'événements pour le clic de souris que vous avez déjà manipulé:

    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;
    }
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top