WPF 技術拼圖之
<?xml:namespace prefix = o />
Command Binding 機制剖析及應用
*****************************
最近正在學習WPF,因此,有時間時會寫一些小文章,介紹Wpf中一些比較有趣和重要的東西。
學習并應用技術的過程就是一個“技術拼圖”的過程,只有將各個技術碎片拼成一張完整的大圖,才算是“功德圓滿”。
本文就是這張WPF技術全景拼圖中的一小塊。
金旭亮
2008-10-21
*****************************
在 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.Command = MyAppCommands .MyCommand;
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 的路由事件和依賴屬性比較困難。
這也再次驗證了一句老話:新路接在舊路的前頭。
好,這篇文章就寫到這里吧!
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
