基於wpf的相關設計問題-Command的使用
這篇來討論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}">
二.由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來提醒使用者”確認要刪除嗎?”
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回撥更是沒有了,這些就需要我們自己想了.這也是在開發中常用到的問題.