Pergunta

Tem mais alguém reparou que ligações com ElementName não resolverem corretamente para objetos MenuItem que estão contidos dentro de objetos ContextMenu? Confira este exemplo:

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

Todas as ligações trabalhar great, exceto para as ligações contidas dentro do ContextMenu. Eles imprimir um erro para a janela de saída durante a execução.

Qualquer um sabe de nenhum arounds trabalho? O que está acontecendo aqui?

Foi útil?

Solução

Eu encontrei uma solução muito mais simples.

No código por trás para o UserControl:

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

Outras dicas

Como foi dito por outros, o 'ContextMenu' não está contido na árvore visual e um 'ElementName' obrigatório não vai funcionar. Configuração do menu de contexto 'NameScope', como sugerido pela resposta aceita só funciona se o menu de contexto não é definido em um 'DataTemplate'. Eu resolvi isso usando o {x: Reference} Markup-Extension que é semelhante ao 'ElementName' vinculativo, mas resolve a ligação de forma diferente, ignorando a árvore visual. Eu considero que isso seja muito mais legível do que usando 'PlacementTarget'. Aqui está um exemplo:

<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 acordo com o MSDN-documentação

x: Reference é uma construção definida em XAML 2009. Em WPF, você pode usar XAML 2009 recursos, mas apenas para XAML que não é WPF marcação-compilado. Markup-compilado XAML eo formulário BAML do XAML atualmente não apoiar as palavras-chave de linguagem XAML 2009 e recursos.

O que isso significa ... trabalha para mim, no entanto.

Aqui está uma outra solução alternativa somente XAML. (Isto também supõe que você quer o que está dentro do DataContext , por exemplo, você está MVVMing lo)

uma Opção, quando o elemento principal da ContextMenu não está em DataTemplate :

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

Isto trabalharia para a pergunta do OP. Isso não vai funcionar se você estiver dentro de um DataTemplate . Nestes casos, o DataContext é muitas vezes um dos muitos em uma coleção, e ICommand que deseja vincular a uma propriedade irmão da coleção dentro do mesmo ViewModel (o DataContext da janela, por exemplo).

Nestes casos, você pode tirar vantagem do tag para reter temporariamente o pai DataContext que contém tanto a recolha e seu ICommand:

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

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

Os menus de contexto são complicados para ligar contra. Eles existem fora da árvore visual de seu controle, portanto, eles não podem encontrar o seu nome do elemento.

Tente configurar o datacontext do seu menu de contexto ao seu alvo colocação. Você tem que usar RelativeSource.

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

Depois de experimentar um pouco, descobri um trabalho em torno de:

Faça nível superior Window / UserControl implementar INameScope e conjunto NameScope de ContextMenu para o controle de nível 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
}

Isso permite que o menu de contexto para encontrar itens nomeados dentro do Window. Quaisquer outras opções?

Eu não tenho certeza por que recorrer a truques de mágica apenas para evitar uma linha de código dentro do eventhandler para o clique do mouse você já lidar com:

    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 em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top