?
WPF的命令是經常使用的,在MVVM中,RelayCommand更是用得非常多,但是命令的本質究竟是什么,有了事件為什么還要命令,命令與事件的區別是什么呢?MVVM里面是如何包裝命令的呢?命令為什么能夠觸發呢?帶著這些疑問,我們深入講解下命令:
首先看看命令系統的幾個基本元素:
1) 命令(Command):實現了ICommand接口的類,用得最多的是RoutedCommand.
ICommand的成員:
event EventHandler CanExecuteChanged;
bool CanExecute(object parameter);確定此命令能否執行的方法
void Execute(object parameter);執行命令調用的方法
?
2)?命令源(Command Source):即命令的發送者,是實現了ICommandSource接口的類,很多界面元素都實現了這個接口,其中包括Button, MenuItem, ListBoxItem等。
ICommandSource成員:
?????? ICommand Command{get;}???獲取將在調用命令源時執行的命令
object CommandParameter{get; }?命令參數。
??????????? IInputElement CommandTarget{get;}?將在其上執行命令的對象。
?
3)命令目標(Command Target):即命令講發送給誰,或者說命令將作用在誰身上。命令目標必須是實現了IInputElement接口的類。
4)命令關聯(Command Binding):負責把一些外圍邏輯與命令關聯起來,比如執行之前對命令是否可以執行進行判斷、命令執行之后還有哪些后續工作。
CommandBinding的成員:
? public ICommand Command{get; set;}?與這個CommandBinding關聯的ICommand。
public event CanExecuteRoutedEventHandler CanExecute;
???????public event ExecutedRoutedEventHandler Executed;
?????? public event CanExecuteRoutedEventHandler PreviewCanExecute;
?????? public event ExecuteRoutedEventHandler PreviewExecuted;
?下面先看看一個命令方面的例子:
代碼:
1 private RoutedCommand clearCmd = new RoutedCommand("Clear", typeof(MainWindow)); 2 private void InitializeCommand() 3 { 4 this.button1.Command = clearCmd; 5 this.clearCmd.InputGestures.Add(new KeyGesture(Key.C, ModifierKeys.Alt)); 6 this.button1.CommandTarget = this.textBoxA; 7 8 CommandBinding cb = new CommandBinding(); 9 cb.Command = this.clearCmd; 10 cb.CanExecute += new CanExecuteRoutedEventHandler(cb_CanExcecute); 11 cb.Executed += new ExecutedRoutedEventHandler(cb_Executed); 12 13 this.stackPanel.CommandBindings.Add(cb); 14 15 } 16 17 void cb_CanExcecute(object sender, CanExecuteRoutedEventArgs e) 18 { 19 if (string.IsNullOrEmpty(this.textBoxA.Text)) 20 { 21 e.CanExecute = false; 22 } 23 else 24 { 25 e.CanExecute = true; 26 } 27 e.Handled = true; 28 } 29 30 void cb_Executed(object sender, ExecutedRoutedEventArgs e) 31 { 32 this.textBoxA.Clear(); 33 e.Handled = true; 34 }
UI上一個按鈕,一個文本框,上級UI是StackPanel。
實現的功能是當文本框里面沒有內容的時候,按鈕是不是能的,當有內容的時候,按鈕使能。
我們來對照著命令的幾大要素,來分析下這個例子:
首先一個clearCmd的路由命令,RoutedCommand,實現了ICommand接口。
然后就是命令源,這里是Button,它實現了ICommandSource接口,在這里把clearCmd路由命令賦值給了其成員Command,把文本框賦值給了命令的目標。
然后命令目標就是文本框,實現了IInputElement接口
最后就是CommandBinding,這里給StackPanel的CommandBindings賦值,其中CommandBinding的Command賦值為clearCmd,并且定義兩個事件驅動程序,用來處理CanExecute,Execute。
另外還有快捷鍵的設置方式,這不是重點,我們重點看看命令的執行模式到底如何?工作原理是什么?
通過調查,我發現是這樣的:
首先命令源會一直查詢其Command,如果有就執行命令,然后就連接到了命令目標,命令目標激發路由事件,然后在外圍的控件的CommandBinding監控下捕捉相關的路由事件,然后就會調用相關的事件處理程序。
對應這個例子,是這樣的,button作為命令源,賦值了clearCmd命令,然后通過查詢并執行這個命令,命令連接到命令目標文本框,然后文本框激發出路由事件clearCmd,然后安裝在StackPanel的CommandBinding監控下,如果是找到的命令的能否執行命令,就執行能否執行命令的事件處理函數,這里的能否執行返回的值直接決定了命令源是否可用。如果找到的命令式執行命令,就執行執行命令的處理函數,這里執行就把文本框清空。為了提高效率,一般都要e.Handled?=?True.
我們可以看出,真正起作用的是CommandBinding,命令源的目的是告訴命令目標發了命令,還有讓命令目標激光路由事件,命令目標的目的就是發生路由事件,CommandBinding賦值監聽命令,執行命令。
用通俗的話說,命令源就相當于火炮,命令相當于炮彈,命令目標相當于跑到要打的目標,命令關聯就詳單與偵察兵,在打炮彈之前的觀察敵情,以及打掃戰場等事情。
命令源會不斷的像命令目標投石問路,命令目標就會不斷的發送路由事件PreviewCanExcecute和CanExcecute附加事件,命令被發送出來并達到命令目標,命令目標就會發送PreviewExecuted和Executed附加事件。命令關聯捕捉到后,就會執行一些任務了。
?
我們再來看看另外一個例子,我們自定義命令的例子:
1 /// <summary> 2 /// CustomRoutedCommand.xaml 的交互邏輯 3 /// </summary> 4 public partial class CustomRoutedCommand : UserControl,IView 5 { 6 public CustomRoutedCommand() 7 { 8 InitializeComponent(); 9 } 10 11 public bool IsChanged { get; set; } 12 public void SetBinding() { } 13 public void Refresh(){} 14 public void Save() { } 15 public void Clear() 16 { 17 this.textBox1.Clear(); 18 this.textBox2.Clear(); 19 this.textBox3.Clear(); 20 this.textBox4.Clear(); 21 } 22 } 23 24 public interface IView 25 { 26 bool IsChanged { get; set; } 27 void SetBinding(); 28 void Refresh(); 29 void Clear(); 30 void Save(); 31 } 32 33 public class ClearCommand : ICommand 34 { 35 public bool CanExecute(object parameter) 36 { 37 throw new NotImplementedException(); 38 } 39 40 public event EventHandler CanExecuteChanged; 41 42 public void Execute(object parameter) 43 { 44 IView view = parameter as IView; 45 if (view != null) 46 { 47 view.Clear(); 48 } 49 //throw new NotImplementedException(); 50 } 51 } 52 53 public class MyCommandSource : UserControl, ICommandSource 54 { 55 public ICommand Command { get; set; } 56 public object CommandParameter { get; set; } 57 public IInputElement CommandTarget { get; set; } 58 59 protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) 60 { 61 base.OnMouseLeftButtonDown(e); 62 if (CommandTarget != null) 63 { 64 this.Command.Execute(CommandTarget); 65 } 66 } 67 68 }
也是點擊一個按鈕把4個文本框全部清空。
同樣的我們來分析下各個要素:
命令:ClearCommand,?實現了ICommand接口,這里的命令執行方法,是執行命令參數的清除方法。
命令源:MyCommandSource,這是一個用戶控件,實現了ICommandSource接口,左鍵按鈕里面如果有命令目標,就執行命令方法。
命令目標是一個窗體,他實現了IView接口,接口里面有個清除方法。
這里沒有CommandBinding。實際執行的代碼就是命令目標的IView接口的方法。
當我們點擊按鈕的時候,觸發了命令的執行,命令執行調用IView接口的方法。
這個自定義的過程要比上面那個例子要好理解一點。
上面的那個例子存在命令源,路由命令和CommandBinding三者之間的關系,命令并不真正執行邏輯代碼,是靠CommandBinding來實現邏輯的,當我們真正自定義的命令的時候,如果想簡單的使用命令,我們可以把邏輯放到命令里面去,這樣便于管理,以上的自定義命令的例子就是這樣。
至于MVVM里面的RelayCommand命令,一樣也是實現的ICommand接口。當我們把命令通過binding賦值給命令源的Command后,當命令源的命令觸發的時候,就執行RelayCommand的方法,這個方法是一個委托方法,這樣我們就可以通過Binding來把聯系了控件和控件要執行的行為。
命令跟事件可能否是在一起被觸發的,比如在ButtonBase的OnClick方法里面是這樣的:
protected virtual void OnClick()
{
??????? RoutedEventArgs e = new RoutedEventArgs(ButtonBase.ClickEvent, this);
?????? base.RaiseEvent(e);
?????? CommandHelpers.ExecuteCommandSource(this);
}
可以看出在Click里面,先激發路由事件,再執行命令。
總結:對于命令而言,我們說的幾大要素,命令,命令源,命令目標,命令關聯,在路由命令中,一般都是存在的,命令在命令源的激發下,到命令目標,有可能沒有命令目標,通過命令關聯來監控并執行邏輯方面的事情。但是我們一般使用比如MVVM里面的RelayCommand,一般邏輯代碼都是放在命令里面的,一般沒有命令目標及命令關聯。
到目前為止,雖然大致對命令有了個了解,但是對于WPF預定義的一些命令沒有完全理解,以及對于命令的好處也沒有完全理解,以后隨著使用越來越多,再去總結吧。
http://files.cnblogs.com/files/monkeyZhong/RoutedCommandEg.zip
?