1. 程式人生 > >基於wpf的相關設計問題-Command的使用

基於wpf的相關設計問題-Command的使用

這篇來討論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回撥更是沒有了,這些就需要我們自己想了.這也是在開發中常用到的問題.