WPF - Command Property、CommandBinding、ApplicationCommands深入探討
最近因為公司需求,所以打算把WPF看過一遍,而最近看到Command Property( Command屬性 )覺得還滿好玩的,所以趕快把它紀錄下來( 沒紀錄,我大概過幾天就忘記了吧… )。
Command Property
我們平常在寫WPF的時候,Button算是最常用到的其中一個Control,通常,我們會點兩下Button,然後就會開始撰寫Click的事件,這部份大概沒甚麼問題;但假設今天我們的程式要處理一個貼上的功能,那會觸發到貼上這個事件的地方就很多了,Menu上面可能會有貼上,滑鼠右鍵可能會有貼上,這個Button按下去也可能需要貼上,鍵盤上也有Ctrl + V,甚至USB的RFID裝置可能也會觸發貼上,這時候我們會怎麼寫,Menu一個事件,Button一個事件,還要偵測Ctrl + V有沒有按下去等等等等,那有沒有比較好的做法呢?
答案是有的 ( 如果沒有,也不會有這篇了 ),在Button裡面有一個Command Property ( Command屬性),在ButtonBass類別或是MenuItem類別都有這個屬性 ( 當然不是每個類別都有這種屬性 ),而WPF裡面的其中一個Control,"Button"就是繼承於ButtonBass這個類別的,那這個屬性是做甚麼的呢?這個屬性可以對應到相關的命令,當按下Button時就可以執行特定的"命令"。
ApplicationCommands靜態類別
談到命令,我們先必須談談ApplicationCommands ( 當然還有ComponentCommands、MediaCommands、NavigationCommands、EditingCommands );這個類別定義了很多標準的命令,例如Copy、Paste等等,所以當按下Ctrl + v、滑鼠右鍵的貼上,USB裝置的貼上,我們不再需要一個一個地去做偵測,這些統一都由此標準做處理,所以今天我們要處理貼上,我們可以使用ApplicationCommands.Paste,他就代表著這是一個"貼上"的命令。
但要注意的是,這只是命令,你可以把他想像成,程式只知道現在你觸發了Ctrl+v,但完全不知道後面要幹嘛,其實ApplicationCommands裡面的都只有定義,不會有後續的操作,所以我這邊才會使用"命令"這個詞,而不是"事件"這個詞。
簡單的說,ApplicationCommands只是把Ctrl+v這些動作封裝起來,但按下Ctrl+v後,程式不會自己幫我們貼上任何東西,還是要我們來撰寫動作。( 例如從剪貼簿取出來,然後貼到哪裡去。 )
所以我們就可以將ApplicationCommands.Paste 指定給 Command。
btn.Command = ApplicationCommands.Paste;
Command Property是一個ICommand型別,而ApplicationCommands.Paste會傳回一個RoutedUICommand型別,至於為何給Command Property參照呢?我想大家應該都猜的到,其實RoutedUICommand實作了ICommand。
所以到這邊,我們的Btuuon按下去,就會有貼上這個動作了,也就是等同於按下Ctrl+v。
CommandBinding類別
這樣就結束了嗎?,當然還沒,雖然我們Button已經有和"貼上"這個命令相關聯了,但是實際上,程式還是不會有任何動作的,程式只知道你要執行貼上這個命令,但程式根本不知道要做甚麼。( 我們甚至可以讓程式受貼上之命令,但執行的卻是複製動作。)
所以,我們現在要將"貼上"這個命令和事件處理器做個關聯,而這種關聯,其實在WPF就叫做繫結,只是繫結有很多種,這是其中的一種。
而CommandBinding就是用來處理ApplicationCommands和事件處理器繫結的類別,在Windows類別下面,有一個CommandBindings Property,他是一個CommandBindingCollection型別,看到Collection就可以發現,CommandBings Protperty可以存放多個CommandBinding,而每一個CommandBinding就可以繫結一個命令,如下,他讓ApplicationCommands.Cut繫結了cutOnExcute事件處理器,所以如果Button的Command有參考到ApplicationCommands.Cut,按下Button時,就會執行cutOnExcute這個事件處理器。
CommandBinding cb = new CommandBinding(ApplicationCommands.Cut, cutOnExcute);
另外,CommandBinding還有一個滿特別的事件叫做CanExecute,我們可以定義此事件處理器,讓程式幫我們檢查並自動Disable Button,例如我要貼上去的地方,可能只能貼字串、因為剪貼簿裡面目前可能是圖片,也有可能我要複製到剪貼簿裡面的東西只允許文字,所以當目標為圖片的時候,就會自動Disable Button,如下。
cb.CanExecute += cutCanExcute;
然後,我們再針對cutCanExcute進行事件處理器的撰寫,後面會有完整程式碼可以看。
而這就是CommandBinding的作用。
來寫程式吧!!
今天的範例,你也可以把它當作是Button的一個進階應用 ( 當然,繼承於MenuItem下面的Control也可以這樣使用喔!! ),範例很簡單,就是按下Button後,會更改Windows Title的標題。
程式執行起來是這樣,超簡潔的=w=。
以下是程式碼,這裡我故意使用剪下命令,但是實際上我卻是給他執行貼上的效果;另外是cutCanExcute這個事件處理器,他在界面發生改變後(如焦點,按鍵變化,或者按鈕再次被點擊),就會觸發,而在偵錯模式的時候,因為焦點一直改變,所以會一直進入此事件;我們利用cutCanExcute事件來控制Button是否可以按下,其他部分就差不多如上面講的。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Input; namespace WpfDemo { public class BindCommand : Window { [STAThread] public static void Main() { Application app = new Application(); app.Run(new BindCommand()); } public BindCommand() { //方便解說,就不使用XMAL了 Title = "按下Button就會改變我"; //這裡是Button定義,就如同XMAL的Button Button btn = new Button(); btn.HorizontalAlignment = HorizontalAlignment.Center; btn.VerticalAlignment = VerticalAlignment.Center; //Button定義完 btn.Command = ApplicationCommands.Cut; //定義Button會執行"剪下"的命令 btn.Content = ApplicationCommands.Cut; //Button的文字為系統的"剪下"字串 Content = btn; //利用建構式,一次把"命令"、執行的事件處理器和CanExcute定義完。 CommandBinding cb = new CommandBinding(ApplicationCommands.Cut, cutOnExcute, cutCanExcute); //別忘了Windows的CommandBindings要加上CommandBinding才會有效用喔! this.CommandBindings.Add(cb); } void cutOnExcute(object sender, ExecutedRoutedEventArgs args) { //從剪貼簿將文字放到Title Title = Clipboard.GetText(); } //只有界面發生改變後(如焦點,按鍵變化,或者按鈕再次被點擊),WPF才會進行CanExecute事件。 //如果偵錯模式,則因為焦點一直變動,所以會一直進入此事件。 void cutCanExcute(object sender, CanExecuteRoutedEventArgs args) { //利用剪貼簿的ContainsText方法來判斷,是否為文字, //是的話會傳回True, //而這邊CanExecuteRouredEventArgs的CanExecute會自動將Button enable或是disable args.CanExecute = Clipboard.ContainsText(); } } }
當按下Cut的時候,就會將剪貼簿的文字貼到標題,同理,如果按下Ctrl + x 也會有同樣效果。
以上,希望能讓大家對此東西,有更深的一層領悟。