2009-06-18 12 views
59

¿Alguien más ha notado que las vinculaciones con ElementName no se resuelven correctamente para los objetos MenuItem que están dentro de los objetos ContextMenu? Echa un vistazo a este ejemplo:Enlace de nombre de elemento de MenuItem en ContextMenu

<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 a excepción de los enlaces contenidos en el ContextMenu. Imprimen un error en la ventana de Salida durante el tiempo de ejecución.

¿Alguien sabe de cualquier problema? ¿Que está pasando aqui?

+0

El problema obviamente tiene algo que ver con los nombrescopios ... –

+0

¿ContextMenus define su propio namescope de forma predeterminada? –

Respuesta

51

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

En el código subyacente para el control de usuario:

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

Esto parece no funcionar más en el marco 4.0. –

+0

Lo siento, no lo he probado en 4.0 –

+5

En realidad, me funciona en 4.0. – esylvestre

4

Después de experimentar un poco, descubrí un trabajo en torno a:

Hacer alto nivel Window/UserControl implemento INameScope y establecer NameScope de ContextMenu para el 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ú de contexto para encontrar elementos con nombre dentro de la Window. ¿Alguna otra opción?

5

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

Intente establecer 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}}"> ... 
+0

Al establecer el DataContext en el PlacementTarget ¿se efectuarán los enlaces de ElementName? Creo que DataContext solo se usa para enlaces que no tienen un conjunto de propiedades Source, RelativeSource o ElementName. –

+0

Establecer una propiedad ElementName solo funcionará si el administrador de diseño puede encontrar el elemento asociado navegando por el árbol visual. Los menús contextuales no existen dentro del árbol visual del control al que se agregan. Debe establecer el contexto de datos del menú contextual para que el administrador de diseño pueda navegar por el árbol visual de su destino de ubicación para encontrar el elemento asociado. – Josh

+0

Agregar el DataContext al ejemplo anterior no solucionó el problema. Todavía tengo el siguiente error en la ventana de resultados: "System.Windows.Data Error: 4: no se puede encontrar el origen del enlace con la referencia 'ElementName = window'. BindingExpression: (sin ruta); DataItem = null; el elemento de destino es 'MenuItem '(Name =' menuItem '); propiedad de destino es' Tag '(tipo' Object ') " –

19

Aquí hay otra solución xaml-solamente. (Esto también supone que desea lo que hay dentro de la DataContext, por ejemplo, estás MVVMing es)

La opción uno, donde el elemento principal de la 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 se encuentra dentro de un DataTemplate. En estos casos, el DataContext es a menudo uno de los muchos en una colección, y la ICommand que deseen unirse a es una propiedad de los hermanos de la colección dentro del mismo modelo de vista (el DataContext de la ventana, por ejemplo).

En estos casos, se puede aprovechar el Tag para mantener temporalmente el padre DataContext que contiene tanto la recogida y el 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> 
+0

Creo que el punto relevante que está haciendo aquí es que puede usar etiquetas y enlaces de fuente relativos para obtener datos en otro lugar en el árbol visual. –

+0

Esto no está realmente relacionado con MVVM. Solo uso los enlaces de ElementName cuando intento vincular dos controles relacionados con vistas fuera de la VM. Esta es una buena solución para vincular los elementos del menú contextual a los comandos en una máquina virtual. Una buena alternativa es usar un comando enrutado que se vincule con la máquina virtual. Un buen ejemplo de esto es la clase CommandSink de Josh Smith. –

1

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 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; 
    } 
+0

Hacerlo de esta manera no permite que el elemento del menú se deshabilite automáticamente de acuerdo con el comando encuadernado. Por lo tanto, mientras funciona para la ejecución, debería agregar más código para también desactivar/habilitar el elemento del menú en consecuencia cuando se carga. No es que esto sea malo, solo trae malos recuerdos de los espaguetis de códigos de interfaz de usuario WinFoms para mucha gente. – jpierson

16

Como dijeron otros, el 'ContextMenu' no está contenido en el árbol visual y un enlace 'ElementName' no funcionará. Configurar el 'NameScope' del menú contextual como lo sugiere la respuesta aceptada solo funciona si el menú contextual no está definido en una 'DataTemplate'. Lo he resuelto utilizando el {x:Reference} Markup-Extension que es similar al enlace 'ElementName' pero resuelve el enlace de manera diferente, pasando por alto el árbol visual. Considero que esto es mucho más legible que usar 'PlacementTarget'. Aquí está 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 is a construct defined in XAML 2009. In WPF, you can use XAML 2009 features, but only for XAML that is not WPF markup-compiled. Markup-compiled XAML and the BAML form of XAML do not currently support the XAML 2009 language keywords and features.

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

+4

En realidad, es el comentario más útil aquí –

+1

Esto realmente funciona mejor, probé todas las respuestas y esta es la correcta para WPF .Net 4.5.2. Gracias @Marc –

Cuestiones relacionadas