WPF: vinculando um ContextMenu a um comando MVVM

Digamos que eu tenha uma janela com uma propriedade retornando um comando (na verdade, é um UserControl com um comando em uma class ViewModel, mas vamos manter as coisas o mais simples possível para reproduzir o problema).

Os seguintes trabalhos:

     

Mas o seguinte não funciona.

          

A mensagem de erro que recebo é

Erro System.Windows.Data: 4: Não é possível localizar a origem para binding com referência ‘ElementName = myWindow’. BindingExpression: caminho = MyCommand; DataItem = nulo; elemento de destino é ‘MenuItem’ (Name = ”); propriedade de destino é ‘Comando’ (digite ‘ICommand’)

Por quê? E como eu corrijo isso? Usar o DataContext não é uma opção, pois esse problema ocorre na tree visual em que o DataContext já contém os dados reais que estão sendo exibidos. Eu já tentei usar {RelativeSource FindAncestor, ...} , mas isso gera uma mensagem de erro similar.

O problema é que o ContextMenu não está na tree visual, então você basicamente tem que dizer ao menu Contexto sobre qual contexto de dados usar.

Confira este blogpost com uma solução muito legal de Thomas Levesque.

Ele cria um Proxy de class que herda o Freezable e declara uma propriedade de dependência de dados.

 public class BindingProxy : Freezable { protected override Freezable CreateInstanceCore() { return new BindingProxy(); } public object Data { get { return (object)GetValue(DataProperty); } set { SetValue(DataProperty, value); } } public static readonly DependencyProperty DataProperty = DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null)); } 

Então, ele pode ser declarado no XAML (em um lugar na tree visual onde o DataContext correto é conhecido):

    

E usado no menu de contexto fora da tree visual:

    

Viva para web.archive.org ! Aqui está a postagem do blog ausente :

Vinculando a um MenuItem em um menu de contexto do WPF

Quarta-feira, 29 de outubro de 2008 – jtango18

Como um ContextMenu no WPF não existe na tree visual de sua página / janela / controle, a vinculação de dados pode ser um pouco complicada. Eu pesquisei alto e baixo na web por isso, e a resposta mais comum parece ser “apenas faça no código por trás”. ERRADO! Eu não entrei no maravilhoso mundo do XAML para voltar a fazer as coisas no código por trás.

Aqui está o meu exemplo para que permitirá ligar a uma string que existe como uma propriedade da sua janela.

 public partial class Window1 : Window { public Window1() { MyString = "Here is my string"; } public string MyString { get; set; } }  

A parte importante é o Tag no botão (embora você possa facilmente definir o DataContext do botão). Isso armazena uma referência à janela pai. O ContextMenu é capaz de acessar isso através da propriedade PlacementTarget. Você pode então passar este contexto através de seus itens de menu.

Eu admito que esta não é a solução mais elegante do mundo. No entanto, é melhor definir as coisas no código por trás. Se alguém tiver uma maneira ainda melhor de fazer isso, eu adoraria ouvir isso.

Eu descobri que não estava funcionando para mim devido ao item de menu ser nested, o que significa que eu tive que percorrer um “Parent” extra para encontrar o PlacementTarget.

Uma maneira melhor é encontrar o próprio ContextMenu como o RelativeSource e depois vincular-se ao destino de posicionamento dele. Além disso, como a tag é a própria janela e seu comando está no viewmodel, também é necessário ter o DataContext definido.

Acabei com algo parecido com isto

  ...         

O que isto significa é que se você acabar com um menu de contexto complicado com submenus, etc., você não precisa continuar adicionando “Pai” a cada nível de Comandos.

– EDIT –

Também surgiu essa alternativa para definir uma tag em cada ListBoxItem que se liga ao Window / Usercontrol. Eu acabei fazendo isso porque cada ListBoxItem era representado por seu próprio ViewModel, mas eu precisava dos comandos de menu para executar através do ViewModel de nível superior para o controle, mas passar sua lista ViewModel como um parâmetro.

   >  ...    

Veja este artigo de Justin Taylor para uma solução alternativa.

Atualizar
Infelizmente, o blog referenciado não está mais disponível. Eu tentei explicar o procedimento em outra resposta SO. Pode ser encontrado aqui .

Com base na resposta da HCL , foi isso que acabei usando:

  ...         

Se (como eu) você tem uma aversão a expressões de binding complexas, aqui está uma solução simples para esse problema. Essa abordagem ainda permite que você mantenha as declarações de comando limpas em seu XAML.

XAML:

     ... 

Código por trás:

 private void ContextMenu_ContextMenuOpening(object sender, ContextMenuEventArgs e) { foreach (var item in (sender as ContextMenu).Items) { if(item is MenuItem) { //set the command target to whatever you like here (item as MenuItem).CommandTarget = this; } } }