命令与事件类似,事件用来发布传播一些消息,消息到达接收者,事件的使命就完成了,至于如何响应事件送来的消息事件并不做规定;而命令一旦发出,所有的命令目标都必须执行这个命令,二者的区别就在命令具有约束力而事件没有。
命令的基本元素
- 命令(Command):WPF的命令实际就是实现了ICommand接口的类,平时使用最多的是RotuedCommand类;
- 命令源(Command Source):命令发送者,是实现类ICommandSource接口的类,例如Button、MenuItem、ListBoxItem等;
- 命令目标(Command Target):命令将作用在谁身上,命令目标必须实现了IInputElement接口;
- 命令关联(Command Binding):负责把一些外围逻辑与命令关联起来,比如执行前对命令是否可以执行进行判断、命令执行之后进行一些后续工作等。
命令的使用步骤
- 创建命令类:获得一个实现了ICommand的类,如果命令与具体的业务逻辑无关,可以使用WPF类库的RotuedCommand;
- 声明命令实例:创建命令类的实例。一般情况下,程序中的某种操作只需要一个命令实例与之对应即可,比如“保存”命令,可以拿同一个实例去命令所有组件完成保存功能,因此命令常使用单例模式;
- 指明命令源:指定由谁来发送这个命令。同一个命令可以有多个命令源,如保存命令,可以由菜单中的保存项发送,也可以由工具栏的保存图标发送;
- 指定命令目标:命令目标并不是命令的属性而是命令源的属性,指定命令目标是告诉命令源向哪个组件发送命令,无论这个组件是否获得焦点它都会收到这个命令。如果没有为命令源指定目标,则WPF系统认为当前拥有焦点的对象就是命令目标;
- 设置命令关联:指定命令针对用户界面的具体区域,可以添加到命令源的外围控件上。
一个UI组件一旦被命令源盯上,命令源就会不停地向命令目标“投石问路”,命令目标也会不停地发送出可路由的CanExcute附加事件,事件沿着UI元素树向上传递并被命令关联捕捉,命令关联捕捉到这些事件后会把命令能不能发送实时报告给命令。
如果命令被发送出去并到达命令目标,命令目标就会发送Excute附加事件,事件沿着UI元素树向上传递并被命令关联捕捉,命令关联会完成指定的后续任务。
现在我们实现这样一个需求,定义一个命令,使用Button来发送这个命令,当命令到达TextBox时,TextBox的文本会被清空,如果TextBox文本为空则命令不可发送,XAML代码如下:
<Window x:Class="WpfApplication7.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <StackPanel x:Name="stackpanel"> <TextBox x:Name="textBox1" Height="23" Margin="10"></TextBox> <Button x:Name="button1" Content="发送命令" Height="23" Margin="10"></Button> </StackPanel> </Window>
后台处理代码如下:
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); InitCmd(); } private RoutedCommand clearCmd = new RoutedCommand("Clear", typeof(MainWindow)); private void InitCmd() { //指定命令源、绑定快捷键 this.button1.Command = clearCmd; this.clearCmd.InputGestures.Add(new KeyGesture(Key.C, ModifierKeys.Alt)); //指定命令目标 this.button1.CommandTarget = textBox1; //创建命令关联 CommandBinding cb = new CommandBinding(); cb.Command = this.clearCmd; cb.Executed += cb_Executed; cb.CanExecute += cb_CanExecute; //把命令关联安置到外围控件 this.stackpanel.CommandBindings.Add(cb); } void cb_CanExecute(object sender, CanExecuteRoutedEventArgs e) { if (string.IsNullOrEmpty(this.textBox1.Text)) e.CanExecute = false; else e.CanExecute = true; //避免继续向上传而降低程序性能 e.Handled = true; } void cb_Executed(object sender, ExecutedRoutedEventArgs e) { this.textBox1.Clear(); e.Handled = true; } }
可见,使用命令就不需要自己写代码判断Button是否可用,CanExcute事件触发频率比较高,为了避免降低性能,在处理完后需要把e.handled设置为ture;CommandBinding一定要设置在命令目标的外围控件上,不然无法捕捉到CanExcute和Excute事件。
命令参数
WPF有很多命令,如New、Copy等,如果界面上有两个Button,一个用来New一个Teacher档案,一个用来New一个Student档案,该如何解决呢?这时就用到了命令的CommandPrameter属性。
我们使用WPF系统的预置命令New,XAML代码如下:
<Window x:Class="WpfApplication7.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <StackPanel x:Name="stackpanel"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="50"></ColumnDefinition> <ColumnDefinition></ColumnDefinition> </Grid.ColumnDefinitions> <TextBlock Text="Name" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Left"></TextBlock> <TextBox x:Name="textBox2" Grid.Column="1" Height="23" Margin="5"></TextBox> </Grid> <Button x:Name="btnTeacher" Content="Teacher" Command="New" CommandParameter="Teacher" Height="23" Margin="10"></Button> <Button x:Name="btnStudent" Content="Student" Command="New" CommandParameter="Student" Height="23" Margin="10"></Button> </StackPanel> <Window.CommandBindings> <CommandBinding Command="New" CanExecute="CommandBinding_CanExecute" Executed="CommandBinding_Executed"></CommandBinding> </Window.CommandBindings> </Window>
CommandBinding的事件处理代码如下:
private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e) { if (string.IsNullOrEmpty(this.textBox2.Text)) e.CanExecute = false; else e.CanExecute = true; e.Handled = true; } private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e) { string name = this.textBox2.Text; if(e.Parameter.ToString()=="Teacher") { this.textBox2.Text = "Hello Teacher"; } if (e.Parameter.ToString() == "Student") this.textBox2.Text = "Hello Student"; }
两个按钮都使用New命令,但分别使用Teacher和Student作为参数,当TextBox的Text为空时,Button不可用,当输入文本后Button变为可用,单击Button,TextBox会显示不同的内容。