2009-03-19 5 views
17

탭 페이지에있는 usercontrol의 상황에 맞는 메뉴에서 명령을 바인딩 할 때 문제가 있습니다. 처음 메뉴를 사용하면 (탭을 마우스 오른쪽 버튼으로 클릭) 탭이 잘 작동하지만 탭을 전환하면 명령이 처음 사용 된 databound 인스턴스를 사용하게됩니다.WPF 컨텍스트 메뉴가 데이터 바인딩 된 항목 오른쪽에 바인딩되지 않습니다.

내가 예상대로 작동 UserControl을의 명령에 바인딩 버튼 ...

누군가가 내가 잘못는지 말해 주실 래요을 넣으면?

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가 변경 될 때 메뉴 항목이 다시 바인딩되지 않는다는 것입니다. 비주얼 트리의 일부가 아니기 때문에 처음으로 어떻게 작동합니까? – 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는 뷰 모델에 RelayCommand이다. 이 예제에서 ViewModel은 코드 숨김의 뷰의 datacontext에 연결되었습니다.

참고 :이 XAML은 작업중인 프로젝트에서 복사 한 것이며 단순화를 위해 사용되었습니다. 오타 또는 기타 사소한 오류가있을 수 있습니다.

+1

CanExecute 대리인 인 CyberMonk와 함께 RelayCommand를 사용해 보셨습니까? Execute 메서드가 올바른 값을 전달했지만 CommandReference가 CanExecute 매개 변수에 null을 전달한다는 것을 발견했습니다. 지금 사용하지 못하게 막았습니다. –

+0

좋아,이 작동 할 수 있지만 아무도 왜 필요한 설명 할 수 있습니까? ContextMenus의 바인딩이 한 번만 실행되는 이유는 무엇입니까? – Schneider

+0

이 작품을 확인할 수 있습니다 ... 설명 환영합니다 :) – Schneider

0

나는 깊은 컨트롤 템플릿 내부의 상황에 맞는 메뉴에서 바인딩 할 때 매우 유용 Tag 속성을 사용하여이 방법을 발견 컨텍스트 메뉴가 열리는 컨트롤에서 사용할 수있는 모든 datacontext. 컨텍스트 메뉴는 "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 방식으로 명령을 바인딩하려고했습니다. 드디어 포기하고 친구에게 도움을 청했습니다. 그는 약간 비틀면서도 간결한 해결책을 발견했습니다. 그는 ListBox를 ContextMenu의 DataContext에 전달하고 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

나는 이미 이전 게시물 인 것을 알고 있지만, 다른 방법을 찾고있는 사람들을 위해 다른 솔루션을 추가하고 싶습니다.

뭔가 다른 일을하려고 할 때부터 동일한 해결책을 내 케이스에서 만들 수 없었습니다 : 마우스 클릭으로 컨텍스트 메뉴 열기 (하위 메뉴가 첨부 된 툴바처럼) 및 명령 바인딩 내 모델에. Event Trigger를 사용하고 있었기 때문에 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> 
관련 문제