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> }