ElementName Binding from MenuItem in ContextMenu


¿Alguien más ha notado que los enlaces con ElementName no se resuelven correctamente para los objetos MenuItem que están contenidos dentro de los objetos ContextMenu? Echa un vistazo a este ejemplo:

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

Todos los enlaces funcionan muy bien excepto los enlaces contenidos dentro del ContextMenu. Imprimen un error en la ventana de salida durante el tiempo de ejecución.

¿Alguien sabe de alguna solución alternativa? ¿Qué está pasando aquí?

Author: Josh G, 2009-06-18

6 answers

Encontré una solución mucho más simple.

En el código detrás del UserControl:

NameScope.SetNameScope(contextMenu, NameScope.GetNameScope(this));
 52
Author: Josh G,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-06-30 20:54:54

Aquí hay otra solución alternativa solo para xaml. (Esto también asume que quieres lo que está dentro del DataContext, por ejemplo, estás MVVMing it)

Opción uno, donde el elemento padre del ContextMenu no está en una DataTemplate :

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

Esto funcionaría para la pregunta de OP. Esto no funcionará si está dentro de una DataTemplate . En estos casos, el DataContext es a menudo uno de los muchos en una colección, y el ICommand desea bind to es una propiedad hermana de la colección dentro del mismo ViewModel (el DataContext de la Ventana, por ejemplo).

En estos casos, usted puede tomar ventaja de la Etiqueta para mantener temporalmente el padre DataContext que contiene tanto la colección COMO su ICommand:

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

Y en el 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
Author: Will,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-16 18:29:58

Como han dicho otros, el 'ContextMenu' no está contenido en el árbol visual y un enlace 'ElementName' no funcionará. Establecer el 'NameScope' del menú contextual como sugiere la respuesta aceptada solo funciona si el menú contextual no está definido en una 'DataTemplate'. He resuelto esto usando el {x:Reference} Markup-Extension que es similar al enlace 'ElementName' pero resuelve el enlace de manera diferente, evitando el árbol visual. Considero que esto es mucho más legible que usar "PlacementTarget". He aquí un ejemplo:

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

Según la documentación de MSDN

X:Reference es una construcción definida en XAML 2009. En WPF, puede utilizar Características de XAML 2009, pero solo para XAML que no está compilado con marcado WPF. Markup-compiled XAML and the BAML form of XAML do not currently admite las palabras clave y características del idioma XAML 2009.

Lo que sea que eso signifique... Pero a mí me funciona.

 17
Author: Marc,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-16 09:47:46

Los menús contextuales son difíciles de enlazar. Existen fuera del árbol visual de su control, por lo que no pueden encontrar el nombre de su elemento.

Intente establecer el datacontext de su menú contextual en su destino de ubicación. Tienes que usar RelativeSource.

<ContextMenu 
   DataContext="{Binding PlacementTarget, RelativeSource={RelativeSource Self}}"> ...
 5
Author: Josh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-07-25 10:01:33

Después de experimentar un poco, descubrí un trabajo alrededor:

Hacer nivel superior Window/UserControl implementar INameScope y establecer NameScope de ContextMenu al control de nivel superior.

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
}

Esto permite que el menú contextual encuentre elementos nombrados dentro de Window. ¿Alguna otra opción?

 4
Author: Josh G,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-06-18 18:08:50

No estoy seguro de por qué recurrir a trucos de magia solo para evitar una línea de código dentro del eventhandler para el clic del ratón que ya maneja:

    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
Author: Marino Šimić,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-06-18 12:36:56