2009-03-19 125 views
17

タブページにあるusercontrolのコンテキストメニューでコマンドをバインドするときに問題があります。 初めてメニューを使用するとき(タブを右クリック)、それはうまくいきますが、タブを切り替えると、コマンドは最初に使用されたデータバインドされたインスタンスを使用します。WPFコンテキストメニューがデータバインドされた右のアイテムにバインドされない

私は期待どおりに動作するユーザーコントロールにコマンドにバインドされているボタンを配置した場合...

は、誰かが私が間違ってやっているものを私に教えてくださいことはできますか?

これは、問題を公開し、テストプロジェクトです:

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> 

Compa nyViewModel.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; } 
} 

答えて

22

ここで覚えておくべき重要なこと私はですnusはビジュアルツリーの一部ではありません。

したがって、バインディングに属するコントロールと同じソースを継承しません。これを処理する方法は、ContextMenu自体の配置ターゲットにバインドすることです。

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

こんにちはキャメロン:

これは、私はそれが唯一のXAMLを使用して動作させるためたソリューションです。あなたのテクニックは、私がここで説明した問題と何らかの形で関係していると思いますか?http://stackoverflow.com/questions/833607/wpf-why-do-contextmenu-items-work-for-listbox-but-not- itemscontrol ...私はコマンドに束縛しているわけではありませんが、それは関連する問題の疑いがあります。 –

+2

私はこの答えによって納得できません。コマンドバインディングは、(ビューモデルをバインドしなければならないことがわかっている)メニュー項目に対して機能します...問題は、タブの切り替えによってdatacontextが変更されたときにmenuitemsが再バインドしないことです。ビジュアルツリーの一部ではないために、最初はどのように動作しますか? – Schneider

+0

@Schneider:メニューのバインディングがうまくいかないとは言わなかった。ちょうどあなたが期待していたように、親からのデータコンテキストを継承しないということだけではない。 WPFバインディングエンジンは、メニューが最初に開かれたときにコンテキストを設定していて、タブが変更されたときに更新しないとします。 –

8

コンテキストメニュー項目にコマンドをバインドする最もクリーンな方法は、CommandReferenceというクラスを使用することです。 CodeplexのMVVMツールキット(WPF Futures)にあります。

XAMLは次のようになります。

<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はViewModelににRelayCommandです。この例では、ViewModelはコードビハインド内のビューのデータコンテキストにアタッチされています。

注:このXAMLは作業中のプロジェクトからコピーされ、図のために簡略化されています。タイプミスやその他の軽微なエラーが発生することがあります。

+1

これは、CanExecuteの代理人、Cyber​​MonkでRelayCommandで試しましたか? Executeメソッドが正しい値を渡しても、CommandReferenceがCanExecuteのパラメータにnullを渡すことがわかりました。今すぐ使用するのを止めてしまいます。 –

+0

これはうまくいくかもしれませんが、なぜ誰かがその必要性を説明できるのでしょうか?なぜContextMenus上のバインディングは一度しか動作しませんか? – Schneider

+0

私はこの作品を確認することができます...説明は歓迎です:) – Schneider

0

は、私は深いコントロールテンプレート内のコンテキストメニューからバインドするときに非常に便利Tagプロパティを使用して、この方法を見つけましたコンテキストメニューが開かれたコントロールで使用可能なデータコンテキストに変換します。コンテキストメニューは、クリックされたコントロールに "PlacementTarget"でアクセスできます。クリックされたコントロールのTagプロパティが目的のdatacontextにバインドされている場合、コンテキストメニュー内の "PlacementTarget.Tag"へのバインディングは、そのdatacontextに直接スラングショットされます。

1

私は別の解決策を好む。 コンテキストメニューローダーイベントを追加します。

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

イベント内にデータコンテキストを割り当てます。

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

最近、ListBoxにあるContextMenuで同じ問題が発生しました。私はコードビハインドなしでMVVMの方法でコマンドをバインドしようとしました。私はついに諦め、友人に助けを頼んだ。彼はややねじれているが簡潔な解決策を見つけた。 彼は、ContextMenuのDataContextにListBoxを渡して、ListBoxのDataContextにアクセスして、ビューモデルでコマンドを探します。これまで私が見てきた最も簡単な解決策です。カスタムコードもタグもなく、純粋なXAMLとMVVMだけです。

Githubに完全に動作するサンプルを投稿しました。ここに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> 
0

これは既に古い投稿であることは知っていますが、私はそれを行う方法を探している人のために別のソリューションを追加したいと思います。

私は何か他のことをやろうとしていたので、私の場合は同じソリューションを作ることができませんでした。マウスのクリックでコンテキストメニューを開きます(サブメニュー付きのツールバーのように)私のモデルに。イベントトリガーを使用していたので、PlacementTargetオブジェクトはnullでした。

<!-- 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> 
関連する問題