在WPF中,有一個非常有意思的Command Binding(命令綁定機制),這種機制在原有的Windows Form中沒有提供。本文設計了一個執行個體,直觀地展示出Command Binding的應用情境,並對其機制進行了剖析。
1 Command Binding有什麼用?
這個機制有何作用?看一下下面這個例子就清楚了(圖 1),此例是由Visual C# 2008 Express建立的標準Wpf應用程式,項目中有一個Window1.xaml作為主表單:
圖 1
可以看到,表單上有一個菜單和一個按鈕,當使用者點擊這兩個控制項時,它們執行相同的功能。
多個控制項執行同一個功能在傳統型應用程式中是非常常見的,比如相同的命令可以通過選擇“菜單”命令或點擊工具列上的特定按鈕執行。
以傳統方式開發這樣的程式,往往需要針對每個控制項的Click事件分別編碼來實現。
然而,在許多時候我們需要同步多個控制項的狀態。比如在一個文字編輯器中 ,當使用者沒有選中任何文本時,菜單中的“Copy”和工具列上的“Copy”按鈕都需要禁用,因此,傳統方式下還必須寫額外代碼來實現這一點。
Command Binding彌補傳統編程方式的缺陷,可以協助我們以很少的代碼實現同樣的功能。
2 實現Command Binding
讓我們修改樣本以利用命令綁定機制。
首先,向項目中加入一個MyAppCommands類,其內容如下:
namespace UnderStandCommandBinding
{
public class MyAppCommands
{
public static RoutedUICommand MyCommand = new RoutedUICommand();
}
}
請特別注意其中的RoutedUICommand 類型的欄位MyCommand。它定義了一個將被表單控制項所調用的命令。
在Window1.cs中書寫以下代碼:
partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
CommandBinding cb = new CommandBinding();
cb.Executed += new ExecutedRoutedEventHandler(cb_Executed);
}
void cb_Executed(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show("響應自訂命令MyCommand");
}
}
上述代碼建立一個CommandBinding對象,此對象指明cb_Executed函數響應MyCommand命令(其實是被MyCommand命令 的Execute方法自動調用,這裡借用事件處理機制的相關術語以便於理解,事實上,命令綁定與事件響應不是一回事,簡單地說,命令綁定建構於Wpf的事 件路由機制之上)。
下一步則需要指定表單上的哪幾個控制項用於調用此命令:
partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
//建立命令對象
CommandBinding cb = new CommandBinding();
cb.Command = MyAppCommands.MyCommand;
cb.Executed += new ExecutedRoutedEventHandler(cb_Executed);
//將要執行的命令對象添加到表單的命令對象集合中
this.CommandBindings.Add(cb);
//設定功能表項目和按鈕都執行MyCommand命令
mnuInvokeMyCommand.Command = MyAppCommands.MyCommand;
btnInvokeMyCommand.Command = MyAppCommands.MyCommand;
}
void cb_Executed(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show("響應自訂命令MyCommand");
}
}
現在運行程式,可以發現,單擊功能表項目和按鈕都會調用cb_Executed函數。
3 使用XAML實現DataBinding
前面使用代碼實現了資料繫結。現在,改用XAML實現相同的功能。
首先,刪除Window1建構函式中的代碼,只留下cb_Executed()函數:
partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
void cb_Executed(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show("響應自訂命令MyCommand");
}
}
回到Window1.xaml檔案中,首先,在其Window元素中加入對本項目命名空間的引用(其目的是在XAML中使用代碼中的類):
<Window x:Class="UnderStandCommandBinding.Window1" ……
xmlns:myapp="clr-namespace:UnderStandCommandBinding">
……
</Window》
然後,修改MenuItem的聲明,加上Command屬性:
<MenuItem Header="Invoke My Command" Name="mnuInvokeMyCommand" Command="MyAppCommands.MyCommand" />
再修改Button的聲明,也加上Command屬性:
<Button Name="btnInvokeMyCommand"
Command="myapp:MyAppCommands.MyCommand">
Invoke My Command
</Button>
最後,給最頂層元素Window添加命令綁定:
<Window.CommandBindings>
<CommandBinding
Command="myapp:MyAppCommands.MyCommand"
Executed="cb_Executed" />
</Window.CommandBindings>
現在運行樣本,可以看到運行效果與代碼一樣。
由此可知,我們可使用XAML以比使用C#編寫代碼方式更少的代碼量實現同樣的功能。
4 實現控制項同步
現在開始展示一下命令綁定的神奇之處。向表單上加入一個CheckBox(取名chkActivateCommand),我們將用它來控制是否可以執行MyCommand命令(圖 2):
圖 2
修改命令綁定對象:
<Window.CommandBindings>
<CommandBinding Command="myapp:MyAppCommands.MyCommand" Executed="cb_Executed" CanExecute="CommandBinding_CanExecute" />
</Window.CommandBindings>
在Window.xaml.cs中添加一個新函數:
private void CommandBinding_CanExecute(
object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = chkActivateCommand.IsChecked.Value;
}
其餘代碼不需要動。OK。現在運行一下樣本程式:
圖 3
可以看到,現在按鈕和功能表命令的啟用與由CheckBox控制了。我們甚至沒有寫一行代碼去同步這三個控制項的狀態!
5 向命令傳送參數
添加一個文字框txtPara用於輸入參數。
<TextBox Name="txtPara" Background="Wheat" />
控制項的CommandParameter屬性可用來向命令對象提供參數,我們使用資料繫結機制將文字框中的文本作為命令參數:
<MenuItem Header="Invoke My Command" Name="mnuInvokeMyCommand" Command="myapp:MyAppCommands.MyCommand" CommandParameter="{Binding ElementName=txtPara,Path=Text}" />
……
<Button Height="23" Name="btnInvokeMyCommand" HorizontalAlignment="Center" Command="myapp:MyAppCommands.MyCommand" CommandParameter="{Binding ElementName=txtPara,Path=Text}" >Invoke My Command</Button>
傳入的參數會被命令響應函數的ExecutedRoutedEventArgs參數接收,樣本程式中修改後的命令響應函數如下:
void cb_Executed(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show(e.Parameter.ToString());
}
運行螢幕 4:
圖 4
在文字框中輸入文字,點擊按鈕或功能表項目,將彈出一個訊息框顯示使用者輸入的文本。
我們的樣本到此結束。
6 Command Binding有何用處?
根據這個例子,大家體會到了Command Binding的好處了嗎?
仔細看一下MyAppCommands類,就發現我們可以向其中添加多個自訂的命令,只需指定好這些命令對象的Execute和CanExecute兩 個事件響應屬性,就可以方便地設定表單上的控制項執行特定的命令(通過其Command屬性),而且WPF架構會自動協助我們同步這些控制項的狀態!
這樣一來,我們就得到了一種比較模組化的Wpf傳統型應用程式結構:
(1) 將應用程式要執行的功能封裝到中介層組件或獨立的類中;
(2) 建立一個專用儲存命令對象的類(比如本例中的MyAppCommands類),在此類中集中存放應用程式要執行的命令對象。
(3) 在表單中建立命令綁定對象,為每個命令對象建立響應函數,並將其添加到Window對象的CommandBindings屬性中,之後,即可將控制項與命令對象相互綁定。
這種架構的優點是增強了代碼的隔離性,並減少了總體的代碼量:
(1) 商務邏輯代碼在中介層中;
(2) 專用於提供特定命令對象的類實際上成了一個功能調用介面層,可以很方便地增刪調整程式提供的功能。
(3) 現在控制項基本上不需要寫大量的代碼響應特定的事件和同步多個控制項的狀態。
7 剖析Command Binding機制
Command Binding機制主要與兩個介面密切相關:一個是ICommand,用於定義命令對象類,另一個是ICommandSource,用於定義可發出“調用 命令”的原始檔控制。RoutedUICommand類實現了ICommand介面,通常用此類的對象表示應用程式需要執行的命令。而Button等控制項則實 現了ICommandSource介面,並且擁有此介面所定義的Command和CommandParameter屬性,可用於調用命令。
Wpf程式主要通過以下兩種對象實現命令綁定:CommandBinding對象將命令對象綁定到特定的命令處理函數(樣本就是這樣的),當執行命令對象 時,這些命令處理函數被調用(命令對象和命令響應函數的這種關係稱為“綁定”)。InputBinding對象將命令對象與特定的按鍵關聯起來,當使用者按 下特定鍵時,相關聯的命令對象被調用。
許多Wpf控制項都擁有CommandBindings和InputBindings兩個集合屬性,其中成員就是CommandBinding對象和 InputBinding對象,從而使這些控制項能夠響應特定的命令,比如TextBox就是這樣,它在內部包容了響應標準的Cut/Copy/Paste 命令對象的代碼,而wpf預設提供的ApplicationCommands類則定義了Cut/Copy/Paste命令對象,這就是為何我們可以不寫一 行代碼直接在TextBox中使用Ctl-C等命令的緣故。
Command Binding機制的內部機制比較複雜,簡單地說:
執 行Command對象時,會引發PreviewExecute/Execute和PreviewCanExecute/CanExecute事件,這些事 件都是RoutedEvent,可以在Wpf元素樹中傳播(從樹葉到樹根方向稱為Bubble,從樹根到樹葉方向稱為tunnel),正是這種事件路由機 制構築了整個Command Binding機制運轉起來的根基。
再進一步追問一下,事件路由是如何?的?這就涉及到Wpf另一個非常重要的新特性——相依性屬性。
有關路由事件和相依性屬性的剖析,將會在另外的文章中進行介紹。需要指出的是,如果您對.NET 2.0所提供的委託、事件等還不清楚,那麼,要弄明白wpf的路由事件和相依性屬性比較困難。
這也再次驗證了一句老話:新路接在舊路的前頭。