【问题标题】:Command Binding not working on a context sensitive menu?命令绑定在上下文相关菜单上不起作用?
【发布时间】:2025-12-06 17:05:02
【问题描述】:

我有一个用户控件(例如:UserCtrlClass),里面有一个树视图

我有视图模型(例如:OBJViewModel)类来表示树视图上的实际项目/数据显示

接下来我有一个树视图模型(例如:TreeViewModel),它有一个 OBJViewModel 对象列表

现在在用户控件的代码隐藏文件中,我已经实例化了树视图模型类并设置为用户控件类的数据上下文

我需要一个上下文相关的菜单,我只需要在我右键单击树中的特定项目时才显示它,因此我已经处理了用户控件类的右键单击事件并在那里完成了工作

但是命令不起作用,命令派生自 I 命令并在 TreeViewModel 类中实例化。我试图调试我的 Command.execute 从未被击中!任何帮助将不胜感激,因为我是 .net 和 wpf 的新手

TreeViewModel 类

<UserControl Name="PFDBUserCtrl" x:Class="BFSimMaster.BFSMTreeview"
         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" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:BFSimMaster.ViewModel"
         xmlns:cmd="clr-namespace:BFSimMaster.Commands"
         mc:Ignorable="d" 
         d:DesignHeight="66" d:DesignWidth="300">
<UserControl.Resources>
    <!--cmd:ActivateProjectCmd x:Key="CMDActivateProject"/-->
    <!--cmd:DeActivateProjectCmd x:Key="CMDDeActivateProject"/-->
</UserControl.Resources>
<DockPanel>
    <!-- PF Object Browser TREE -->
    <TreeView Name="PFDataBrowser" ItemsSource="{Binding LevelOnePFObjects}" >  
        <TreeView.Resources>
            <ContextMenu x:Key ="ProjectMenu"  StaysOpen="true" >
                <!-- Text="{Binding Source={StaticResource myDataSource}, Path=PersonName}-->
                <!--MenuItem Header="Activate" Command="{Binding Source={StaticResource CMDActivateProject}}" CommandParameter="{Binding Path=PlacementTarget,RelativeSource={RelativeSource AncestorType=ContextMenu}}"/-->
                <MenuItem Header="Activate" Command="{Binding DataContext.CMDActivateProject, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" CommandParameter="{Binding Path=PlacementTarget,RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
                <MenuItem Header="Deactivate" Command="{Binding Source=TVViewModel, Path=CMDDeActivateProject}" CommandParameter="{Binding Path=PlacementTarget,RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
            </ContextMenu>
        </TreeView.Resources>
        <TreeView.ItemContainerStyle>
            <!-- This Style binds a TreeViewItem to a PFObject View Model.-->                
            <Style TargetType="{x:Type TreeViewItem}">                    
                <EventSetter Event="MouseRightButtonDown" Handler="OnRightButtonDown"/>
                <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
                <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
                <Setter Property="FontWeight" Value="Normal" />

                <Style.Triggers>
                    <Trigger Property="IsSelected" Value="True">
                        <Setter Property="FontWeight" Value="Bold" />
                    </Trigger>                        
                </Style.Triggers>

            </Style>
        </TreeView.ItemContainerStyle>

        <TreeView.ItemTemplate>
            <HierarchicalDataTemplate ItemsSource="{Binding Children}">
                <TextBlock Text="{Binding Name}" />
            </HierarchicalDataTemplate>
        </TreeView.ItemTemplate>
    </TreeView>
</DockPanel>

类后面的代码

using System;
namespace BFSimMaster
{

public partial class BFSMTreeview : UserControl
{

    readonly TreeViewItemViewModel mViewModelPFObjBrowserTree;  
    public BFSMTreeview()
    {
        InitializeComponent();

        WApplication appPF = PFAPIUtils.APIInstance.GetApplication();
        WDataObject User = appPF.GetCurrentUser();



        // Get raw objects - tree data from a PF database.
        //BFPFDataObject userdb = new BFPFDataObject(User,false,"*.IntPrj");
        BFPFDataObject userdb = new BFPFDataObject(User, true);

        // Create UI-friendly wrappers around the 
        // raw data objects (i.e. the view-model).
        mViewModelPFObjBrowserTree = new TreeViewItemViewModel(userdb);

        // Let the UI bind to the view-model.
        base.DataContext = mViewModelPFObjBrowserTree;

    }
    public TreeViewItemViewModel TVViewModel
    {
        get { return mViewModelPFObjBrowserTree; }
    }

    private void OnRightButtonDown(object sender, MouseButtonEventArgs e)
    {
        //MessageBox.Show("Right Clicked on tree view");
        if (sender is TreeViewItem)
        {
            e.Handled = true;
            (sender as TreeViewItem).IsSelected = true;

            string strObjectType = ((sender as TreeViewItem).Header as PFObjectViewModel).PFDataObject.mThisPFObject.GetClassName().GetString();
            switch (strObjectType)
            {
                case "IntPrj":
                    (sender as TreeViewItem).ContextMenu = PFDataBrowser.Resources["ProjectMenu"] as System.Windows.Controls.ContextMenu;
                    (sender as TreeViewItem).ContextMenu.PlacementTarget = (sender as TreeViewItem); 
                    break;
                case "Folder":
                    (sender as TreeViewItem).ContextMenu = PFDataBrowser.Resources["ProjectMenu"] as System.Windows.Controls.ContextMenu;
                    break;
            }

        }
    }
}

}

TreeViewModel 类

using System;
namespace BFSimMaster.ViewModel
{

public class TreeViewItemViewModel 
{
    #region Data

    readonly ReadOnlyCollection<PFObjectViewModel> mLevelOnePFObjects;
    readonly PFObjectViewModel mRootOfPFObjects;

    #endregion // Data

    #region Constructor


    public TreeViewItemViewModel(BFPFDataObject rootOfPFObjectsA)
    {
        this.CMDActivateProject = new ActivateProjectCmd();
        this.CMDDeActivateProject = new DeActivateProjectCmd();
        mRootOfPFObjects = new PFObjectViewModel(rootOfPFObjectsA);

        mLevelOnePFObjects = new ReadOnlyCollection<PFObjectViewModel>(
            new PFObjectViewModel[] 
            { 
                mRootOfPFObjects 
            });            
    }

    #endregion // Constructor
    public ICommand CMDActivateProject { get; set; }
    public ICommand CMDDeActivateProject { get; set; }

    public ReadOnlyCollection<PFObjectViewModel> LevelOnePFObjects
    {
        get { return mLevelOnePFObjects; }
    }       

}
}

【问题讨论】:

    标签: c# wpf user-controls commandbinding


    【解决方案1】:

    ContextMenu 不是逻辑树的一部分,所以这个绑定"{Binding DataContext.CMDActivateProject, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" 不起作用,因为它根本没有 UserControl 类型的祖先。如果您没有手动设置 PlacementTarget 的 DataContext,您可以尝试

    "{Binding PlacementTarget.DataContext.CMDActivateProject, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"
    

    我不能 100% 确定以下内容,但我认为如果您使用 ContextMenu 属性,这将起作用:

    <Treeview>
        <Treeview.ContextMenu>
            <ContextMenu>
                ...
            <ContextMenu>
        <Treeview.ContextMenu>
        ...
    

    这里的好处是,您不必在代码隐藏中处理 rightbuttondown 事件,如果您右键单击树视图,它会自动打开。

    【讨论】:

      【解决方案2】:

      我通过为 MenuItem 引入 VM 类并使用 ExtendedContextMenu.Items={Binding ContextMenu} 附加属性设置上下文菜单解决了这个问题。 MenuResourcesDictionary 是带有背面 .cs 文件的 ResourceDictionary.xaml(如下所示)。

      要将它用于您的代码,您需要在树模型上添加 IEnumerable&lt;MenuItemVM&gt; ContextMenu 属性并将命令放在那里(例如,将它们传递给 MenuItemVM 构造函数)。并在项目样式中添加&lt;Setter Property="ExtendedContextMenu.Items" Value="{Binding DataContext.ContextMenu}" /&gt;

      public static class ExtendedContextMenu
      {
          private static readonly StyleSelector _styleSelector = new ContextMenuItemStyleSelector();
      
          public static readonly DependencyProperty ItemsProperty =
              DependencyProperty.RegisterAttached("Items",
                                                  typeof(IEnumerable<MenuItemVM>),
                                                  typeof(ExtendedContextMenu),
                                                  new FrameworkPropertyMetadata(default(IEnumerable<MenuItemVM>), ItemsChanged));
      
          public static void SetItems(DependencyObject element, IEnumerable<MenuItemVM> value)
          {
              element.SetValue(ItemsProperty, value);
          }
      
          public static IEnumerable<MenuItemVM> GetItems(DependencyObject element)
          {
              return (IEnumerable<MenuItemVM>)element.GetValue(ItemsProperty);
          }
      
          private static void ItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
          {
              var target = (FrameworkElement)d;
      
              var items = (IEnumerable<MenuItemVM>)e.NewValue;
              var contextMenu = new ContextMenu();
      
              contextMenu.ItemContainerStyleSelector = _styleSelector;
              contextMenu.ItemsSource = items;
      
              return contextMenu;
          }
      
          private static void AdjustContextMenuVisibility(ContextMenu menu)
          {
              menu.Visibility = menu.HasItems ? Visibility.Visible : Visibility.Hidden;
          }
      }
      
      public class ContextMenuItemStyleSelector : StyleSelector
      {
          private static readonly MenuResourcesDictionary _resources = new MenuResourcesDictionary();
          private static readonly Style _menuItemStyle = (Style)_resources[MenuResourcesDictionary.MenuItemStyleResourceKey];
          private static readonly Style _separatorStyle = (Style)_resources[MenuResourcesDictionary.SeparatorStyleResourceKey];
      
          public override Style SelectStyle(object item, DependencyObject container)
          {
              if (item == MenuItemVM.Separator)
                  return _separatorStyle;
      
              return _menuItemStyle;
          }
      }
      
      public sealed partial class MenuResourcesDictionary
      {
          public const string MenuItemStyleResourceKey = "MenuItemStyleResourceKey";
          public const string DynamicMenuItemStyleResourceKey = "DynamicMenuItemStyleResourceKey";
          public const string SeparatorStyleResourceKey = "SeparatorStyleResourceKey";
          public const string LoadingStyleResourceKey = "LoadingMenuItemStyleResourceKey";
      
          public MenuResourcesDictionary()
          {
              InitializeComponent();
          }
      }
      

      这里是 XAML 样式:

      <Style x:Key="{x:Static local:MenuResourcesDictionary.MenuItemStyleResourceKey}">
        <Setter Property="MenuItem.Header" Value="{Binding Path=(menuVMs:MenuItemVM.Text)}" />
        <Setter Property="MenuItem.Command" Value="{Binding Path=(menuVMs:MenuItemVM.Command)}" />
      </Style>
      
      <Style x:Key="{x:Static local:MenuResourcesDictionary.SeparatorStyleResourceKey}" TargetType="{x:Type MenuItem}">
        <Setter Property="Template">
          <Setter.Value>
            <ControlTemplate>
              <Separator />
            </ControlTemplate>
          </Setter.Value>
        </Setter>
      </Style>
      

      MenuItemVM 类或多或少直截了当,所以这里就不写了。

      【讨论】:

        【解决方案3】:

        我在这里找到了带有和不带有 DataTemplate 的 TreeView 上下文菜单的答案: TreeView ContextMenu MVVM Binding MVVM binding command to contextmenu item

        【讨论】: