【问题标题】:WPF MVVM Add dynamic context Menu for TreeView using code C# in View ModelWPF MVVM 在视图模型中使用代码 C# 为 TreeView 添加动态上下文菜单
【发布时间】:2015-11-25 21:35:09
【问题描述】:

在这个著名的article 的帮助下,我使用 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>

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 });
}

我认为我缺少与绑定相关的内容。

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

【问题讨论】:

    标签: wpf xaml mvvm treeview contextmenu


    【解决方案1】:

    您必须代理绑定。 ContextMenus 是弹出窗口,因此它们不是同一可视化树的一部分,因此不继承 DataContext。您可以在Thomas Levesque's article 'How to bind to data when the DataContext is not inherited' 上阅读更多相关信息,他还提供了 BindingProxy 类的源代码。将其添加到您的项目中,然后修改您的 ContextMenu 以使用它:

    <local:BindingProxy x:Key="MyBindingProxy" Data="{Binding}" />
    <ContextMenu x:Key="MyContextMenu" DataContext="{Binding Source={StaticResource MyBindingProxy}, Path=Data}" ItemsSource="{Binding ContextMenuMap}" />
    

    不过,您粘贴的代码还有很多其他问题,对于初学者来说,当我确定您的意思是 ContextMenuMap 时,您将上下文菜单的项目绑定到 ContextMenuItem。另外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);
        }
    }
    

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

    // 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; }
    

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

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

    唯一的其他更改是让您的 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>
    

    【讨论】:

    • 是的,当然应该是ContextMenuMap。当我问我的答案时,它粗心地打字,因为我改变了我的变量的所有名称。你能否为我解释一下“你永远不应该在你的视图模型中声明视图控件”是什么意思 ContextMenuMap 不应该是字符串列表,我会丢失我的菜单项。我找到了答案,现在效果很好,我刚刚将 DataContext 添加到 contextMenu (几乎如您所说):
    • MenuItem 是一个控件,即一个视图对象,你不应该在你的视图模型中创建视图对象。这有很多很好的理由,但基本上该模式的重点是保持良好的关注点分离。如果您将菜单的ItemsSource 绑定到字符串类型的集合,它将正常工作,或者您可以将其绑定到其他类型的集合并将DisplayMemberPath 设置为要用于菜单项文本的字段。
    • 顺便说一句,如果您想要一个工作示例来说明为什么这不是一个好主意,然后尝试再次复制整个树结构(使用它自己的 ContextMenu 资源等),然后打开上下文菜单第一棵树,然后是第二棵,然后又是第一棵。您会看到第一个树的菜单上的所有项目突然变为空白,因为控件(包括 MenuItems)只能有一个父项。当您使用正确的数据绑定时,不会发生这种情况。
    • 根据第一点(菜单项),如果我将菜单的 ItemsSource 绑定到字符串类型的集合,我该如何处理每个 MenuItem 的命令和标题!
    • 完美标记,你是最棒的
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-26
    • 1970-01-01
    • 1970-01-01
    • 2016-06-11
    • 1970-01-01
    • 2017-09-24
    相关资源
    最近更新 更多