在ItemsControl中绑定上下文菜单?

如何将ContextMenu添加到ItemsControl,其中:

  • ItemsControl的ItemsSource位于包含ItemsControl的UserControl的ViewModel中
  • Context Menu的CommandParameter是绑定到ItemsControl中Item的ViewModel。

我遵循这种方法 。 但是,我有一个Command,它从绑定到ItemsControl的ObservableCollection中删除项目。 发生这种情况时,会在RelayCommand中抛出exception。 在我看来,ContextMenu不是“隐藏”,所以它试图为其命令评估“CanExecute”,但是当项目被删除时,它不能在RelayCommand的CanExecute方法中将参数转换为“T”类。

我想知道如何完成我需要的正确方法。


到目前为止我的实施:

MainViewModel

public class MainViewModel { public ObservableCollection ContextMenuItems{ get;set; } public ObservableCollection MyItems{ get;set; } public void AddItem(MyItemClass item) { MyItems.Add(item); } public void AddContextMenuItem(MyContextMenuClass item) { ContextMenuItems.Add(item); } public MainViewModel(IList myItems, IList myContextualMenuItems) { MyItems.AddRange(myItems); ContextMenuItems.AddRange(myContextualMenuItems); } public MainViewModel() {} } 

MyItemClass

 public class MyItemClass { public string MyText{get;set;} } 

MyContextMenuClass

 public class MyContextMenuClass { public RecentContextMenuItem() {} public string Caption{get;set;} public RelayCommand Command{get;set;} } 

我的UserControl (DataContext = MainViewModel)

                                

我的UserControl Codebehind

 public partial class MyUserControl : UserControl { ///  /// Initializes a new instance of the  class. ///  public MyUserControl() { InitializeComponent(); } private void Item_ContextMenuOpening(object sender, ContextMenuEventArgs e) { var contentPresenter = sender as ContentPresenter; if (contentPresenter != null) { this.Dispatcher.BeginInvoke(new Action(ShowItemContextMenu), new object[] { contentPresenter }); } } private void ShowItemContextMenu(ContentPresenter sourceContentPresenter) { if (sourceContentPresenter != null) { var ctxMenu = (ContextMenu)this.FindResource("ItemContextMenu"); ctxMenu.DataContext = this.DataContext; if (ctxMenu.Items.Count == 0) { sourceContentPresenter.ContextMenu = null; } else { ctxMenu.PlacementTarget = sourceContentPresenter; ctxMenu.IsOpen = true; } } } } 

RemoveItemCommand我添加到MainViewModel

 new RelayCommand(RemoveItem, (param) => true); private void RemoveItem(MyItemClassitemToRemove) { MyItems.Remove(itemToRemove); } 

RelayCommand的CanExecute方法

 public bool CanExecute(object parameter) { if (_canExecute == null) { return true; } if (parameter == null) { return _canExecute.Invoke(default(T)); } T value; try { value = (T)parameter; } catch(Exception exception) { Trace.TraceError(exception.ToString()); return _canExecute.Invoke(default(T)); } return _canExecute.Invoke(value); } 

我在value =(T)参数中得到错误; line,因为参数是Disconnected并且无法将其强制转换为T。

我得到的例外情况

MyProgram.vshost.exe错误:0:System.InvalidCastException:无法将类型为“MS.Internal.NamedObject”的对象强制转换为“MyItemClass”。 at MyNamespace.RelayCommand`1.CanExecute(Object parameter)in c:\ MyPath \ RelayCommand.cs:line xxx

如果我检查参数它是一个NamedObject:

  • 参数{DisconnectedItem} object {MS.Internal.NamedObject}
  • 非公共成员
    _name“{DisconnectedItem}”字符串

问题不在于Exception,它是通过DisconnectedItem达到这一点的事实。 这次评估被多次评估。 就像ContextMenu在Visual Tree中一样“永远”。

首先,只需检查参数值为null

 return parameter == null ? false : _canExecuteMethod((T)parameter); 

其次,这是旧的ContextMenu.DataContext问题: ContextMenu显示在与UI其余部分不同的可视树中。 因此,它无法从主UI可视树中访问DataContext 。 因此,我们必须使用一个小技巧将其传递给另一个可视树。 我们之间的连接是ContextMenu.PlacementTarget属性 。

从链接页面,此属性

获取或设置ContextMenu打开时相对于其定位的UIElement。

我们可以使用ContextMenu.PlacementTarget对象的Tag属性来传递DataContext 。 基本上,只需在要设置ContextMenu的对象上设置Tag属性。 尝试这样的事情:

        

  

而已。 现在, ContextMenu声明的UI元素将可以访问数据绑定到Tag属性的任何对象。 EventSetter绝对不需要使用ContextMenu …… 如果你知道如何,它就很简单了。