这篇来讨论Command基于ViewModel的基本使用.
以prism内置Command Demo为例子,效果图如下
View相对应的ViewModel
1.OrderEditorView的Model就是OrdersEditorPresentationModel了
2.SaveAllOrdersCommand可以在数据通过验证后同时保存,即同时触发三个Command
3.每个Order都有一个Save的Command,这里Orders是一个OrderPresentationModel列表,注意这里OrdersEditorPresentationModel有一个View的属性(因为我们还没有Presenter来操作调配)
路由命令由RoutedCommand实现,必须由当前使用的控件的父级控件在CommandBindings集合中注册命令.
这里需要注意的是wpf内置的Command,如ApplicationCommands类有很多Command,其只是定义而已,并未真正实现,像TextBox这些控件内置已经实现了一些,复制粘贴功能,所以不要以为是内置Command实现,在注册这些内置的Command时,必须手动实现才可以.
有些控件实现了内置的Command逻辑,你可以复用这些功能,这时候你就必须指定CommandTarget这个属性
下面我们来看一下做法
1.CreateCommandBinding负责注册命令,注册的Model均继承自CommandModel
CommandModel commandModel = e.NewValue as CommandModel; if (commandModel != null) { element.CommandBindings.Add(new CommandBinding(commandModel.Command, commandModel.OnExecute, commandModel.OnQueryEnabled)); }
2.Model
/// <summary> /// Model for a command /// </summary> public abstract class CommandModel { public CommandModel() { _routedCommand = new RoutedCommand(); } /// <summary> /// Routed command associated with the model. /// </summary> public RoutedCommand Command { get { return _routedCommand; } } /// <summary> /// Determines if a command is enabled. Override to provide custom behavior. Do not call the /// base version when overriding. /// </summary> public virtual void OnQueryEnabled(object sender, CanExecuteRoutedEventArgs e) { e.CanExecute = true; e.Handled = true; } /// <summary> /// Function to execute the command. /// </summary> public abstract void OnExecute(object sender, ExecutedRoutedEventArgs e); private RoutedCommand _routedCommand; }
3.使用方法,这里分析一下其用法
CreateCommandBinding附加属性Command省却了手动注册Command这一步骤
Command还是绑定ViewModel的一个属性,但其处理的对象本应该在ViewModel中的,这里却用CommandParameter将一个与ViewModel绑定的参考重新传了进去,这是一个不好的地方.这是可以通过设计来改善的,因为在ViewModel定义出来的Command大多数是操作ViewModel对象的,只要想办法把定义的CommandModel的Command提取到ViewModel中,就可以直接操作ViewModel了,依靠CommandParamter来传值并不是一个好的办法.但注册Command这一步骤还是少不了的.
<Button Command="{Binding UnblockCommandModel.Command}" CommandParameter="{Binding Path=SelectedItem, ElementName=_skillList}" luna:CreateCommandBinding.Command="{Binding UnblockCommandModel}">
DelegateCommand实现了ICommand接口(SaveOrderCommand便是一个DelegateCommand),wpf有一些控件实现了ICommandSource接口,具有Command属性和CommandParameter属性,两者皆为依赖属性,这就为数据绑定提供了便利.CompositeCommand的职责在于实现多个Command同时执行.如wpf的Button控件(注意:silverlight当前版本很多控件并未具有Command属性,所以prism使用附加属性的形式实现了这一过程),如下
<Button Grid.Row="6" Grid.Column="1" Content="Save" cal:Click.Command="{Binding Path=SaveOrderCommand}" />
wpf版本的
<Button Grid.Row="6" Grid.Column="1" Content="Save" Command="{Binding SaveOrderCommand}"></Button>
其在ViewModel中的定义方法(参考包含Execute,CanExecute两个委托)
this.SaveOrderCommand = new DelegateCommand<object>(this.Save, this.CanSave);
CompositeCommand有两个方法用于注册(RegisterCommand)和注销(UnregisterCommand)想要同时触发Command的方法.其可以是一个静态属性
<Button Command="{x:Static inf:OrdersCommands.SaveAllOrdersCommand}">Save All Orders</Button>
这种做法其实也是为了把View和逻辑分开,将事件替换成Command,从而直接对ViewModel进行操作.
由于RoutedCommand在设计时,做了相对多的封装,所以具备的功能也比较多.我们举个例子,在我们删除数据时,常要用MessageBox来提醒用户”确认要删除吗?”
CommandManager提供了4个额外的附加事件,Preview事件可以通过设置ExecutedRoutedEventArgs的Handled为True阻止Command的触发
<Button CommandManager.PreviewExecuted="Button_PreviewExecuted" Command="vm:PersonViewModel.SpeakCommand" CommandParameter="Howdy partner!" Content="Speak" Margin="0,0,6,0" Width="60" />
private void Button_PreviewExecuted(object sender, System.Windows.Input.ExecutedRoutedEventArgs e) { MessageBoxResult result = MessageBox.Show("Are you sure?", "MessageBox", MessageBoxButton.OKCancel); if (result == MessageBoxResult.OK) { e.Handled = false; } else { e.Handled = true; } }
目前prism的DelegateCommand并为提供类似功能,你也可以通过在PreviewMouseLeftButtonDown事件中设置Handled为True阻止其触发.至于CallBack回调更是没有了,这些就需要我们自己想了.这也是在开发中常用到的问题.