在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
…… 如果你知道如何,它就很简单了。