1. WPF命令
假設有一個程序,該程序包含了一個應用程序方法PrintDocument()。可以使用4中方式觸發該方法:通過主菜單、右鍵菜單、鍵盤快捷鍵和工具欄按鈕。在應用程序生命週期的特定時刻,需要暫時禁用PrintDocument()功能。這意味着需要禁用兩個菜單命令、一個工具欄命令、忽略快捷鍵。編碼完成這些工作是很麻煩的。更糟糕的是,如果沒有正確完成這項工作,可能會使不同狀態的代碼塊不正確的重疊,導致某個控件在不應該可用時被啓用。調試這類代碼是很枯燥的。
WinForm沒有提供任何機制解決上述問題,而WPF採用命令機制解決了上述問題。
WPF的命令模型增加了兩個特性:
1.將事件委託給適當的命令;
2.使控件的啓用狀態跟命令的狀態保持同步。
2. WPF命令模型
命令模型包含4個重要元素:
1.命令:表示一個程序任務,例如常用的New、Open、Cut、Paste等。也可以是自定義命令的如用於計算數據的Calculate。
2.命令綁定:每個命令可以被多個地方綁定(作用範圍),並且可以在不同地方有不同事件邏輯(具體程序)。例如有兩塊文本編輯區域,Cut、Copy、Paste命令分別對它們綁定。
3.命令源:觸發命令的源。例如按鈕、菜單項、工具欄項等。
4.命令目標:執行命令的目標控件。例如Cut、Copy、Paste命令綁定的目標是一個文本編輯區域。
ICommand:它是命令類的基類接口,其中Execute()將包含應用程序任務邏輯,CanExecute返回命令是否可用的狀態,當狀態改變時會觸發事件CanExecuteChanged。
RoutedCommand:所有WPF命令都是繼承該類。在ICommand基礎上增加了Name(命令名稱標識)、OwnerType(命令所屬類)、InputGestures(命令快捷鍵的集合)。函數裏的target表示執行命令的目標。
RoutedUICommand:用於具有文本的命令。事實上命令庫提供的所有命令都是繼承它的。
public interface ICommand
{
voidExecute(object parameter);
boolCanExecute(object parameter);
eventEventHandler CanExecuteChanged;
}
public class RoutedCommand : ICommand
{
Name;
OwnerType;
InputGestures;
voidExecute(object parameter, IInputElement target);
boolCanExecute(object parameter, IInputElement target);
eventEventHandler CanExecuteChanged;
}
public class RoutedUICommand :RoutedCommand
{
Text;
...
}
3. 命令庫
命令庫中包含了超過100條常用的命令,通過5個靜態類的靜態屬性提供。
命令類 |
示例命令 |
ApplicationCommands |
New、Open、Close、Cut、Copy、Paste、Save、Print |
NavigationCommands |
BrowseForward、BrowseBack、Zoom、Search |
EditingCommands |
AlignXXX、MoveXXX、SelectXXX |
MediaCommands |
Play、Pause、NextTrack、IncreaseVolume、Record、Stop |
ComponentCommands |
MoveXXX、SelectXXX、ScrollXXX、ExtendSelectionXXX |
例如:ApplicationCommands.Open是一個RoutedUICommand的靜態變量。根據綁定的源的不同決定在用戶界面什麼地方觸發。命令庫中的很多命令都有默認的快捷鍵綁定,如Open命令的InputGestures包含了Ctrl+O,OwnerType是ApplicationCommands。
4. 執行命令
很多控件都實現了ICommandSource接口,ICommandSource包含三個屬性:Command、CommandParameter、CommandTarget。也就是說很多控件都有這三個屬性。
Command:它連接一個已存在的命令,例如命令庫的Open等,或我們自定義的命令。
CommandParameter:希望隨命令一起發送的數據。
CommandTarget:命令目標。
例如:Command連接到ApplicationCommands.Open的代碼:
<ButtonCommand="ApplicationCommands.Open">Open</Button>
或者省略命令庫的類名:
<ButtonCommand="Open">Open</Button>
其中Button就是命令源。
在XAML裏綁定命令:
<Grid Name=”grid1”>
<Grid.CommandBindings>
<CommandBinding Command="ApplicationCommands.Open"Executed="CommandBinding_Executed"></CommandBinding>
</Grid.CommandBindings>
<Button Command="ApplicationCommands.Open">Open</Button>
</Grid>
在C#代碼裏綁定命令:
CommandBinding binding = newCommandBinding(ApplicationCommands.Open);
binding.Executed +=CommandBinding_Executed;
grid1.CommandBindings.Add(binding);
可見,命令綁定的主要是內容包括兩個:綁定一個事件處理函數,綁定一個區域(grid1)。
事件處理函數爲:
private void CommandBinding_Executed(objectsender, ExecutedRoutedEventArgs e)
其中通過參數e可以傳遞很多信息。如綁定的命令是什麼、命令的參數等。
如果命令源是菜單項。則連接命令後,會把命令的Text賦值給菜單項的文本,並把快捷鍵加入菜單項。
<Menu>
<MenuItemHeader="File">
<MenuItem Command="Open"></MenuItem>
</MenuItem>
</Menu>
最初的例子:
現在我們回到最初的問題,當多個控件對應相同的命令時,命令的禁用功能會影響到所有控件的禁用狀態。
下面的程序包含一個菜單欄,一個工具欄和一個TextBox。平時Save爲灰色不可用。當TextBox的文本內容變化時,啓用Save功能。
首先我們創建命令綁定:
CommandBinding binding = newCommandBinding(ApplicationCommands.Save);
binding.Executed += Save_Executed;
binding.CanExecute +=Save_CanExecute;
stackpanel1.CommandBindings.Add(binding);
綁定的區域是stackpanel,也就是說stackpanel裏所有連接到該命令的子控件都執行Executed事件處理函數,並且它們的啓用狀態受CanExecute的影響。
我們定義一個布爾變量isDirty表示如果TextBox的文本內容如果改變了沒保存就是“dirty”。bool isDirty = false;
void Save_CanExecute(object sender,CanExecuteRoutedEventArgs e)
{
e.CanExecute =isDirty;
}
void Save_Executed(object sender,ExecutedRoutedEventArgs e)
{
//do something
isDirty = false;
}
void TextBox_TextChanged_1(object sender,TextChangedEventArgs e)
{
isDirty = true;
}
XAML代碼如下:
<StackPanel Name="stackpanel1">
<Menu>
<MenuItem Header="File">
<MenuItemHeader="Open"></MenuItem>
<MenuItemHeader="Save" Command="Save"></MenuItem>
</MenuItem>
</Menu>
<ToolBar Name="toolbar1">
<Button>Open</Button>
<Button Command="Save">Save</Button>
<Button Click="Button_Click_1">手動禁用Save</Button>
</ToolBar>
<TextBox Height="100"TextWrapping="WrapWithOverflow" Text="text"AcceptsReturn="True"TextChanged="TextBox_TextChanged_1"/>
</StackPanel>
上面的代碼可以很好的滿足我們的需求,但是在更深層次上並不太好理解。TextBox並沒有連接到Save命令,它只是單純的修改isDirty的值而已,Save_CanExecute就會被觸發。看起來就好像是Save_CanExecute經常會被觸發的樣子。事實上只要stackpanel1裏的任意控件狀態變化都會觸發stackpanel1綁定的所有命令的CanExecute,而不論其是否連接到該命令。
5. 內置命令
一些文本輸入控件(TextBox)具有內置的命令處理事件(Cut、Copy、Paste等)。所以我們可以在TextBox裏用鍵盤組合鍵來複制(Ctrl+C)粘貼(Ctrl+V),而我們並沒有寫任何相關代碼。此外,我們可以在菜單欄和工具欄裏使用類似下面的代碼,就可以實現對應的功能。
<MenuItem Header="Cut" Command="Cut"></MenuItem>
上面代碼會綁定TextBox的內置事件,因此不用我們自己寫Cut的詳細實現。然而,只能在菜單欄和工具欄使用纔有效,因爲菜單欄和工具欄默認實現了CommandTarget屬性爲當前獲取焦點的控件。也就是說當前TextBox獲取焦點,菜單欄的CommandTarget就指定了TextBox。如果我們想要在菜單欄和工具欄之外的控件裏實現Cut功能怎麼辦呢?可以用下面的代碼:
<Button Command="Cut"CommandTarget="{Binding ElementName=textbox}"> Cut</Button>
上面代碼需要手動指定CommandTarget爲TextBox,這樣就可以實現點擊按鈕來剪切TextBox文本的功能了。
6. 自定義命令
通過實例化一個新的RoutedUICommand對象來實現自定義命令。最好的方式是模仿命令庫的方式創建靜態命令。
下面看一個例子:
模仿命令庫裏的命令,我們自定義一個名稱爲MyCommands.MyCalculate的命令,它的Text是"My Calculate",Name是"MyCalculate",對應的快捷鍵是Ctrl+T。具體代碼如下:
public class MyCommands
{
private static RoutedUICommand myCalculate;
static MyCommands()
{
InputGestureCollection inputGestures = new InputGestureCollection();
inputGestures.Add(new KeyGesture(Key.T, ModifierKeys.Control, "Ctrl+T"));
myCalculate = new RoutedUICommand("My Calculate","MyCalculate", typeof(MyCommands), inputGestures);
}
public static RoutedUICommand MyCalculate
{
get { return myCalculate; }
}
}
我們打算用這個命令完成一個簡單的數學計算任務——加法運算。
XAML代碼如下:
<Windowx:Class="WpfApplication10.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication10"
Title="MainWindow"Height="350" Width="525">
<Grid Height="Auto"VerticalAlignment="Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"></ColumnDefinition>
<ColumnDefinition Width="30"></ColumnDefinition>
<ColumnDefinition Width="100"></ColumnDefinition>
<ColumnDefinition Width="30"></ColumnDefinition>
<ColumnDefinition Width="100"></ColumnDefinition>
<ColumnDefinition Width="100"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.CommandBindings>
<CommandBinding Command="local:MyCommands.MyCalculate"Executed="CommandBinding_Executed_1"CanExecute="CommandBinding_CanExecute_1"></CommandBinding>
</Grid.CommandBindings>
<TextBox Grid.Column="0"Height="30" Margin="3" Name="textbox1"></TextBox>
<LabelGrid.Column="1">+</Label>
<TextBox Grid.Column="2"Height="30" Margin="3"Name="textbox2"></TextBox>
<LabelGrid.Column="3">=</Label>
<TextBox Grid.Column="4"Height="30" Margin="3"Name="textbox3"></TextBox>
<Button Grid.Column="5"Command="local:MyCommands.MyCalculate">Calculate</Button>
</Grid>
</Window>
具體的事件響應函數代碼如下:
<p>private voidCommandBinding_Executed_1(object sender, ExecutedRoutedEventArgs e)</p><p> {</p><p> int value = Int32.Parse(textbox1.Text)+ Int32.Parse(textbox2.Text);</p><p> textbox3.Text = value.ToString();</p><p> }</p><p> </p><p> private voidCommandBinding_CanExecute_1(object sender, CanExecuteRoutedEventArgs e)</p><p> {</p><p> if (textbox1.Text == ""|| textbox2.Text == "")</p><p> e.CanExecute = false;</p><p> else</p><p> e.CanExecute = true;</p> }