事件的作用是发布、传播一些消息,消息送达接收者,事件的使命也就完成了,至于如何响应事件送来的消息事件并不做规定,每个接收者可以使用自己的行为来响应事件。也就是说,事件不具有约束力。命令与事件的区别就在于命令具有约束力。
1:命令系统的基本元素与关系
1:命令系统的基本元素
WPF的命令系统由几个基本要素构成:
(1)命令(Command):WPF的命令实际上就是实现了ICommand接口的类,平时使用最多的是RoutedCommand类。
(2)命令源(Command Source):即命令的发送者,是实现了ICommandSource接口的类。很多界面元素都是实现了这个接口,其中包括Button、MenuItem、ListBoxItem等。
(3)命令目标(Command Target):即命令将发送给谁,或者说命令将作用在谁身上。命令目标必须是实现了IInputElement接口的类。
(4)命令关联(Command Binding):负责把一些外围逻辑与命令关联在一起。
2:基本元素之间的关系
这些基本元素之间的关系体现在使用的命令过程中。命令的使用大概分为如下几步:
(1):创建命令类:即获得一个实现ICommand接口的类,如果命令与具体业务逻辑无关则使用WPF类库中的RoutedCommand类即可。如果想得到与业务逻辑相关的专有命令,则需要创建RoutedCommand的派生类。
(2)声明命令实例:使用命令时需要创建命令类的实例。
(3)指定命令源:即指定由谁来发送这个命令。
(4)指定命令目标:命令目标并不是命令的属性而是命令源的属性,指定命令目标是告诉命令源向哪个组件发送命令,无论这个组件是否拥有焦点它都会收到这个命令。如果没有为命令源指定命令目标,则WPF系统认为当前拥有焦点的对象就是命令目标。
(5)设置命令关联:WPF命令需要CommandBinding在执行前来帮助判断是不是可以执行、在执行后做一些事件。
在命令目标和命令关联之间还有个微妙的关系。无论命令目标是由程序员指定还是有WPF系统根据焦点所在地判断出来的,一旦某个UI组件被命令“瞄上”,命令源就会不停地向命令目标“投石问路”,命令目标就会不停地发送出可路由的PreviewCanExecute和CanExecute附加事件,事件会沿着UI元素树向上传递并被命令关联所捕捉,命令关联捕捉到这些事件后会把命令能不能发送实时报告给命令。类似的,如果命令被发送出来并到达命令目标,命令目标就会发送PreviewExecuted和CanExecuted两个附加事件,这两个事件也会沿着UI元素树向上传递并被命令关联所捕捉,命令关联会完成一些后续的任务。
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Height="350" Width="525">
<StackPanel x:Name="stackPanel">
<Button x:Name="button1" Content="send Command" Margin="5"/>
<TextBox x:Name="textBoxA" Margin="5,0" Height="100"/>
</StackPanel>
</Window>
namespace WpfApplication1
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
InitializeCommand();
}
//声明并定义命令
private RoutedCommand clearCmd = new RoutedCommand("Clear", typeof(Window));
private void InitializeCommand()
{
//把命令赋给命令源并指定快捷键
this.button1.Command = this.clearCmd;
this.clearCmd.InputGestures.Add(new KeyGesture(Key.C, ModifierKeys.Alt));
//指定命令目标
this.button1.CommandTarget = this.textBoxA;
//创建命令关联
CommandBinding cb = new CommandBinding();
cb.Command = clearCmd;
cb.CanExecute += new CanExecuteRoutedEventHandler(Cb_CanExecute);
cb.Executed += new ExecutedRoutedEventHandler(cb_Execute);
//把命令关联安置在外围控件上
this.stackPanel.CommandBindings.Add(cb);
}
private void cb_Execute(object sender, ExecutedRoutedEventArgs e)
{
this.textBoxA.Clear();
e.Handled = true;
}
private void Cb_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
if (string.IsNullOrEmpty(this.textBoxA.Text))
{
e.CanExecute = false;
}
else
{
e.CanExecute = true;
}
e.Handled = true;
}
}
}
使用命令可以避免自己写代码判断Button是否可用以及添加快捷键;RoutedCommand是一个与业务无关的类,只负责在程序中“跑腿”而并不对命令目标做任何操作;因为CanExecute事件激发频率比较高,为了避免降低性能,在处理完后建议把e.Handler设为true;CommandBinding一定要这是在命令目标的外围控件上,不然无法捕捉到CanExecute和Executed等路由事件。
4:WPF的命令库
命令具有“一处声明、处处使用”的特点,微软在WPF类库里准备了一些便捷的命令库。
(1)ApplicationCommands
(2)ComponentCommands
(3)NavigationCommands
(4)MediaCommands
(5)EditingCommands
它们都是静态类,而命令就是用这些类的静态只读属性以单件模式暴露出来的。
5:命令参数
命令源一定是实现了ICommandSource接口的对象,而ICommandSource有一个属性就是CommandPrameter,如果把命令看做飞向目标的炮弹,那么CommandPrameter就相当于装载在炮弹里的“消息”。
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication2"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.CommandBindings>
<CommandBinding Command="New" CanExecute="New_CanExecute" Executed="New_Executed"/>
</Window.CommandBindings>
<Grid Margin="6">
<Grid.RowDefinitions>
<RowDefinition Height="24"/>
<RowDefinition Height="4"/>
<RowDefinition Height="24"/>
<RowDefinition Height="4"/>
<RowDefinition Height="24"/>
<RowDefinition Height="4"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Text="Name:" VerticalAlignment="Center" HorizontalAlignment="Left" Grid.Row="0"/>
<TextBox x:Name="nameTextBox" Margin="60,0,0,0" Grid.Row="0"/>
<Button Content="New Teacher" Command="New" CommandParameter="Teacher" Grid.Row="2"/>
<Button Content="New Student" Command="New" CommandParameter="Student" Grid.Row="4"/>
<ListBox x:Name="listBoxNewItems" Grid.Row="6"/>
</Grid>
</Window>
namespace WpfApplication2
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void New_Executed(object sender, ExecutedRoutedEventArgs e)
{
string name = this.nameTextBox.Text;
if (e.Parameter.ToString() == "Teacher")
{
this.listBoxNewItems.Items.Add(string.Format("Teacher {0},好好学习", name));
}
if (e.Parameter.ToString() == "Student")
{
this.listBoxNewItems.Items.Add(string.Format("Student {0},天天向上", name));
}
}
private void New_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
if (string.IsNullOrEmpty(this.nameTextBox.Text))
{
e.CanExecute = false;
}
else
{
e.CanExecute = true;
}
e.Handled = true;
}
}
}
6:命令与Binding的结合
控件有很多事件,可以让我们进行各种各样的操作,可控件只有一个Command属性、而命令库中却有数十种命令,这样怎么可能使用这个唯一Command属性来调用那么多的命令呢?答案是:使用Binding。
2:近观命令
一般情况下,程序中使用与逻辑无关的RoutedCommand来跑跑龙套就足够了,但为了使程序的结构更加简洁,我们常需要定义自己的命令。
1:ICommand接口与RoutedCommand
WPF的命令是实现了ICommand接口的类。ICommand接口非常简单,只包含两个方法和一个事件:
(1)Execute方法:命令执行,或者说命令作用于命令目标之上。
(2)CanExecute方法:在执行之前用来探知命令是否可被执行。
(3)CanExecuteChanged事件:当命令可执行状态发生改变时,可激发此事件来通知其他对象。
RoutedCommand就是这样一个实现了ICommand接口的类。RoutedCommand在实现ICommand接口时,并未向Execute和CanExecute方法中添加任何逻辑,也就是说,它是通用的、与具体业务逻辑无关。
2:自定义Command
WPF自带的命令源和CommandBinding就是专门为RoutedCommand而编写的,如果我们想要使用自己的ICommand派生类就必须连命令源一起实现。
参考教材书:深入浅出WPF 刘铁猛 著