WPF MVVM使用View Model中的代码C#为TreeView添加动态上下文菜单

xte*_*408 1 wpf treeview xaml contextmenu mvvm

我在这篇着名的文章的帮助下使用HierarchicalDataTemplate创建了一个TreeView.

我的树视图中的每个节点都有不同的contextMenu.所以我为treeView创建了一个属性,它为我返回所选节点的对象.然后我使用下面的代码来显示我的ContextMenu.但contextMenu总是空的.

<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>
Run Code Online (Sandbox Code Playgroud)

PrincipalViewModel :(与ChildViewModel无关)

private ICommand _editMapCommand;

    public ICommand EditMapCommand
    {
        get
        {
            return _editMapCommand;
        }
        set
        {
            SetProperty(ref _editMapCommand, value, () => EditMapCommand);
            OnPropertyChanged("EditMapCommand");

        }
    }

    private ICommand _removeMapCommand;

    public ICommand RemoveMapCommand
    {
        get
        {
            return _removeMapCommand;
        }
        set
        {
            SetProperty(ref _removeMapCommand, value, () => RemoveMapCommand);
            OnPropertyChanged("RemoveMapCommand");

        }
    }
 private ObservableCollection<MenuItem> _contextMenuMap;
    public ObservableCollection<MenuItem> ContextMenuMap
    {
        get
        {
            return _contextMenuMap;
        }
        set
        {
            SetProperty(ref _contextMenuMap, value, () => ContextMenuMap);
            OnPropertyChanged("ContextMenuMap");

        }
    }
private object _selectedItem;
    public object SelectedItem
    {
        get
        {
            return _selectedItem;
        }

        set
        {
            SetProperty(ref _selectedItem, value, () => SelectedItem);
            OnPropertyChanged("SelectedItem");
            Fill(_selectedItem);
        }
    }
 private void FillPropertyCard(object obj)
    {
        PcEditable = false;
        if (obj is MyObject)
        {
            ContextMenuMap = new ObservableCollection<MenuItem>();
            EditMapCommand = new DelegateCommand<CancelEventArgs>(OnEditMapCommandExecute, OnEditMapCommandCanExecute);
            RemoveMapCommand = new DelegateCommand<CancelEventArgs>(OnRemoveMapCommandExecute, OnRemoveMapCommandCanExecute);
            ContextMenuMap.Add(new MenuItem() { Header = "editHeader", Command = EditMapCommand });
            ContextMenuMap.Add(new MenuItem() { Header = "removeHeader", Command = RemoveMapCommand });
}
Run Code Online (Sandbox Code Playgroud)

我相信我错过了与绑定相关的东西.

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

Mar*_*man 6

你必须代理绑定.ContextMenus是弹出窗口,因此它们不属于同一个可视树,因此不会继承DataContext.您可以在Thomas Levesque的文章"如何在未继承DataContext时绑定数据"中阅读更多相关信息,他还提供了BindingProxy类的源代码.将其添加到项目中,然后修改ContextMenu以使用它:

<local:BindingProxy x:Key="MyBindingProxy" Data="{Binding}" />
<ContextMenu x:Key="MyContextMenu" DataContext="{Binding Source={StaticResource MyBindingProxy}, Path=Data}" ItemsSource="{Binding ContextMenuMap}" />
Run Code Online (Sandbox Code Playgroud)

你粘贴的代码还存在很多其他问题,对于初学者来说,你将上下文菜单的项目绑定到ContextMenuItem我确定你的意思ContextMenuMap.同时ContextMenuMap不应该是一个集合MenuItem,你永远不应该在你的视图模型声明视图控件.ContextMenuMap改为将其变为字符串集合; MenuItems将自动创建上下文菜单.

编辑:对不起Sadok,我并不认真建议你在你的应用程序中使用字符串集合,我只是用它来说明数据绑定如何在这样的情况下工作的总体点.在真实世界的应用程序中,您将为菜单项创建视图模型,就像您对其他类型的视图一样.一个简单的可能只需要头文本,ICommands(您当前设置为单独的属性)并且可能需要支持CanExecute处理程序:

public class MenuItemViewModel
{
    public string Header { get; private set; }
    public ICommand Command { get; private set; }

    public MenuItemViewModel(string header, Action execute, Func<bool> canExecute = null)
    {
        this.Header = header;
        this.Command = new RelayCommand(execute, canExecute);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后将在您的代码中设置菜单,如下所示:

// 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; }
Run Code Online (Sandbox Code Playgroud)

或者,如果您愿意,可以在适当的地方使用匿名函数:

this.ContextMenuMap = new ObservableCollection<MenuItemViewModel>
{
    new MenuItemViewModel("Cut", () => { /* cut code here */ }),
    new MenuItemViewModel("Copy", () => { /* copy code here */ }),
    new MenuItemViewModel("Paste", () => { /* paste code here */ }, () => false)
};
Run Code Online (Sandbox Code Playgroud)

唯一的另一个变化是让您的XAML知道如何使用此视图模型.正如我在下面提到的,您可以设置DisplayMemberPath指定要用于文本的字段,您可以使用样式设置器来指定命令字段:

<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>
Run Code Online (Sandbox Code Playgroud)