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

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


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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章