2011年11月2日

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=。

image

以下是程式碼,這裡我故意使用剪下命令,但是實際上我卻是給他執行貼上的效果;另外是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 也會有同樣效果。

image

以上,希望能讓大家對此東西,有更深的一層領悟。

沒有留言:

張貼留言