RoutedCommand和RelayCommand 有什么区别?在 MVVM 模式中何时使用 RoutedCommand 以及何时使用 RelayCommand?
4 回答
RoutedCommand是 WPF 的一部分,而RelayCommand是由 WPF 门徒 Josh Smith 创建的;)。
不过,说真的,RS Conley 描述了其中的一些差异。主要区别在于 RoutedCommand 是一个 ICommand 实现,它使用 RoutedEvent 在树中路由,直到找到该命令的 CommandBinding,而 RelayCommand 不进行路由,而是直接执行一些委托。在 MV-VM 场景中,RelayCommand(Prism 中的 DelegateCommand)可能是更好的选择。
关于在 MVVM 中使用 RelayCommand 和 RoutedCommand,对我来说主要区别如下:
代码位置
RelayCommand 允许您在任何类中实现命令(作为具有委托的 ICommand 属性),然后通常将数据绑定到调用命令的控件。这个类是ViewModel。如果使用路由命令,则必须在控件的代码隐藏中实现与命令相关的方法,因为这些方法由 CommandBinding 元素的属性指定。假设严格的 MVVM 意味着有一个“空”的代码隐藏文件,实际上不可能将标准路由命令与 MVVM 一起使用。
RS Conley 所说的,RelayCommand 允许您在 ViewModel 之外定义 RelayCommand 是对的,但首先它允许您在 ViewModel内部定义它,而 RoutedCommand 没有。
路由
另一方面,RelayCommands 不支持通过树进行路由(如前所述),这不是问题,只要您的界面基于单个 viewModel。如果不是,例如,如果您有一组具有自己的 viewModel 的项目,并且想要一次从父元素中为每个项目调用子 ViewModel 的命令,则必须使用路由(另请参见 CompositeCommands) .
总而言之,我想说,标准的 RoutedCommands 在严格的 MVVM 中不可用。RelayCommands 非常适合 MVVM,但不支持您可能需要的路由。
不同之处在于 RelayCommand 可以接受委托。您可以在 ViewModel 之外定义 RelayCommand。然后,ViewModel 可以在创建命令并将命令绑定到 UI 对象(如控件)时向命令添加委托。委托反过来可以访问 ViewModel 的私有变量,因为它们是在 View Model 本身的范围内定义的。
它用于减少 ViewModel 中包含的代码量,因为趋势是将 Routed 命令定义为 ViewModel 内的嵌套类。两者的功能在其他方面相似。
我认为 RoutedCommands 在严格的 MVVM 中是完全合法的。尽管 RelayCommands 通常因其简单性而受到青睐,但 RoutedCommands 有时会提供组织优势。例如,您可能希望多个不同的视图连接到一个共享的 ICommand 实例,而无需将该命令直接暴露给底层的 ViewModel。
作为旁注,请记住严格的 MVVM 并不禁止使用代码隐藏。如果那是真的,那么您将永远无法在视图中定义自定义依赖项属性!
为了在严格的 MVVM 框架中使用 RoutedCommand,您可以按照以下步骤操作:
为您的自定义命令声明一个静态 RoutedCommand 实例。如果您打算使用 ApplicationCommands 类中的预定义命令,则可以跳过此步骤。例如:
public static class MyCommands { public static RoutedCommand MyCustomCommand = new RoutedCommand(); }
使用 XAML 将所需的视图附加到 RoutedCommand:
<Button Command="{x:Static local:MyCommands.MyCustomCommand}" />
您绑定到合适 ViewModel 的视图之一(即,无论哪个 ViewModel 实现命令功能)都需要公开一个自定义 DependencyProperty,该自定义 DependencyProperty 将绑定到您的 ViewModel 的实现:
public partial class MainView : UserControl { public static readonly DependencyProperty MyCustomCommandProperty = DependencyProperty.Register("MyCustomCommand", typeof(ICommand), typeof(MainView), new UIPropertyMetadata(null)); public ICommand MyCustomCommand { get { return (ICommand)GetValue(MyCustomCommandProperty); } set { SetValue(MyCustomCommandProperty, value); } }
相同的视图应将自身绑定到步骤 1 中的 RoutedCommand。在 XAML 中:
<UserControl.CommandBindings> <CommandBinding Command="{x:Static local:MyCommands.MyCustomCommand}" CanExecute="MyCustomCommand_CanExecute" Executed="MyCustomCommand_Executed" /> </UserControl.CommandBindings>
在您的视图的代码隐藏中,关联的事件处理程序将仅从步骤 3 中声明的依赖属性委托给 ICommand:
private void MyCustomCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e) { var command = this.MyCustomCommand; if (command != null) { e.Handled = true; e.CanExecute = command.CanExecute(e.Parameter); } } private void MyCustomCommand_Executed(object sender, ExecutedRoutedEventArgs e) { var command = this.MyCustomCommand; if (command != null) { e.Handled = true; command.Execute(e.Parameter); } }
最后,将 ViewModel 的命令实现(应该是 ICommand)绑定到 XAML 中的自定义依赖项属性:
<local:MainView DataContext="{Binding MainViewModel}" MyCustomCommand="{Binding CustomCommand}" />
这种方法的优点是您的 ViewModel 只需要提供 ICommand 接口的单个实现(它甚至可以是 RelayCommand),而任意数量的视图可以通过 RoutedCommand 附加到它,而无需直接绑定到那个视图模型。
不幸的是,有一个缺点是 ICommand.CanExecuteChanged 事件将不起作用。当您的 ViewModel 希望 View 刷新 CanExecute 属性时,您必须调用 CommandManager.InvalidateRequerySuggested()。