在Windows应用程序中,又3种基本的用户输入形式:鼠标、键盘和手写板。同时,还有一种更高级输入方式,其可能来自快捷键、工具栏的按钮、菜单项。
尽管控件担当着主要的输入对象,用户界面的所有元素都可以接受输入。不必吃惊,这是因为,为了提供外观,控件完全依赖于底层元素的服务,如Rectangle和TextBlock。因此,在用户界面内的元素类型中,所有的输入机制都是有用的,我们将要在接下来的章节介绍这些机制。
3.2.1 Routed事件
.Net框架定义了一个标准的机制来暴露事件。一个类可能暴露了一些事件,每个事件可能有任意数量的订阅者。虽然WPF也使用了这一标准机制,声称其克服了一个局限:如果一个正常.NET事件没有注册句柄,该事件将被视为无效并忽略。
考虑一下这对于一个典型的WPF控件意味着什么。大多数控件是由多个可视化组件组成的。例如,即使你为一个按钮添加了一个非常简单的可视化树,这棵树包括一个单独的矩形框,以及一条简单的文本,目前有两个元素:文本和矩形框。不管光标是否在文本或矩形框上,这个按钮都要响应鼠标点击事件。在标准.NET事件处理模型中,这意味着要为所有元素注册MouseLeftButtonUp事件。
更严重的是使用WPF内容模型。一个按钮并不局限于只有简单文本作为标题,它可以包含任意标签。示例3-2是一个相当普通的情况,但即使如此,其中仍然有6个元素:黄色的边框,代表眼睛的两个点,代表嘴的曲线,文本,以及作为背景的按钮本身。为每一个单独元素关联事件句柄关联,是烦冗而且效率低下的。幸运的是,这些并不是必需的。
图3-2
WPF使用routed事件,该事件比其他普通事件更为直接。原先的机制是,将委托句柄关联到激发该事件的元素,调用该句柄。如今,一个rounted事件会调用所有的关联到已知代码的句柄,从初始元素向上直到用户界面书的根元素。
示例3-1显示了图3-2中按钮的标记。如果Canvas中的一个Elliipse元素接收到输入,事件路由可以支持Button、Grid、Canvas和Ellispse接收事件,如图3-3所示。
示例3-1
PreviewMouseLeftButtonDown="PreviewMouseButtonDownButton">
<Grid MouseLeftButtonDown="MouseButtonDownGrid"
PreviewMouseLeftButtonDown="PreviewMouseButtonDownGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Canvas MouseLeftButtonDown="MouseButtonDownCanvas"
PreviewMouseLeftButtonDown="PreviewMouseButtonDownCanvas"
Width="20" Height="18" VerticalAlignment="Center">
<Ellipse MouseLeftButtonDown="MouseButtonDownEllipse"
PreviewMouseLeftButtonDown="PreviewMouseButtonDownEllipse"
Canvas.Left="1" Canvas.Top="1" Width="16" Height="16"
Fill="Yellow" Stroke="Black" />
<Ellipse Canvas.Left="4.5" Canvas.Top="5" Width="2.5" Height="3"
Fill="Black" />
<Ellipse Canvas.Left="11" Canvas.Top="5" Width="2.5" Height="3"
Fill="Black" />
<Path Data="M 5,10 A 3,3 0 0 0 13,10" Stroke="Black" />
</Canvas>
<TextBlock Grid.Column="1">Foo</TextBlock>
</Grid>
</Button>
一个路由事件可以是bubbling,tunneling或Direct的。Bubbling事件以寻找附属到激发事件的事件句柄开始,接着寻找它的父级别,再接着是它的父级别的父级别,依次类推,直到达到这棵树的根,这个顺序是由图3-3的数字表明的。Tunneling事件以相反的方式工作。它先在树根寻找句柄,接着向下开始工作,以原始的元素作为结束。
Direct事件的路由方式与传统的.NET事件处理相同,只有直接附属到原始元素的句柄会被通知到。这典型地用于仅在它们的原始元素的上下文中有意义的那些事件。例如,如果鼠标的进入和移开是bubbled或tunneled的,这将是无用的。父级元素未必会关心何时鼠标从一个元素移动到另一个元素。在父一级元素,你可能希望“鼠标移开”意味着“鼠标已经离开了父一级元素”,因为使用了Direct事件路由,这才是它正确地意味着什么。一旦使用bubbiling,事件将有效的意味着“鼠标已经离开了这个元素,可能在或不在其父一级内的另一个元素中”。
除direct事件之外,WPF还定义了很多成对(bubbling和tunneling)的路由事件。Tunneling事件的名称通常以Preview开始,而且会首先被激发。这将给原始元素的父级一个看一下事件的机会,在到达其子级别之前(因此以“Preview”为前缀。)tunneling的Preview事件直接遵循bubbling事件。在大多数情形中,你将要处理bubbling事件,preview事件只用于你想要阻塞一个事件时,或者你想要父一级在正常处理事件时预先做一些事情。
在示例3-1中,大多数元素拥有事件句柄,由MouseLeftButtonDown和PreviewMouseLeftButtonDown事件指定相应的bubbling和tunneling事件。示例3-2显示了相应的后台代码文件。
示例3-2
using System.Windows;
using System.Diagnostics;
namespace EventRouting {
public partial class Window1 : Window {
public Window1( ) {
InitializeComponent( );
}
private void MouseButtonDownButton(object sender, RoutedEventArgs e)
{ Debug.WriteLine("MouseButtonDownButton"); }
private void PreviewMouseButtonDownButton(object sender, RoutedEventArgs e)
{ Debug.WriteLine("PreviewMouseButtonDownButton"); }
private void MouseButtonDownGrid(object sender, RoutedEventArgs e)
{ Debug.WriteLine("MouseButtonDownGrid"); }
private void PreviewMouseButtonDownGrid(object sender, RoutedEventArgs e)
{ Debug.WriteLine("PreviewMouseButtonDownGrid"); }
private void MouseButtonDownCanvas(object sender, RoutedEventArgs e)
{ Debug.WriteLine("MouseButtonDownCanvas"); }
private void PreviewMouseButtonDownCanvas(object sender, RoutedEventArgs e)
{ Debug.WriteLine("PreviewMouseButtonDownCanvas"); }
private void MouseButtonDownEllipse(object sender, RoutedEventArgs e)
{ Debug.WriteLine("MouseButtonDownEllipse"); }
private void PreviewMouseButtonDownTextBlock(object sender,
RoutedEventArgs e)
{ Debug.WriteLine("PreviewMouseButtonDownEllipse"); }
}
}
每一个句柄输出了一条debug信息。这里时我们获得的debug输出,当点击Canvas中的TextBlock时。
PreviewButtonDownButton
PreviewButtonDownGrid
PreviewButtonDownCanvas
PreviewButtonDownEllipse
ButtonDownEllipse
ButtonDownCanvas
ButtonDownGrid
ButtonDownButton
输出结果证实了Preview事件是最先被激发的。还显示了它是从Button元素开始向下工作,正如我们对tunneling事件希望的那样。bubbling事件则从Ellispse开始向上工作。
Bubbling路由事件提供了很多事件,意味着你可以注册一个单独的事件处理在一个控件上,而且它将为内嵌在控件中的任何元素接收事件。你不需要任何特殊的处理以解决内嵌内容或自定义可视化内容,事件简单的向上冒泡,并且在那里可以全部被处理。
3.2.1.1中止事件处理
有很多情形你可能不想让事件冒泡。例如,你可能希望转换事件为别的什么东西,Button元素有效的转换了MouseLeftButtonDown和MouseLeftButtonUp事件为Click事件。它抑止了底层事件,从而只有Click事件冒泡到控件之外。
任何句柄都能防止进一步的处理路由事件——通过设置RoutedEvebtArgs的Handled属性,如示例3-4所示。
示例3-3
Debug.WriteLine("ButtonDownCanvas");
e.Handled = true;
}
另一个设置Handled标志的原因是,如果你想要防止正常的事件处理。一旦你在Preview句柄中这么做,不仅tunneling的Preview事件会停止,本应正常执行的bubbling事件也不会被激活,因此看起来似乎事件没有发生。
3.2.1.2确定目标
虽然在一个单独的地方,能够处理来自一组元素的事件,这是非常便利的,你的句柄可能需要知道是哪个元素引起激活一个事件,你可能想这正是句柄中sender参数的意图。事实上,sender一直将对象归诸于你附加到的事件句柄上。在使用bubbling和tunneling事件的情形中,这并不总是引起事件被激活的元素。在示例3-1中,ButtonDownWindow句柄的sneder是Window本身。
幸运的是,找到潜在的导致事件发生的元素,这是容易的。RouteEventArgs对象作为第二个参数传递,提供了一个OriginalSource属性。
3.2.1.3路由事件和正常的事件
正常的.NET事件(或者说,他们曾经称为CLR事件),提供了一个优势——相对于路由事件语法:很多.NET语言对处理CLR事件提供内嵌的支持。这就提供了最好的两种世界:你可以使用你喜欢的语言的事件处理语法,而不是利用额外的由路由事件提供的功能。
多亏了CLR事件机制的弹性设计。虽然这里有一种标准的联合了CLR事件的简单行为,CLR的设计者有远见的意识到,一些应用程序需要更多的高级行为。这些类因此可以自由的实现它们喜欢的事件。WPF获益于这种有CLR事件定义的设计——这些事件内在的作为路由事件来实现。
示例3-1和示例3-2安排了事件句柄的连接,通过使用标记中的属性。但是我们可能已经替代地使用了正常的C#事件句柄语法来关联构造函数中的句柄。例如,我们要在示例3-1中移除MouseLeftButtonDown和PreviewMouseLeftButtonDown属性,接着修改示例3-2的构造函数,如下面的示例3-4。
示例3-4
public Window1( ) {
InitializeComponent( );
this.MouseLeftButtonDown += MouseButtonDownWindow;
this.PreviewMouseLeftButtonDown += PreviewMouseButtonDownWindow;
}
我们还能对来自内嵌元素的事件进行同样的处理。我们不得不应用x:Name属性为了能够访问C#的元素。
后台代码经常是最好的地方来附属事件句柄。一旦你的用户界面有不寻常和有创意的可视化外观,这是一个好的时机让xaml文件有效地被图形设计器拥有。一个设计者不应该知道开发者需要处理哪些事件,或者调用那些句柄函数。因此,你将通常要设计者在xaml中给元素命名,同时开发者将要在后台代码附属句柄。
3.2.2鼠标输入
鼠标输入关注于哪个元素直接位于鼠标下。所有的用户界面元素派生于UIElement基类,这个基类定义了大量的鼠标输入事件。这些事件列于表3-1中。
表3-1
Event |
Routing |
Meaning |
---|---|---|
GotMouseCapture |
Bubble |
Element captured the mouse. |
LostMouseCapture |
Bubble |
Element lost mouse capture. |
MouseEnter |
Direct |
Mouse pointer moved into element. |
MouseLeave |
Direct |
Mouse pointer moved out of element. |
PreviewMouseLeftButtonDown, MouseLeftButtonDown |
Tunnel, Bubble |
Left mouse button pressed while cursor inside element. |
PreviewMouseLeftButtonUp, MouseLeftButtonUp |
Tunnel, Bubble |
Left mouse button released while cursor inside element. |
PreviewMouseRightButtonDown, MouseRightButtonDown |
Tunnel, Bubble |
Right mouse button pressed while cursor inside element. |
PreviewMouseRightButtonUp, MouseRightButtonUp |
Tunnel, Bubble |
Right mouse button released while cursor inside element. |
PreviewMouseMove, MouseMove |
Tunnel, Bubble |
Mouse cursor moved while cursor inside element. |
PreviewMouseWheel, MouseWheel |
Tunnel, Bubble |
Mouse wheel moved while cursor inside element. |
QueryCursor |
Bubble |
Mouse cursor shape to be determined while cursor inside element. |
UIElement 还定义了一对属性,表示鼠标当前是否在元素上:ISMouseOver 和ISDirectMouseOver 。这两个属性的区别在于,当鼠标在正被讨论的元素上或任何它的子元素上时,前者为true ;而后者仅当鼠标在正被讨论的元素上的时候才为true ,不包括它的子元素这种情况。
注意到,上表中基本的鼠标事件设置不包括Click事件。这是因为Click一个高级别的概念——相对于基本的输入。一个按钮可以被点击——通过鼠标或键盘。此外,Click并不是必要的直接符合一个单独的鼠标事件。通常的,用户不得不点击或按下或释放鼠标,当鼠标在鼠标之上以注册一个Click事件时。相应地,这些高级别的事件由更明确的元素类型提供。Control类添加了一对事件:MouseDoubleClick和PreviewMouseDoubleClick。ButtonBase——Button的基类,CheckBox,RadioButton,都有添加这个Click事件。
如果你使用了一个Fill属性为透明的Shape,这个Shape将担当输入的目标,一旦鼠标在Shape之上。这回有一点令人惊讶,如果你使用了一个完全透明的笔刷。这个Shape将是不可见的,但是仍然作为输入的目标,不管鼠标在其上看来可能是什么样的。如果你想要一个填充为透明的Shape,而且不捕获鼠标输入,简单的根本不提供Fill属性,如果Fill属性为null值(而不是一个完全的透明笔刷),,这个Shape将不会担当输入的模板。
记住,如果你考虑处理一个鼠标事件的原因是,简单的为用户提供某些可见的反馈,写一个事件句柄可能过度了。这通常是可能的,通过声明性的属性触发器和事件触发器,可以在样式的标签中,完全达到你需要的可视化效果。
3.2.3键盘输入
键盘输入引入了focus的概念。不同于鼠标,没法为用户移动键盘在一个元素上,从而指出输入的目标。在Windows中,一个特定的元素被指定为拥有focus,意味着它会担当键盘输入的目标。用户通过点击鼠标或Alt+Tap 在正在讨论的控件上设置focus,或者通过使用导航键如Tab和指针。
原则上,任何用户元素可以获得焦点。IsFocused属性定义在UIElement——FrameworkElement的基类。尽管如此,Focusable属性决定了是否支持这个特征在任意特定的元素上。默认的,这个值对于控件是true;对其他元素是false。
表3-2显示了有用户界面元素提供的盘输入事件。所有的这些项使用tunnel和bubble路由,分别为Preview和主要事件。
表3-2
Event
Routing
Meaning
PreviewGotFocus, GotFocus
Tunnel, Bubble
Element received the focus.
PreviewLostFocus, LostFocus
Tunnel, Bubble
Element lost the focus.
PreviewKeyDown, KeyDown
Tunnel, Bubble
Key pressed.
PreviewKeyUp, KeyUp
Tunnel, Bubble
Key released.
PreviewTextInput, TextInput
Tunnel, Bubble
Element received text input.
注意到,TextInput 并不是必要的键盘的输入。它代表了文本的输入在一个独立于设备的方式,因此这个事件也能被手动输入的结果所激活。
3.2.4手动输入
手写板上的铁笔以及其他支持手动输入的系统,有一套自己的事件。表3-3显示了手动输入事件——由用户界面元素提供。
表3-3
Event |
Routing |
Meaning |
---|---|---|
GotStylusCapture |
Bubble |
Element captured stylus. |
LostStylusCapture |
Bubble |
Element lost stylus capture. |
PreviewStylusDown, StylusDown |
Tunnel, Bubble |
Stylus touched screen over element. |
PreviewStylusUp, StylusUp |
Tunnel, Bubble |
Stylus left screen while over element. |
PreviewStylusEnter, StylusEnter |
Tunnel, Bubble |
Stylus moved into element. |
PreviewStylusLeave, StylusLeave |
Tunnel, Bubble |
Stylus left element. |
PreviewStylusInRange, StylusInRange |
Tunnel, Bubble |
Stylus moved close enough to screen to be detected. |
PreviewStylusOutOfRange, StylusOutOfRange |
Tunnel, Bubble |
Stylus moved out of detection range. |
PreviewStylusMove, StylusMove |
Tunnel, Bubble |
Stylus moved while over element. |
PreviewStylusInAirMove, StylusInAirMove |
Tunnel, Bubble |
Stylus moved while over element but not in contact with screen. |
PreviewStylusSystemGesture, StylusSystemGesture |
Tunnel, Bubble |
Stylus performed a gesture. |
PreviewTextInput, TextInput |
Tunnel, Bubble |
Element received text input. |
3.2.5 命令
很多应用程序提供了多于一种的方式来执行确定动作。例如,考虑创建一个新文件的动作。你可以选择Fiel——New menu item,或者你可以点击相应的工具栏按钮。可选择的,你可以使用快捷键如Ctrl+N。如果应用程序提供了一个脚本系统,这个脚本还可以提供另一种执行这个动作的方式。结果是,无论你使用什么机制,都是一样的,因为这里有不同的方式调用同样的底层命令。
WPF对这个想法提供了内嵌的支持。RoutedCommand类代表了一个可以在多种方式调用的逻辑动作。在典型的WPF应用程序中,每个菜单项和工具栏按钮都联合到一个底层的RoutedCommand对象。
RoutedCommand以一种与底层输入表单非常相似的方式工作。当调用一个命令的时候,它激活了两个事件:PreviewExecuteEvent和ExecuteEvent。这些事件在这棵元素树中使用tunnel和bubble机制,和输入事件的方式相同。命令的目标是由命令的调用方式来决定。典型地,这个目标将会是当前有焦点的任何一个元素,但是RoutedCommand还提供了一个Execute的重载方法,这会传递一个明确的目标元素。
你可以从很多地方获取一个RoutedCommand。一些控件提供了命令。例如,ScrollBar控件为它的每个动作定义了命令,使之在静态字段有效,如LineUpCommand和PageDownCommand。然而,大多数命令并不是唯一对应到特定的控件。一些符合应用程序级别的动作如”新文件”或“打开”。其他动作会在控件上被调用,但是可以被一些不同的控件实现。例如,TextBox和RichTextBox都能处理剪切操作。
这里有一组提供了标准命令的类。这些类显示在表3-4中。这意味着你不需要创建自己的RoutedCommand对象来代表最普遍的操作。此外,很多命令被内嵌控件了解。例如TextBox和RichTextBox都支持很多标准的操作,包括clipboard,undo和redo命令。
表3-4
Class
Command types
ApplicationCommands
Commands common to almost all applications. Includes clipboard commands, undo and redo, and document-level operations (open, close, print, etc.).
ComponentCommands
Operations for moving through information such as scroll up and down, move to end, and text selection.
EditCommands
Text-editing commands such as bold, italic, and alignment.
MediaCommands
Media-playing operations such as transport (play, pause, etc.), volume control, and track selection.
3.2.5.1 命令句柄
作为一个有用的命令,必须有事物对其进行响应。这个工作些微不同于处理正常的输入事件,因为大多数不是由控件定义的命令将会处理它们。表3-4中的类定义了95个命令,因此如果Control为每个截然不同的命令定义了CLR事件,那将需要190个事件——一旦还要包括preview的话。这不仅会极度不广泛,甚至还不是一个完全的解决方案。大多数应用程序在使用标准命令的同时,还定义了他们自身的自定义命令。明显的可选择性是为了RoutedCommand自身激活事件。然而,每个命令都是一个单件。例如,只有一个ApplicationCommand.New对象。如果你能直接添加一个句柄到命令对象,这个句柄会在任何时间运行。这个命令在你的应用程序任何地方被调用。如果你正想处理一个命令,当此命令在一个特定的窗口中执行的时候,会怎么样呢?
CommandBinding类解决了这些问题。一个CommandBinding对象映射了一个明确的RoutedCommand到一个句柄函数上——在一个特定的用户界面元素级别。正是这个CommandBinding会激活PreviewExecute和Execute事件,而不是UI元素。这些绑定保存在UI元素定义的CommandBinding属性。示例3-5显示了如何为一个窗体在后台代码文件中,处理ApplicationCommand.New命令。
示例3-5
public Window1( ) {
InitializeComponent( );
CommandBinding cmdBindingNew = new CommandBinding(ApplicationCommands.New);
cmdBindingNew.Execute += NewCommandHandler;
CommandBindings.Add(cmdBindingNew);
}
private void NewCommandHandler(object sender, ExecuteEventArgs e) {
if (unsavedChanges) {
MessageBoxResult result = MessageBox.Show(this,
"Save changes to existing document?", "New",
MessageBoxButton.YesNoCancel);
if (result == MessageBoxResult.Cancel) {
return;
}
if (result == MessageBoxResult.Yes) {
SaveChanges( );
}
}
// Reset text box contents
inputBox.Clear( );
}
}
这段代码依赖于命令路由的冒泡本质。顶级Window元素不同于成为命令目标的元素,当焦点通常属于某个窗体中的子元素时。然而,命令会向上冒泡到顶级。这个路由对命令的处理只放在一个地方,从而变得容易。
示例3-5处理的命令是ApplicationCommand.New。如果这组标准命令并没有满足你的应用程序的需要,你可以为明确的操作定义自定义命令。
3.2.5.2定义命令
示例3-6显示了如何定义一个命令。WPF使用对象实例来确定命令的唯一性。如果你要创建同名的第二个命令,这不会被当作同样的命令。由于这个原因,命令通常放置在静态字段或属性。
示例3-6
public partial class Window1 : Window {
public static RoutedCommand FooCommand;
static Window1( ) {
InputGestureCollection fooInputs = new InputGestureCollection( );
fooInputs.Add(new KeyGesture
(Key.F,
ModifierKeys.Control|ModifierKeys.Shift));
FooCommand = new RoutedCommand("Foo", typeof(Window1), fooInputs);
}
}
在示例
3.2.5.3调用命令
不仅定义了一个自定义命令,示例3-6还显示了一个将命令联合到用户输入的方法。配置这个特别的命令用来被一个特殊的输入表示所调用。当前支持两种输入表示类型:MouseGesture,是一个特别的由鼠标和触笔选中的形状;KeyGesture,正如在示例3-6中使用的,是一个特别的键盘快捷键。很多内嵌控件联合了标准的表示。例如,ApplicationCommand.Copy联合了标准的键盘快捷键,用来复制(大多数地方为Ctrl+C)。
虽然一个命令在创建的时候可以联合一组表示, 在一个特别的窗体的上下文中,你可能希望为这个命令分配另外的快捷键。为了允许这样做,用户界面元素有一个InputBindings属性。这个集合包含了InputBinding对象——联合了输入表示和命令。这些增加了联合了命令的默认表示。
输入表示如快捷键,不是唯一调用命令的方式。你可以在命令上调用Execute方法从而在代码上调用它。正如示例3-7所示,Execute被重载了。如果你没有传递参数,这个命令目标将会是任何得到焦点的元素,正如通过一个输入表示调用一个命令。但是你可以传递任何你想要的目标元素。
示例3-7
or
ApplicationCommands.New.Execute(targetElement);
你可能想,要在菜单项和工具栏按钮的
Click 句柄中,编写这样的代码。尽管如此,由于命令经常联合于菜单项和工具栏按钮,Button 和MenuItem 都支持Command 属性。这就唯一标志了要调用的命令,当元素被点击的时候。这里,为命令本身,提供了一种声明式的方式,而不是为每一个绑定到命令的UI 元素提供一个句柄。示例3-8 显示了一个联合了标准Copy 命令的Button 。示例3-8
因为这个示例使用了来自ApplicationCommands 类的标准命令,我们可以使用这个语法的简写形式,只需要指出命令名称。因为命令不是定义表3-4 中的类定义的,这就需要一些更详细的信息。完整的命令属性xaml 语法是:
[[xmlNamePrefix:]ClassName.]EventName
如果当前只有事件名,这个事件假定为标准命令中的一个。例如,Undo是ApplicationCommands. Undo的简写。否则,你必须提供一个类的名称,以及可能一个命名空间前缀。如果你正在使用自定义命令或者某个第三方组件定义的命令,这个命名空间前缀就是需要的。与Mapping这个XML处理指令(使外部类型在xaml文件中有效)协力工作。(参见附录A获取更多Mapping处理指令的信息。)
示例3-9显示了命令名称语法的使用——所有部分都在。M:MyCommand.Foo的值意味着当前正在讨论的命令是在mylib组件的MyLib.Commands.MyCommands类中定义的,并且存储在名为Foo的字段或属性中。
示例3-9
XmlNamespace="urn:mylib" ?>
< Window xmlns:m ="urn:mylib" >
< Button Command ="m:MyCommands.Foo" > Custom Command </ Button >
3.2.5.4支持命令
不仅可以被执行,命令还提供了一个QueryEnabled方法,返回了一个Boolean值表明命令是否能被立刻调用;某些命令仅在特定的上下文中有效。这个特征可以用来决定菜单或工具栏中的项是否应该变为灰色。调用QueryEnabled方法,会被以Execute同样的方式处理;CommandBinding对象用于处理这次查询。这个绑定激活一对PreviewQueryEnabled和QueryEnabled事件,这将以与PreviewExecute和Execute同样的方式进行tunnel和bubble。示例3-10显示了如何处理这个事件,为了系统定义的Redo命令。
示例3-10
InitializeComponent( );
CommandBinding redoCommandBinding =
new CommandBinding(ApplicationCommands.Redo);
redoCommandBinding.QueryEnabled += RedoCommandQueryEnabled;
CommandBindings.Add(redoCommandBinding);
}
void RedoCommandQueryEnabled( object sender, QueryEnabledEventArgs e) {
if (!CanRedo( )) {
e.IsEnabled = false;
}
}
不幸的是,截止到写作时间,当前
WPF 的版本并不会使菜单或工具栏中的项变灰。它会激活QueryEnabled 事件当一个菜单项被调用时,以及防止命令的执行,如果被disabled 了,但是当前不提供任何可视化的指示,来表明一个项被disabled 。我们希望这个问题会被解决在将来的版本中。我们已经看到在WPF中控件是如何处理输入的所有可能方式。现在让我们开一下一组内嵌在WPF中的控件。