天天看點

WPF學習(第九章)指令

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

我們打算用這個指令完成一個簡單的數學計算任務——加法運算。

WPF學習(第九章)指令
WPF學習(第九章)指令

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

繼續閱讀