ElementName ligação de MenuItem em ContextMenu
-
06-07-2019 - |
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?
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
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;
}