代码改变世界

基于wpf的相关设计问题-Command的使用

2009-02-26 16:05  Clingingboy  阅读(7200)  评论(1编辑  收藏  举报

     这篇来讨论Command基于ViewModel的基本使用.

以prism内置Command Demo为例子,效果图如下

image

View相对应的ViewModel

image

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}">

   

二.由prism提供的DelegateCommand和CompositeCommand


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进行操作.

三.阻止Command的触发

 

由于RoutedCommand在设计时,做了相对多的封装,所以具备的功能也比较多.我们举个例子,在我们删除数据时,常要用MessageBox来提醒用户”确认要删除吗?”

image

CommandManager提供了4个额外的附加事件,Preview事件可以通过设置ExecutedRoutedEventArgs的Handled为True阻止Command的触发

image

<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回调更是没有了,这些就需要我们自己想了.这也是在开发中常用到的问题.