Command Binding机制剖析及应用

系统 1774 0

WPF 技术拼图之

<?xml:namespace prefix = o />

Command Binding 机制剖析及应用

*****************************

最近正在学习WPF,因此,有时间时会写一些小文章,介绍Wpf中一些比较有趣和重要的东西。

学习并应用技术的过程就是一个“技术拼图”的过程,只有将各个技术碎片拼成一张完整的大图,才算是“功德圆满”。

本文就是这张WPF技术全景拼图中的一小块。


金旭亮

2008-10-21


*****************************



WPF 中,有一个非常有意思的 Command Binding (命令绑定机制),这种机制在原有的 Windows Form 中没有提供。本文设计了一个实例,直观地展示出 Command Binding 的应用场景,并对其机制进行了剖析。

1 Command Binding 有什么用?

这个机制有何作用?看一下下面这个例子就清楚了( 1 ),此例是由 Visual C# 2008 Express 创建的标准 Wpf 应用程序,项目中有一个 Window1.xaml 作为主窗体:


Command Binding机制剖析及应用

1

从图中可以看到,窗体上有一个菜单和一个按钮,当用户点击这两个控件时,它们执行相同的功能。

多个控件执行同一个功能在桌面应用程序中是非常常见的,比如相同的命令可以通过选择“菜单”命令或点击工具栏上的特定按钮执行。

以传统方式开发这样的程序,往往需要针对每个控件的 Click 事件分别编码来实现。

然而,在许多时候我们需要同步多个控件的状态。比如在一个文本编辑器中 ,当用户没有选中任何文本时,菜单中的“ Copy ”和工具栏上的“ Copy ”按钮都需要禁用,因此,传统方式下还必须写额外代码来实现这一点。

Command Binding 弥补传统编程方式的缺陷,可以帮助我们以很少的代码实现同样的功能。

2 实现 Command Binding

让我们修改示例以利用命令绑定机制。

首先,向项目中加入一个 MyAppCommands 类,其内容如下:

namespace UnderStandCommandBinding

{

public class MyAppCommands

{

public static RoutedUICommand MyCommand = new RoutedUICommand ();

}

}

请特别注意其中的 RoutedUICommand 类型的字段 MyCommand 。它定义了一个将被窗体控件所调用的命令。

Window1.cs 中书写以下代码:

partial class Window1 : Window

{

public Window1()

{

InitializeComponent();

CommandBinding cb = new CommandBinding();

cb.Command = MyAppCommands .MyCommand;

cb.Executed += new ExecutedRoutedEventHandler(cb_Executed);

}

void cb_Executed(object sender, ExecutedRoutedEventArgs e)

{

MessageBox.Show(" 响应自定义命令 MyCommand");

}

}

上述代码创建一个 CommandBinding 对象,此对象指明 cb_Executed 函数响应 MyCommand 命令(其实是被 MyCommand 命令的 Execute 方法自动调用,这里借用事件处理机制的相关术语以便于理解,事实上,命令绑定与事件响应不是一回事,简单地说,命令绑定建构于 Wpf 的事件路由机制之上)。

下一步则需要指定窗体上的哪几个控件用于调用此命令:

partial class Window1 : Window

{

public Window1()

{

InitializeComponent();

// 创建命令对象

CommandBinding cb = new CommandBinding ();

cb.Command = MyAppCommands .MyCommand;

cb.Executed += new ExecutedRoutedEventHandler (cb_Executed);

// 将要执行的命令对象添加到窗体的命令对象集合中

this.CommandBindings.Add(cb);

// 设定菜单项和按钮都执行 MyCommand 命令

mnuInvokeMyCommand.Command = MyAppCommands.MyCommand;

btnInvokeMyCommand.Command = MyAppCommands.MyCommand;

}

void cb_Executed(object sender, ExecutedRoutedEventArgs e)

{

MessageBox .Show( " 响应自定义命令 MyCommand" );

}

}

现在运行程序,可以发现,单击菜单项和按钮都会调用 cb_Executed 函数。

3 使用 XAML 实现 DataBinding

前面使用代码实现了数据绑定。现在,改用 XAML 实现相同的功能。

首先,删除 Window1 构造函数中的代码,只留下 cb_Executed ()函数:

partial class Window1 : Window

{

public Window1()

{

InitializeComponent();

}

void cb_Executed(object sender, ExecutedRoutedEventArgs e)

{

MessageBox .Show( " 响应自定义命令 MyCommand" );

}

}

回到 Window1.xaml 文件中,首先,在其 Window 元素中加入对本项目命名空间的引用(其目的是在 XAML 中使用代码中的类):

<Window x : Class ="UnderStandCommandBinding.Window1" ……

xmlns:myapp="clr-namespace:UnderStandCommandBinding" >

……

</Window

然后,修改 MenuItem 的声明,加上 Command 属性:

< MenuItem Header ="Invoke My Command" Name ="mnuInvokeMyCommand" Command="MyAppCommands.MyCommand" />

再修改 Button 的声明,也加上 Command 属性:

< Button Name ="btnInvokeMyCommand"

Command="myapp:MyAppCommands.MyCommand">

Invoke My Command

</ Button >

最后,给最顶层元素 Window 添加命令绑定:

< Window.CommandBindings >

< CommandBinding

Command="myapp:MyAppCommands.MyCommand"

Executed="cb_Executed" />

</ Window.CommandBindings >

现在运行示例,可以看到运行效果与代码一样。

由此可知,我们可使用 XAML 以比使用 C# 编写代码方式更少的代码量实现同样的功能。

4 实现控件同步

现在开始展示一下命令绑定的神奇之处。向窗体上加入一个 CheckBox (取名 chkActivateCommand ),我们将用它来控制是否可以执行 MyCommand 命令( 2 ):


Command Binding机制剖析及应用


2

修改命令绑定对象:

< Window.CommandBindings >

< CommandBinding Command ="myapp:MyAppCommands.MyCommand" Executed ="cb_Executed" CanExecute="CommandBinding_CanExecute" />

</ Window.CommandBindings >

Window.xaml.cs 中添加一个新函数:

private void CommandBinding_CanExecute(

object sender, CanExecuteRoutedEventArgs e)

{

e.CanExecute = chkActivateCommand.IsChecked.Value;

}

其余代码不需要动。 OK 。现在运行一下示例程序:


Command Binding机制剖析及应用


3

可以看到,现在按钮和菜单命令的激活与由 CheckBox 控制了。我们甚至没有写一行代码去同步这三个控件的状态!

5 向命令传送参数

添加一个文本框 txtPara 用于输入参数。

< TextBox Name ="txtPara" Background ="Wheat" />

控件的 CommandParameter 属性可用来向命令对象提供参数,我们使用数据绑定机制将文本框中的文本作为命令参数:

< MenuItem Header ="Invoke My Command" Name ="mnuInvokeMyCommand" Command ="myapp:MyAppCommands.MyCommand" CommandParameter="{Binding ElementName=txtPara,Path=Text}" />

……

< Button Height ="23" Name ="btnInvokeMyCommand" HorizontalAlignment ="Center" Command ="myapp:MyAppCommands.MyCommand" CommandParameter="{Binding ElementName=txtPara,Path=Text}" > Invoke My Command </ Button >

传入的参数会被命令响应函数的 ExecutedRoutedEventArgs 参数接收,示例程序中修改后的命令响应函数如下:

void cb_Executed( object sender, ExecutedRoutedEventArgs e)

{

MessageBox .Show( e.Parameter.ToString() );

}

运行屏幕截图如 4


Command Binding机制剖析及应用


4

在文本框中输入文字,点击按钮或菜单项,将弹出一个消息框显示用户输入的文本。

我们的示例到此结束。

6 Command Binding 有何用处?

根据这个例子,大家体会到了 Command Binding 的好处了吗?

仔细看一下 MyAppCommands 类,就发现我们可以向其中添加多个自定义的命令,只需指定好这些命令对象的 Execute CanExecute 两个事件响应属性,就可以方便地设定窗体上的控件执行特定的命令(通过其 Command 属性),而且 WPF 框架会自动帮助我们同步这些控件的状态!

这样一来,我们就得到了一种比较模块化的 Wpf 桌面应用程序结构:

(1) 将应用程序要执行的功能封装到中间层组件或独立的类中;

(2) 创建一个专用保存命令对象的类(比如本例中的 MyAppCommands 类),在此类中集中存放应用程序要执行的命令对象。

(3) 在窗体中创建命令绑定对象,为每个命令对象创建响应函数,并将其添加到 Window 对象的 CommandBindings 属性中,之后,即可将控件与命令对象相互绑定。

这种架构的优点是增强了代码的隔离性,并减少了总体的代码量:

(1) 业务逻辑代码在中间层中;

(2) 专用于提供特定命令对象的类实际上成了一个功能调用接口层,可以很方便地增删调整程序提供的功能。

(3) 现在控件基本上不需要写大量的代码响应特定的事件和同步多个控件的状态。

7 剖析 Command Binding 机制

Command Binding 机制主要与两个接口密切相关:一个是 ICommand ,用于定义命令对象类,另一个是 ICommandSource ,用于定义可发出“调用命令”的源控件。 RoutedUICommand 类实现了 ICommand 接口,通常用此类的对象表示应用程序需要执行的命令。而 Button 等控件则实现了 ICommandSource 接口,并且拥有此接口所定义的 Command CommandParameter 属性,可用于调用命令。

Wpf 程序主要通过以下两种对象实现命令绑定: CommandBinding 对象将命令对象绑定到特定的命令处理函数(示例就是这样的),当执行命令对象时,这些命令处理函数被调用(命令对象和命令响应函数的这种关系称为“绑定”)。 InputBinding 对象将命令对象与特定的按键关联起来,当用户按下特定键时,相关联的命令对象被调用。

许多 Wpf 控件都拥有 CommandBindings InputBindings 两个集合属性,其中成员就是 CommandBinding 对象和 InputBinding 对象,从而使这些控件能够响应特定的命令,比如 TextBox 就是这样,它在内部包容了响应标准的 Cut/Copy/Paste 命令对象的代码,而 wpf 默认提供的 ApplicationCommands 类则定义了 Cut/Copy/Paste 命令对象,这就是为何我们可以不写一行代码直接在 TextBox 中使用 Ctl-C 等命令的缘故。

Command Binding 机制的内部机制比较复杂,简单地说:

执行 Command 对象时,会引发 PreviewExecute/Execute PreviewCanExecute/CanExecute 事件,这些事件都是 RoutedEvent ,可以在 Wpf 元素树中传播(从树叶到树根方向称为 Bubble ,从树根到树叶方向称为 tunnel ),正是这种事件路由机制构筑了整个 Command Binding 机制运转起来的根基。

再进一步追问一下,事件路由是如何实现的?这就涉及到 Wpf 另一个非常重要的新特性——依赖属性。

有关路由事件和依赖属性的剖析,将会在另外的文章中进行介绍。需要指出的是,如果您对 .NET 2.0 所提供的委托、事件等还不清楚,那么,要弄明白 wpf 的路由事件和依赖属性比较困难。

这也再次验证了一句老话:新路接在旧路的前头。

好,这篇文章就写到这里吧!

Command Binding机制剖析及应用


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论