WPF学习(第九章)命令

来源:互联网 发布:mac怎么切换独立显卡 编辑:程序博客网 时间:2024/06/05 15:05

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

 

0 0
原创粉丝点击