电脑技术学习

了解WPF中的路由事件和命令

dn001
路由命令的局限

路 由命令非常适合单用户界面,挂接工具栏和菜单项以及处理与键盘焦点项目(如剪贴板操作)相关的条目。但是,如果您要构建复杂的用户界面,即命令处理逻辑位 于视图定义的支持代码之内,且命令调用程序不总是在工具栏或菜单之内,在这种情况下,路由命令就显得力不从心了。使用 UI 复合模式时,如 Model View Controller 或 MVC (msdn.microsoft.com/magazine/cc337884)、Model View Presenter 或 MVP (msdn.microsoft.com/magazine/cc188690)、Presentation Model,在 WPF 循环中亦称做 Model View ViewModel (msdn.microsoft.com/library/cc707885),通常会出现这种情况。

此时的问题是启用并处理命令逻辑可能不是直接归属于可视树,而是位于表示器或表示模型。此外,确定是否启用命令的状态与命令调用程序和视图在可视树中的位置无关。有时,您会遇到一个特殊命令在给定时间有多个处理程序的情形。

要了解在哪些情况下路由命令会出现问题,请查看图 5。它是一个简单的窗口,包含一对用户控件,这两个控件以 MVP 或 MVC 模式表示视图。主窗口包含一个 File 菜单和工具栏,其中有 Save 命令按钮。在主窗口上方还有一个输入文本框,以及一个将 Command 设为 Save 的 Button。


图5复合用户界面(单击图像可查看大图)

提示:挂接匿名方法

在图 6 所示的代码中,我使用了我同事 Juval Lowy 传授给我的技巧,向声明中的委托挂接一个空的匿名方法。

Action<string> m_ExecuteTargets = delegate { };这样,在调用委托前,您就不必再检查是否有空值,因为在调用列表中始终都有一个 no-op 订户。您还可能通过在多线程环境中取消订阅避免可能的争用,如果您检查空值,经常会出现争用。

有关此技巧的详细信息,请参阅 Juval Lowy 撰写的《Programming .NET Components, Second Edition》。

UI 的其余部分由两个视图提供,每个都是简单用户控件的实例。每个用户控件实例的边界颜色各不相同,这是为更清楚地显示它们所提供的 UI 内容。每个用户控件实例都有一个 Save 按钮,它将 Command 属性设为 Save 命令。

路由命令(与可视树中的位置密切相关)带来的困难在这一简单示例中一览无余。在图 5 中,窗口本身没有针对 Save 命令的 CommandBinding。但它的确包含该命令的两个调用程序(菜单和工具栏)。在此情形中,我不想让顶层窗口在调用命令时必须了解采取何种操作。而 是希望由用户控件表示的子视图处理命令。此例中的用户控件类有针对 Save 命令的 CommandBinding,它为 CanExecute 返回 true。

但在图 5 中,您可以看到焦点项位于顶部文本框的窗口内,而此级别的命令调用程序却被禁用。此外,尽管用户控件中没有焦点项,但用户控件中的 Save 按钮却被启用。

如果您将焦点项从一个文本框更改到一个用户控件实例内,菜单和工具栏中的命令调用程序会变为启用状态。但窗口本身的 Save 按钮不会变为启用状态。实际上,在这种情况下无法用正常路由启用窗口上方文本框旁的 Save 按钮。

原 因仍与单个控件的位置相关。由于在窗口级没有命令处理程序,尽管焦点项位于用户控件之外,但可视树上方或焦点项路径上仍没有命令处理程序会启用挂接为命令 调用程序的控件。因此一旦涉及这些控件,会默认禁用命令。但是,对于用户控件内的命令调用程序,由于处理程序在可视树的位置靠上,所以会启用命令。

一旦您将焦点项转到其中一个用户控件内,位于窗口和焦点项路径上文本框之间的用户控件即会提供命令处理程序,用于为工具栏和菜单启用命令,这是因为它们会检查焦点项路径以及其在可视树中的位置与根项之间的路径。由于窗口级按钮和根项之间没有处理程序,所以无法启用该按钮。

要是这个简单的小示例中的可视树和焦点项路径的繁文缛节就让您倍感头疼,如果 UI 相当复杂,在可视树中众多不同位置有命令调用程序和处理程序,要想理顺命令启用和调用有多难就可想而知了。那会您联想起电影《Scanners》中的怕人情节,让人头昏眼花。

避免命令出错

要 防止路由命令出现与可视树位置相关的问题,您需要保持简洁。通常应确保命令处理程序位于相同的元素,或在可视树中处于调用命令的元素上方。您可以从包含命 令处理程序的控件使用 CommandManager.RegisterClassCommandBinding 方法,在窗口级加入命令绑定,这样就能实现上述目标。

如果您实现的是本身接受键盘焦点项(像文本框)的自定义控件,那么属于例外情况。在这种情形下,如果您想在控件本身嵌入命令处理且该命令处理仅在焦点项处于您的控件上时产生关联,您可实现这一目标,它的工作状况类似先前所示的 Cut 命令示例。

您也可通过 CommandTarget 属性明确指定命令处理程序来解决上述问题。例如,对于图 5 中从未启用过的窗口级 Save 按钮,您可将其命令挂接更改为如下所示:

<Button Command="Save" CommandTarget="{Binding ElementName=uc1}" Width="75" Height="25">Save</Button>

在 此代码中,Button 专门将其 CommandTarget 设为 UIElement 实例,该实例中包含一个命令处理程序。在本例中,它指定名为 uc1 的元素,该元素恰好为示例中两个用户控件实例之一。由于该元素有一个始终返回 CanExecute = true 的命令处理程序,窗口级的 Save 按钮始终处于启用状态,并仅调用该控件的命令处理程序,无论调用程序相对于命令处理程序的位置如何都是如此。