WPF MVVM在视图模型中使用代码C为TreeView添加动态上下文菜单

WPF MVVM在视图模型中使用代码C为TreeView添加动态上下文菜单,wpf,xaml,mvvm,treeview,contextmenu,Wpf,Xaml,Mvvm,Treeview,Contextmenu,我有一个树状视图创建使用HierarchycalDataTemplate的帮助下,这个著名的 我的树视图中的每个节点都有不同的上下文菜单。因此,我为treeView创建了一个属性,它为我返回每个选定节点的对象。然后我使用下面的代码来显示我的上下文菜单。但是上下文菜单总是空的 <view:MyTreeView ItemsSource="{Binding MyNode}" SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}" >

我有一个树状视图创建使用HierarchycalDataTemplate的帮助下,这个著名的

我的树视图中的每个节点都有不同的上下文菜单。因此,我为treeView创建了一个属性,它为我返回每个选定节点的对象。然后我使用下面的代码来显示我的上下文菜单。但是上下文菜单总是空的

<view:MyTreeView ItemsSource="{Binding MyNode}" 
SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}" >
    <TreeView.Resources>
      <ContextMenu x:Key="MyContextMenu" ItemsSource="{Binding ContextMenuItem}"/>
       <DataTemplate DataType="{x:Type local:ChildViewModel}">
         <StackPanel Orientation="Horizontal" ContextMenu="{StaticResource MyContextMenu}">
//...
         </StackPanel>
       </DataTemplate>
   </TreeView.Resources>
</view:MyTreeView>

//...
PrincipalViewModel:(与ChildViewModel无关)

private ICommand\u editmap命令;
公共ICommand editmap命令
{
得到
{
返回editmap命令;
}
设置
{
SetProperty(ref _editmap命令,值,()=>editmap命令);
OnPropertyChanged(“EditMapCommand”);
}
}
私有ICommand_removeMappCommand;
公共ICommand removeMap命令
{
得到
{
return\u removemap命令;
}
设置
{
SetProperty(ref _removeMapCommand,value,()=>removeMapCommand);
OnPropertyChanged(“RemoveMapCommand”);
}
}
私有可观测集合上下文菜单映射;
公共可观察收集上下文菜单映射
{
得到
{
返回上下文菜单映射;
}
设置
{
SetProperty(ref _contextMenuMap,value,()=>contextMenuMap);
OnPropertyChanged(“ContextMenuMap”);
}
}
私有对象_selectedItem;
公共对象SelectedItem
{
得到
{
返回_selectedItem;
}
设置
{
SetProperty(ref _selectedItem,value,()=>selectedItem);
OnPropertyChanged(“SelectedItem”);
填充(_selectedItem);
}
}
私有void FillPropertyCard(对象obj)
{
pcediate=false;
if(对象是MyObject)
{
ContextMenuMap=新的ObservableCollection();
EditMapCommand=新的DelegateCommand(OnEditMapCommandExecute,OnEditMapCommandCanExecute);
RemoveMapCommand=新的DelegateCommand(OnRemoveMapCommandExecute,OnRemoveMapCommandCanExecute);
添加(新的MenuItem(){Header=“editHeader”,Command=EditMapCommand});
添加(新的MenuItem(){Header=“removeHeader”,Command=RemoveMapCommand});
}
我想我遗漏了一些与装订有关的东西


注意:调试时,我在xaml中发现ContextMenuMap的值按预期发生了更改,但始终没有显示任何内容。

您必须代理绑定。ContextMenus是弹出窗口,因此它们不是同一可视树的一部分,因此不会继承DataContext。您可以在上了解更多信息,他还提供了Bindi的源代码ngProxy类。将其添加到项目中,然后修改上下文菜单以使用它:

<local:BindingProxy x:Key="MyBindingProxy" Data="{Binding}" />
<ContextMenu x:Key="MyContextMenu" DataContext="{Binding Source={StaticResource MyBindingProxy}, Path=Data}" ItemsSource="{Binding ContextMenuMap}" />
然后在代码中设置菜单,如下所示:

// set up the menu
this.ContextMenuMap = new ObservableCollection<MenuItemViewModel>
{
    new MenuItemViewModel("New", OnNew),
    new MenuItemViewModel("Open", OnOpen),
    new MenuItemViewModel("Save", OnSave, CanSave)
};

// menu command handlers
private void OnNew() { /* ... */ }
private void OnOpen() { /* ... */ }
private void OnSave() { /* ... */ }
private bool CanSave() { /* ... */ return false; }

您必须代理绑定。ContextMenus是弹出窗口,因此它们不是同一可视树的一部分,因此不会继承DataContext。您可以在上阅读更多关于此的内容,他还提供BindingProxy类的源代码。将其添加到项目中,然后修改ContextMenu以使用它:

<local:BindingProxy x:Key="MyBindingProxy" Data="{Binding}" />
<ContextMenu x:Key="MyContextMenu" DataContext="{Binding Source={StaticResource MyBindingProxy}, Path=Data}" ItemsSource="{Binding ContextMenuMap}" />
然后在代码中设置菜单,如下所示:

// set up the menu
this.ContextMenuMap = new ObservableCollection<MenuItemViewModel>
{
    new MenuItemViewModel("New", OnNew),
    new MenuItemViewModel("Open", OnOpen),
    new MenuItemViewModel("Save", OnSave, CanSave)
};

// menu command handlers
private void OnNew() { /* ... */ }
private void OnOpen() { /* ... */ }
private void OnSave() { /* ... */ }
private bool CanSave() { /* ... */ return false; }

是的,当然,应该是ContextMenuMap。当我问我的答案时,这是一个粗心的输入,因为我更改了变量的所有名称。您能为我解释一下“您永远不应该在视图模型中声明视图控件”是什么意思吗ContextMenuMap不应该是字符串列表,我将丢失我的菜单项。我找到了答案,现在它工作得很好,我刚刚将DataContext添加到contextMenu(几乎如您所说):
MenuItem
是一个控件,即视图对象,您永远不应该在视图模型中创建视图对象。这有很多很好的理由,但基本上整个模式的目的是保持关注点的良好分离。如果您将菜单的
ItemsSource
绑定到一个类型字符串集合,它将正常工作,或者可以将其绑定到其他类型的集合,并将
DisplayMemberPath
设置为要用于菜单项文本的字段。顺便说一句,如果您想知道为什么这不是一个好主意,请尝试复制整个树结构(使用它自己的ContextMenu资源等)第二次,然后在第一棵树上打开上下文菜单,然后再打开第二棵树,然后再打开第一棵树。您会看到第一棵树菜单上的所有项突然变为空白,因为控件(包括菜单项)只能有一个父项。当您使用正确的数据绑定时,不会发生这种情况。根据第一点(菜单项)如果我将菜单项资源绑定到一个类型字符串集合,我如何处理每个菜单项的命令和标题!!完美的标记,你是最好的当然,它应该是ContextMenuMap。当我问我的答案时,它会粗心地键入,因为我更改了变量的所有名称。你能为我解释一下你的意思吗“您不应该在视图模型中声明视图控件”ContextMenuMap不应该是字符串列表,我将丢失我的菜单项。我找到了答案,现在它工作得很好,我只是将DataContext添加到contextMenu(几乎如您所说):
MenuItem
是一个控件,即视图对象,您永远不应该在视图模型中创建视图对象。这有很多很好的理由,但基本上整个模式的目的是保持关注点的良好分离。如果您将菜单的
ItemsSource
绑定到一个类型字符串集合,它将正常工作,或者可以将其绑定到其他类型的集合,并将
DisplayMemberPath
设置为要用于菜单项文本的字段。顺便说一句,如果您想知道为什么这不是一个好主意,请尝试第二次复制整个树结构(使用自己的ContextMenu资源等),然后将
this.ContextMenuMap = new ObservableCollection<MenuItemViewModel>
{
    new MenuItemViewModel("Cut", () => { /* cut code here */ }),
    new MenuItemViewModel("Copy", () => { /* copy code here */ }),
    new MenuItemViewModel("Paste", () => { /* paste code here */ }, () => false)
};
<ContextMenu x:Key="MyContextMenu" DataContext="{Binding Source={StaticResource MyBindingProxy}, Path=Data}" ItemsSource="{Binding ContextMenuMap}" DisplayMemberPath="Header">
    <ContextMenu.Resources>
        <Style TargetType="{x:Type MenuItem}">
            <Setter Property="Command" Value="{Binding Command}" />
        </Style>
    </ContextMenu.Resources>
</ContextMenu>