Pregunta

¿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 ? Mira esta muestra:

<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 en el menú contextual. Imprimen un error en la ventana Salida durante el tiempo de ejecución.

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

¿Fue útil?

Solución

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

En el código detrás del UserControl:

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

Otros consejos

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 según lo sugerido por la respuesta aceptada solo funciona si el menú contextual no está definido en una 'Plantilla de datos'. He resuelto esto usando el {x: Reference} Markup-Extension que es similar al enlace 'ElementName' pero resuelve el enlace de manera diferente, sin pasar por el árbol visual. Considero que esto es mucho más legible que usar 'PlacementTarget'. Aquí hay 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>

De acuerdo con la documentación de MSDN

  

x: Reference es una construcción definida en XAML 2009. En WPF, puede usar   Características de XAML 2009, pero solo para XAML que no está compilado con marcado WPF.   XAML compilado con marcado y la forma BAML de XAML actualmente no   admite las palabras clave y características del lenguaje XAML 2009.

lo que sea que eso signifique ... funciona para mí, sin embargo.

Aquí hay otra solución alternativa solo para xaml. (Esto también supone que desea lo que hay dentro del DataContext , por ejemplo, usted es MVVMing )

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 muchos en una colección, y el ICommand al que desea enlazar es una propiedad hermana de la colección dentro del mismo ViewModel (el DataContext de la ventana, digamos).

En estos casos, puede aprovechar la Etiqueta para mantener temporalmente el DataContext principal que contiene la colección Y 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>

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

Intente configurar el contexto de datos de su menú contextual en su destino de ubicación. Tienes que usar RelativeSource.

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

Después de experimentar un poco, descubrí una solución:

Haga que la ventana de nivel superior / UserControl implemente INameScope y establezca NameScope en 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 con nombre dentro de la Ventana . ¿Alguna otra opción?

No estoy seguro de por qué recurrir a trucos de magia solo para evitar una línea de código dentro del controlador de eventos para el clic del mouse que ya manejas:

    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;
    }
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top