如何在WPF中实现命令以使用祖先方法?
我有这个上下文菜单资源:
我想在两个地方重复使用它。 首先,我试图将它放在DataGrid
:
...
ContextMenu
本身工作正常,但是使用Executed="..."
我现在打破了应用程序并抛出:
PresentationFramework.dll中出现“System.InvalidCastException”类型的第一次机会exception
附加信息:无法将类型为“System.Reflection.RuntimeEventInfo”的对象强制转换为“System.Reflection.MethodInfo”。
如果我删除整个Executed="..."
定义,那么代码就可以工作(并且命令不执行任何操作/灰显)。 一旦我右键单击网格/打开上下文菜单,就会抛出exception。
DataGrid
放在几个元素下面,但最终它们都在一个TabControl
(名为MainTabs
) MainTabs
,它将ItemsSource
设置为FooViewModel
的集合,在那个FooViewModel
我有一个我想要调用的方法HelpExecuted
。
让我们想象一下:
- TabControl(
ItemsSource=ObservableCollection
,x:Name=MainTabs
)- 格
- 更多UI
- DataGrid(带上下文菜单集)
- 更多UI
- 格
为什么我会收到此错误,如何使上下文菜单命令“定位” FooViewModel
的HelpExecuted
方法?
遗憾的是,您无法为ContextMenu
绑定Executed
,因为它是一个事件。 另一个问题是,在应用程序的其余部分存在的VisualTree
中, ContextMenu
不存在。 这两个问题都有解决方案。
首先,您可以使用ContextMenu
的父控件的Tag
属性来传递应用程序的DataContext
。 然后你可以使用DelegateCommand
为你的CommandBinding
,你去。 这是一个小样本,显示了您必须添加到项目中的View
, ViewModel
和DelegateCommand
实现。
DelegateCommand.cs
public class DelegateCommand : ICommand { private readonly Action
MainWindowView.xaml
MainWindowView.xaml.cs
public partial class MainWindowView : Window { public MainWindowView() { InitializeComponent(); DataContext = new MainWindowViewModel(); } }
MainWindowViewModel.cs
public class MainWindowViewModel { public ObservableCollection FooViewModels { get; set; } public MainWindowViewModel() { FooViewModels = new ObservableCollection (); } }
FooViewModel.cs
public class FooViewModel { public ICommand HelpExecuted { get; set; } public FooViewModel() { HelpExecuted = new DelegateCommand(ShowHelp); } private void ShowHelp(object obj) { // Yay! } }
这有帮助吗?
我担心MatthiasG会打败我。 我的解决方案类似:
此处,“帮助”命令由选项卡项的视图模型处理。 将TestViewModel的引用传递给每个TestItemViewModel并在需要时将ShowHelp回调到TestViewModel中会很简单。
public class TestViewModel { public TestViewModel() { Items = new List{ new TestItemViewModel(), new TestItemViewModel() }; } public ICommand HelpCommand { get; private set; } public IList Items { get; private set; } } public class TestItemViewModel { public TestItemViewModel() { // Expression Blend ActionCommand HelpCommand = new ActionCommand(ShowHelp); Header = "header"; } public ICommand HelpCommand { get; private set; } public string Header { get; private set; } private void ShowHelp() { Debug.WriteLine("Help item"); } }
xaml
备用视图模型,以便将帮助命令定向到根视图模型
public class TestViewModel { public TestViewModel() { var command = new ActionCommand(ShowHelp); Items = new List { new TestItemViewModel(command), new TestItemViewModel(command) }; } public IList Items { get; private set; } private void ShowHelp() { Debug.WriteLine("Help root"); } } public class TestItemViewModel { public TestItemViewModel(ICommand helpCommand) { HelpCommand = helpCommand; Header = "header"; } public ICommand HelpCommand { get; private set; } public string Header { get; private set; } }
ActionCommand的一个非常简单的实现
public class ActionCommand : ICommand { private readonly Action _action; public ActionCommand(Action action) { if (action == null) { throw new ArgumentNullException("action"); } _action = action; } public bool CanExecute(object parameter) { return true; } public void Execute(object parameter) { _action(); } // not used public event EventHandler CanExecuteChanged; }
您收到此错误,因为CommandBinding.Executed不是依赖项属性,因此您无法绑定它。
相反,使用后面的ResourceDictionary代码为CommandBinding.Executed事件指定事件处理程序,并在事件处理程序代码中调用FooViewModel.HelpExecuted()方法,如下所示:
MainWindowResourceDictionary.xaml
MainWindowResourceDictionary.xaml.cs
public partial class MainWindowResourceDictionary : ResourceDictionary { public MainWindowResourceDictionary() { InitializeComponent(); } private void HelpExecuted(object sender, ExecutedRoutedEventArgs e) { var fooViewModel = (FooViewModel)((FrameworkElement)e.Source).DataContext; fooViewModel.HelpExecuted(); } }
可以创建一个适配器类,可以将其配置为XAML中的资源,可以附加到Control以便在那里创建CommandBindings,另一端可以绑定到ViewModel中的一个方法,该方法应该在命令由Button或MenuItem触发。 在这种情况下,命令将是RoutedCommand,无论您是选择其中一个预定义的WPF命令还是在应用程序中创建自定义RoutedCommand都无关紧要。
绑定到方法的技巧是
- 使适配器成为Freezable,因此可以使用当前的DataContext作为绑定源,
- 给它一个Delegate类型或它的一个子类型的DependencyProperty,和
- 使用转换器接受方法名称作为ConverterParameter并检查绑定源类型,以便为应该由命令调用的方法创建委托。
虽然这听起来很复杂,但好处是,一旦将框架的各个部分放在一起,您只需在XAML中重复使用它们,并且在ViewModel或后面的代码中根本不会有任何粘合代码。
你可以想象,这需要一些基础设施,代码比我想发布的更多。 但是,我刚刚在我的博客上发表了一篇关于这个主题的文章, http://wpfglue.wordpress.com/2012/05/07/commanding-binding-controls-to-methods/ ,你可以通过博客下载完整的框架源代码和VB.Net中的示例。
应用于您的问题,XAML将如下所示:
在ContextMenu的定义中:
并在DataGrid的定义中
- 接口属于自己的文件
- 通过win7 FirewallAPI将应用程序防火墙规则添加到专用和公用网络
- Expression.PropertyOrField可以用来访问静态属性或字段吗?
- 如果在WPF中不再存在,Triggers / DataTrigger会返回到先前的状态吗?
- 在Entity Framework和WPF UI中显示计算属性
- 如何使用动态对象类型创建List
- Unity3D在C#中寻找使用纹理绘制的setPixels示例
- Umbraco 4.6+ – 如何通过C#中的doctype获取所有节点?
- 浏览器链接与Visual Studio 2015 Update 3中的ASP.NET Core v1.0