Pergunta

Eu tenho um problema ao ligar um comando em um menu de contexto em um usercontrol que está em uma página da guia. A primeira vez que usar o menu (botão direito do mouse na guia) ele funciona muito bem, mas se eu mudar tab o comando usará a instância de ligação de dados que foi usado pela primeira vez.

Se eu colocar um botão que está ligado ao comando no usercontrol ele funciona como esperado ...

Alguém por favor pode me dizer o que estou fazendo de errado ??

Este é um projeto de teste que expõe o problema:

App.xaml.cs:

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        CompanyViewModel model = new CompanyViewModel();
        Window1 window = new Window1();
        window.DataContext = model;
        window.Show();
    }
}

Window1.xaml:

<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:vw="clr-namespace:WpfApplication1"
Title="Window1" Height="300" Width="300">

  <Window.Resources>
    <DataTemplate x:Key="HeaderTemplate">
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="{Binding Path=Name}" />
        </StackPanel>
    </DataTemplate>
    <DataTemplate DataType="{x:Type vw:PersonViewModel}">
        <vw:UserControl1/>
    </DataTemplate>

</Window.Resources>
<Grid>
    <TabControl ItemsSource="{Binding Path=Persons}" 
                ItemTemplate="{StaticResource HeaderTemplate}"
                IsSynchronizedWithCurrentItem="True" />
</Grid>
</Window>

UserControl1.xaml:

<UserControl x:Class="WpfApplication1.UserControl1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    MinWidth="200">
    <UserControl.ContextMenu>
        <ContextMenu >
            <MenuItem Header="Change" Command="{Binding Path=ChangeCommand}"/>
        </ContextMenu>
    </UserControl.ContextMenu>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <Label Grid.Column="0">The name:</Label>
        <TextBox Grid.Column="1" Text="{Binding Path=Name, UpdateSourceTrigger=PropertyChanged}" />
    </Grid>
</UserControl>

CompanyViewModel.cs:

public class CompanyViewModel
{
    public ObservableCollection<PersonViewModel> Persons { get; set; }
    public CompanyViewModel()
    {
        Persons = new ObservableCollection<PersonViewModel>();
        Persons.Add(new PersonViewModel(new Person { Name = "Kalle" }));
        Persons.Add(new PersonViewModel(new Person { Name = "Nisse" }));
        Persons.Add(new PersonViewModel(new Person { Name = "Jocke" }));
    }
}

PersonViewModel.cs:

public class PersonViewModel : INotifyPropertyChanged
{
    Person _person;
    TestCommand _testCommand;

    public PersonViewModel(Person person)
    {
        _person = person;
        _testCommand = new TestCommand(this);
    }
    public ICommand ChangeCommand 
    {
        get
        {
            return _testCommand;
        }
    }
    public string Name 
    {
        get
        {
            return _person.Name;
        }
        set
        {
            if (value == _person.Name)
                return;
            _person.Name = value;
            OnPropertyChanged("Name");
        }
    }
    void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = this.PropertyChanged;
        if (handler != null)
        {
            var e = new PropertyChangedEventArgs(propertyName);
            handler(this, e);
        }
    }
    public event PropertyChangedEventHandler PropertyChanged;
}

TestCommand.cs:

public class TestCommand : ICommand
{
    PersonViewModel _person;
    public event EventHandler CanExecuteChanged;

    public TestCommand(PersonViewModel person)
    {
        _person = person;
    }
    public bool CanExecute(object parameter)
    {
        return true;
    }
    public void Execute(object parameter)
    {
        _person.Name = "Changed by command";
    }
}

Person.cs:

public class Person
{
    public string Name { get; set; }
}
Foi útil?

Solução

A principal coisa a lembrar aqui é menus de contexto não são parte da árvore visual.

Por isso, eles não herdam a mesma fonte que o controle a que pertencem para a ligação. A maneira de lidar com isso é para se ligar ao alvo colocação do próprio ContextMenu.

<MenuItem Header="Change" Command="{Binding 
    Path=PlacementTarget.ChangeCommand, 
    RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}"
/>

Outras dicas

A maneira mais limpa que eu encontrei para comandos se ligam a itens de menu de contexto envolve o uso de uma classe chamada CommandReference. Você pode encontrá-lo no kit de ferramentas MVVM no Codeplex em WPF Futures .

O XAML pode ter esta aparência:

<UserControl x:Class="View.MyView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:vm="clr-namespace:ViewModel;assembly=MyViewModel"
                xmlns:mvvm="clr-namespace:ViewModelHelper;assembly=ViewModelHelper"
           <UserControl.Resources>
                <mvvm:CommandReference x:Key="MyCustomCommandReference" Command="{Binding MyCustomCommand}" />

                <ContextMenu x:Key="ItemContextMenu">
                    <MenuItem Header="Plate">
                        <MenuItem Header="Inspect Now" Command="{StaticResource MyCustomCommandReference}"
                                CommandParameter="{Binding}">
                        </MenuItem>
                    </MenuItem>
               </ContextMenu>
    </UserControl.Resources>

MyCustomCommand é um RelayCommand no ViewModel. Neste exemplo, o ViewModel foi anexado ao datacontext da vista no código-behind.

Nota: este XAML foi copiado de um projeto de trabalho e simplificado para ilustração. Pode haver erros de digitação ou outros erros menores.

Eu tive o mesmo problema recentemente com um ContextMenu localizado em um ListBox. Tentei ligar um comando a maneira MVVM sem qualquer código. Eu finalmente desistiu e eu perguntei a um amigo para sua ajuda. Ele encontrou uma solução ligeiramente torcido mas conciso. Ele está passando a caixa de listagem no DataContext do ContextMenu e, em seguida, encontrar o comando no modelo de vista, acessando o DataContext do ListBox. Esta é a solução mais simples que eu tenho visto até agora. Nenhum código personalizado, nenhum Tag, apenas pura XAML e MVVM.

Eu postei uma amostra totalmente trabalhando em Github . Aqui está um trecho do XAML.

<Window x:Class="WpfListContextMenu.MainWindow" 
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Title="MainWindow" Height="350" Width="268">
  <Grid>
    <DockPanel>
      <ListBox x:Name="listBox" DockPanel.Dock="Top" ItemsSource="{Binding Items}" DisplayMemberPath="Name"
               SelectionMode="Extended">
        <ListBox.ContextMenu>
          <ContextMenu DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource Self}}">
            <MenuItem Header="Show Selected" Command="{Binding Path=DataContext.ShowSelectedCommand}"
                      CommandParameter="{Binding Path=SelectedItems}" />
          </ContextMenu>
        </ListBox.ContextMenu>
      </ListBox>
    </DockPanel>
  </Grid>
</Window>

Eu prefiro outra solução. Adicionar contexto evento loader menu.

<ContextMenu Loaded="ContextMenu_Loaded"> 
    <MenuItem Header="Change" Command="{Binding Path=ChangeCommand}"/> 
</ContextMenu> 

contexto de dados Atribuir dentro do evento.

private void ContextMenu_Loaded(object sender, RoutedEventArgs e)
{
    (sender as ContextMenu).DataContext = this; //assignment can be replaced with desired data context
}

Eu encontrei este método usando a propriedade Tag muito útil quando a ligação a partir de um menu de contexto profundamente dentro de um modelo de controle:

http: //blog.jtango. net / ligação-a-um-menuitem-in-a-menu de contexto do WPF-

Isso torna possível para ligar a qualquer disponíveis datacontext ao controle que o menu de contexto foi aberto a partir. O menu de contexto pode acessar o controle clicado através de "PlacementTarget". Se a propriedade Tag do controle clicado está vinculado a um datacontext desejado, a ligação a "PlacementTarget.Tag" de dentro do menu de contexto será estilingue-lo diretamente para que datacontext.

Eu sei que isso já é um post antigo, mas eu gostaria de acrescentar uma outra solução para aqueles quem está procurando maneiras diferentes de fazê-lo.

Eu não poderia fazer a mesma solução para o trabalho no meu caso, desde que eu estava tentando fazer outra coisa: abra o menu de contexto com um clique do mouse (como uma barra de ferramentas com um submenu ligado a ele) e também se ligam comandos para meu modelo. Desde que eu estava usando um disparador do evento, o objeto PlacementTarget foi nula.

Esta é a solução que eu encontrei para fazer isso só funcionam usando XAML:

<!-- This is an example with a button, but could be other control -->
<Button>
  <...>

  <!-- This opens the context menu and binds the data context to it -->
  <Button.Triggers>
    <EventTrigger RoutedEvent="Button.Click">
      <EventTrigger.Actions>
        <BeginStoryboard>
          <Storyboard>
            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="ContextMenu.DataContext">
              <DiscreteObjectKeyFrame KeyTime="0:0:0" Value="{Binding}"/>
            </ObjectAnimationUsingKeyFrames>
            <BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="ContextMenu.IsOpen">
              <DiscreteBooleanKeyFrame KeyTime="0:0:0" Value="True"/>
            </BooleanAnimationUsingKeyFrames>
          </Storyboard>
        </BeginStoryboard>
      </EventTrigger.Actions>
    </EventTrigger>
  </Button.Triggers>

  <!-- Here it goes the context menu -->
  <Button.ContextMenu>
    <ContextMenu>
      <MenuItem Header="Item 1" Command="{Binding MyCommand1}"/>
      <MenuItem Header="Item 2" Command="{Binding MyCommand2}"/>
    </ContextMenu>
  </Button.ContextMenu>

</Button>
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top