Private Sub OnTextInputKeyDown(ByVal sender As Object, ByVal e As KeyEventArgs)
If e.Key = Key.O AndAlso Keyboard.Modifiers = ModifierKeys.Control Then
handle()
e.Handled = True
End If
End Sub
Private Sub OnTextInputButtonClick(ByVal sender As Object, ByVal e As RoutedEventArgs)
handle()
e.Handled = True
End Sub
Public Sub handle()
MessageBox.Show("Pretend this opens a file")
End Sub
由于输入事件在事件路由中向上浮升,因此不管哪个元素具有键盘焦点,StackPanel 都将接收输入。 会首先通知 TextBox 控件,仅当 TextBox 不处理输入时,才会调用 OnTextInputKeyDown
处理程序。 如果使用了 PreviewKeyDown 事件而不是 KeyDown 事件,将首先调用 OnTextInputKeyDown
处理程序。
在此示例中,处理逻辑写入了两次,分别针对 CTRL+O和按钮的单击事件。 使用命令,而不是直接处理输入事件,可简化此过程。 本概述和命令概述中将讨论这些命令。
触摸和操作
Windows 7 操作系统中的新硬件和 API 使应用程序能够同时接收来自多个触控的输入。 WPF 通过在触摸发生时引发事件,使应用程序能够以类似于响应其他输入(例如鼠标或键盘)的方式来检测和响应触摸设备。
发生触摸时,WPF 将公开两种类型的事件:触摸事件和操作事件。 触摸事件提供有关触摸屏上每个手指及其移动的原始数据。 操作事件将输入解释为特定操作。 本部分将讨论这两种类型的事件。
需要以下组件才能开发响应触摸的应用程序。
Visual Studio 2010。
Windows 7。
支持 Windows 触控的设备,如触摸屏。
讨论触摸时使用了以下术语。
触摸是 Windows 7 可识别的一种用户输入。 通常,将手指放在触敏式屏幕上会触发触摸。 请注意,如果设备仅将手指的位置和移动转换为鼠标输入,则笔记本电脑上常用的触摸板等设备不支持触摸。
多点触摸是同时发生在多个点上的触摸。 Windows 7 和 WPF 支持多点触摸。 WPF 文档中每当论及触摸时,相关概念均适用于多点触摸。
当触摸被解释为应用于对象的实际操作时,就发生了操作。 在 WPF 中,操作事件将输入解释为平移、展开或旋转操作。
touch device
表示产生触摸输入的设备,例如触摸屏上的一根手指。
响应触摸的控件
如果以下控件的内容延伸到视图之外,则可以通过在控件上拖动手指来滚动该控件。
ComboBox
ContextMenu
DataGrid
ListBox
ListView
MenuItem
TextBox
ToolBar
TreeView
ScrollViewer 定义了 ScrollViewer.PanningMode 附加属性,该属性让你可以指定是水平、垂直、同时以这两种方式还是不以任何一种方式启用触摸移动。 ScrollViewer.PanningDeceleration 属性指定当用户从触摸屏上抬起手指时滚动速度减慢的速度。 ScrollViewer.PanningRatio 附加属性指定滚动偏移与平移操作偏移的比率。
基类 UIElement、UIElement3D 和 ContentElement 定义你可以订阅的事件,以便你的应用程序对触摸做出响应。 当应用程序将触摸解释为操作对象以外的其他操作时,触摸事件非常有用。 例如,使用户能够以一个或多个手指绘制的应用程序将订阅触摸事件。
所有三个类都定义了以下事件,其行为类似,而无论定义类是什么。
TouchDown
TouchMove
TouchUp
TouchEnter
TouchLeave
PreviewTouchDown
PreviewTouchMove
PreviewTouchUp
GotTouchCapture
LostTouchCapture
像键盘和鼠标事件一样,触摸事件也是路由事件。 以 Preview
开头的事件是隧道事件,以 Touch
开头的事件是冒泡事件。 有关路由事件的详细信息,请参阅路由事件概述。 当你处理这些事件时,可以通过调用 GetTouchPoint 或 GetIntermediateTouchPoints 方法获得输入相对于任何元素的位置。
为了理解触控事件之间的交互,请考虑以下这种情况:用户将一个手指放在元素上,在该元素中移动手指,然后将手指从该元素上移开。 下图显示了冒泡事件的执行(为简单起见,省略了隧道事件)。
下列内容描述了上图中的事件顺序。
当用户将手指放在元素上时,会发生一次 TouchEnter 事件。
会发生一次 TouchDown 事件。
当用户在元素中移动手指时,会发生多次 TouchMove 事件。
当用户从元素抬起手指时,会发生一次 TouchUp 事件。
会发生一次 TouchLeave 事件。
当使用两根以上的手指时,每根手指都会发生事件。
对于应用程序支持用户操作对象的情况,UIElement 类定义了操作事件。 与只是报告触摸位置的触摸事件不同,操作事件会报告可采用何种方式解释输入。 有三种类型的操作:转换、扩展和旋转。 下列内容介绍了如何调用这三种类型的操作。
将一根手指放在对象上,并在触摸屏上拖动手指以调用转换操作。 此操作通常会移动对象。
将两根手指放在物体上,并将手指相互靠拢或分开以调用扩展操作。 此操作通常会调整对象的大小。
将两根手指放在对象上,并将一个手指围绕另一个手指旋转以调用旋转操作。 此操作通常会旋转对象。
多种类型的操作可以同时发生。
使对象响应操作时,可以让对象看起来具有惯性。 这样可以使对象模拟真实的世界。 例如,在桌子上推一本书时,如果你足够用力,书将在你松手后继续移动。 利用 WPF,可以通过在用户的手指松开对象后引发操作事件来模拟这种行为。
如需深入了解如何创建使用户可以对对象进行移动、调整大小和旋转的应用程序,请参阅演练:创建你的第一个触控应用程序。
UIElement 定义了以下操作事件。
ManipulationStarting
ManipulationStarted
ManipulationDelta
ManipulationInertiaStarting
ManipulationCompleted
ManipulationBoundaryFeedback
默认情况下,UIElement 不会收到这些操作事件。 若要在 UIElement 上收到操作事件,请将 UIElement.IsManipulationEnabled 设置为 true
。
操作事件的执行路径
考虑用户“抛出”一个对象的情况。 用户将手指放在对象上,将手指在触摸屏上移动一段短距离,然后在移动的同时抬起手指。 此操作的结果是,该对象将在用户的手指下方移动,并在用户抬起手指后继续移动。
下图显示了操作事件的执行路径和每个事件的重要信息。
下列内容描述了上图中的事件顺序。
当用户将手指放在对象上时,会发生 ManipulationStarting 事件。 此外,此事件还允许你设置 ManipulationContainer 属性。 在后续事件中,操作的位置将相对于 ManipulationContainer。 在除 ManipulationStarting 之外的事件中,此属性是只读的,因此你只能在发生 ManipulationStarting 事件时设置此属性。
接下来会发生 ManipulationStarted 事件。 此事件报告操作的原始位置。
当用户的手指在触摸屏上移动时,会发生多次 ManipulationDelta 事件。 ManipulationDeltaEventArgs 类的 DeltaManipulation 属性报告操作是解释为移动、展开还是平移。 这是你执行操作对象的大部分工作的地方。
当用户的手指与对象失去接触时,会发生 ManipulationInertiaStarting 事件。 此事件使你可以指定操作在惯性期间的减速。 这样,选择时对象就可以模拟不同的物理空间或特性。 例如,假设应用程序有两个表示真实世界中的物品的对象,并且一个物品比另一个物品重。 你可以使较重的对象比较轻的对象减速更快。
在发生惯性时,会发生多次 ManipulationDelta 事件。 请注意,当用户的手指在触摸屏上移动并且 WPF 模拟惯性时,将发生此事件。 换句话说,在 ManipulationInertiaStarting 事件之前和之后,会发生 ManipulationDelta。 ManipulationDeltaEventArgs.IsInertial 属性报告在惯性期间是否发生了 ManipulationDelta 事件,以便你可以检查该属性并根据其值执行不同的操作。
当操作和任何惯性结束时,会发生 ManipulationCompleted 事件。 也就是说,在所有 ManipulationDelta 事件发生后,会发生 ManipulationCompleted 事件以指示操作已完成。
UIElement 还定义了 ManipulationBoundaryFeedback 事件。 在 ManipulationDelta 事件中调用 ReportBoundaryFeedback 方法时,会发生此事件。 ManipulationBoundaryFeedback 事件使应用程序或组件可以在对象到达边界时提供可视反馈。 例如,Window 类会处理 ManipulationBoundaryFeedback 事件,以便在到达窗口边缘时使窗口轻微移动。
你可以通过对任意操作事件中的事件参数调用 Cancel 方法来取消操作,ManipulationBoundaryFeedback 除外。 当你调用 Cancel 时,不再引发操作事件,触摸会发生鼠标事件。 下表描述了取消操作的时间与所发生的鼠标事件之间的关系。
在其中调用取消的事件
针对已经发生的输入发生的鼠标事件
请注意,如果你在操作处于惯性期间调用 Cancel,则该方法返回 false
,并且输入不会引发鼠标事件。
触摸事件和操作事件之间的关系
UIElement 可以始终收到触摸事件。 当 IsManipulationEnabled 属性设置为 true
时,UIElement 会同时收到触摸和操作事件。 如果未处理 TouchDown 事件(即 Handled 属性为 false
),则操作逻辑会将触摸捕获到元素并生成操作事件。 如果 Handled 属性在 TouchDown 事件中设置为 true
,则操作逻辑不会生成操作事件。 下图显示了触摸事件和操作事件之间的关系。
触摸和操作事件
下列内容描述了上图中所示的触摸事件和操作事件之间的关系。
当第一个触摸设备在 UIElement 上生成 TouchDown 事件时,操作逻辑将调用 CaptureTouch 方法,该方法会生成 GotTouchCapture 事件。
当发生 GotTouchCapture 时,操作逻辑将调用 Manipulation.AddManipulator 方法,该方法会生成 ManipulationStarting 事件。
当发生 TouchMove 事件时,操作逻辑将生成在 ManipulationInertiaStarting 事件之前发生的 ManipulationDelta 事件。
当元素上的最后一个触摸设备引发 TouchUp 事件时,操作逻辑将生成 ManipulationInertiaStarting 事件。
在 WPF 中,有两个与焦点有关的主要概念:键盘焦点和逻辑焦点。
键盘焦点指当前正在接收键盘输入的元素。 在整个桌面上,只能有一个具有键盘焦点的元素。 在 WPF 中,具有键盘焦点的元素会将 IsKeyboardFocused 设置为 true
。 静态 Keyboard 方法 FocusedElement 返回当前具有键盘焦点的元素。
可以通过 Tab 键移到某个元素或通过在特定元素(如 TextBox)上单击鼠标来获取键盘焦点。 也可以使用 Keyboard 类的 Focus 方法以编程方式获取键盘焦点。 Focus 尝试为指定元素提供键盘焦点。 Focus 返回的元素是当前具有键盘焦点的元素。
为使元素获得键盘焦点,必须将 Focusable 属性和 IsVisible 属性设置为 true。 某些类(例如 Panel)默认将 Focusable 设置为 false
;因此,如果希望该元素能够获得焦点,必须将此属性设置为 true
。
以下示例使用 Focus 将键盘焦点设置在 Button 上。 Loaded 事件处理程序是在应用程序中设置初始焦点的推荐位置。
private void OnLoaded(object sender, RoutedEventArgs e)
// Sets keyboard focus on the first Button in the sample.
Keyboard.Focus(firstButton);
Private Sub OnLoaded(ByVal sender As Object, ByVal e As RoutedEventArgs)
' Sets keyboard focus on the first Button in the sample.
Keyboard.Focus(firstButton)
End Sub
有关键盘焦点的详细信息,请参阅焦点概述。
逻辑焦点是指焦点范围内的FocusManager.FocusedElement。 一个应用程序中可以有多个具有逻辑焦点的元素,但在一个特定的焦点范围中只能有一个具有逻辑焦点的元素。
焦点范围是一个容器元素,用于跟踪其范围内的 FocusedElement。 焦点离开焦点范围时,焦点元素会失去键盘焦点,但保留逻辑焦点。 焦点返回到焦点范围时,焦点元素会再次获得键盘焦点。 这使得键盘焦点可在多个焦点范围之间切换,但确保了焦点返回到焦点范围时,焦点范围中的焦点元素仍为焦点元素。
通过将 FocusManager 附加属性 IsFocusScope 设置为 true
,或者通过在代码中使用 SetIsFocusScope 方法设置该附加属性,可将元素转换为 Extensible Application Markup Language (XAML) 中的焦点范围。
以下示例通过设置 IsFocusScope 附加属性将 StackPanel 转换为焦点范围。
<StackPanel Name="focusScope1"
FocusManager.IsFocusScope="True"
Height="200" Width="200">
<Button Name="button1" Height="50" Width="50"/>
<Button Name="button2" Height="50" Width="50"/>
</StackPanel>
StackPanel focuseScope2 = new StackPanel();
FocusManager.SetIsFocusScope(focuseScope2, true);
Dim focuseScope2 As New StackPanel()
FocusManager.SetIsFocusScope(focuseScope2, True)
WPF 中默认为焦点范围的类是 Window、Menu、ToolBar 和 ContextMenu。
具有键盘焦点的元素还具有它所属的焦点范围的逻辑焦点;因此,在 Keyboard 类或基元素类上使用 Focus 方法将焦点设置在一个元素上将尝试赋予该元素键盘焦点和逻辑焦点。
要确定焦点范围中的焦点元素,请使用 GetFocusedElement。 若要更改焦点范围的焦点元素,请使用 SetFocusedElement。
有关逻辑焦点的详细信息,请参阅焦点概述。
WPF 输入 API 提供了与坐标空间有关的有用信息。 例如,坐标 (0,0)
为左上角坐标,但该坐标是树中那一个元素的左上角坐标? 是属于输入目标的元素? 是在其上附加事件处理程序的元素? 还是其他内容? 为了避免混淆,WPF 输入 API 要求,在处理通过鼠标获取的坐标时,应指定参考框架。 GetPosition 方法返回鼠标指针相对于指定元素的坐标。
鼠标设备专门保留称为鼠标捕获的模式特征。 鼠标捕获用于在拖放操作开始时保持转换的输入状态,从而不一定发生涉及鼠标指针的标称屏幕位置的其他操作。 拖动过程中,未终止拖放时用户无法单击,这使得大多数鼠标悬停提示在拖动来源拥有鼠标捕获时是不合适的。 输入系统公开了可确定鼠标捕获状态的 API 以及可强制在特定元素上捕获鼠标或清除鼠标捕获状态的 API。 有关拖放操作的详细信息,请参阅拖放概述。
使用命令,输入处理可以更多地在语义级别(而不是在设备输入级别)进行。 命令是简单的指令,如 Cut
、Copy
、Paste
或 Open
。 命令可用于集中命令逻辑。 可从 Menu、在 ToolBar 上或者通过键盘快捷方式使用相同的命令。 命令还提供一种机制,用于在命令不可用时禁用控件。
RoutedCommand 是 ICommand 的 WPF 实现。 当执行 RoutedCommand 时,将在命令目标上引发 PreviewExecuted 和 Executed 事件,这会像其他输入一样,在元素树中发生隧道操作和浮升操作。 如果未设置命令目标,则具有键盘焦点的元素将成为命令目标。 执行命令的逻辑将附加到 CommandBinding。 当 Executed 事件到达该特定命令的 CommandBinding 时,将调用 CommandBinding 上的 ExecutedRoutedEventHandler。 此处理程序执行该命令的操作。
有关命令的详细信息,请参阅命令概述。
WPF 提供由 ApplicationCommands、MediaCommands、ComponentCommands、NavigationCommands 和 EditingCommands 组成的常用命令库,你也可以定义自己的命令。
以下示例显示了如何设置 MenuItem,以便在单击时它将调用 TextBox 上的 Paste 命令,假定 TextBox 具有键盘焦点。
<StackPanel>
<MenuItem Command="ApplicationCommands.Paste" />
</Menu>
<TextBox />
</StackPanel>
// Creating the UI objects
StackPanel mainStackPanel = new StackPanel();
TextBox pasteTextBox = new TextBox();
Menu stackPanelMenu = new Menu();
MenuItem pasteMenuItem = new MenuItem();
// Adding objects to the panel and the menu
stackPanelMenu.Items.Add(pasteMenuItem);
mainStackPanel.Children.Add(stackPanelMenu);
mainStackPanel.Children.Add(pasteTextBox);
// Setting the command to the Paste command
pasteMenuItem.Command = ApplicationCommands.Paste;
// Setting the command target to the TextBox
pasteMenuItem.CommandTarget = pasteTextBox;
' Creating the UI objects
Dim mainStackPanel As New StackPanel()
Dim pasteTextBox As New TextBox()
Dim stackPanelMenu As New Menu()
Dim pasteMenuItem As New MenuItem()
' Adding objects to the panel and the menu
stackPanelMenu.Items.Add(pasteMenuItem)
mainStackPanel.Children.Add(stackPanelMenu)
mainStackPanel.Children.Add(pasteTextBox)
' Setting the command to the Paste command
pasteMenuItem.Command = ApplicationCommands.Paste
有关 WPF 中的命令的详细信息,请参阅命令概述。
输入事件(例如由 Mouse、Keyboard 和 Stylus 类定义的附加事件)由输入系统引发,并基于在运行时命中测试可视化树来注入到对象模型中的某个特定位置。
Mouse、Keyboard 和 Stylus 定义为附加事件的每个事件也会由基元素类 UIElement 和 ContentElement 重新公开为新路由事件。 基元素路由事件由处理原始附加事件并重用事件数据的类生成。
当输入事件通过其基元素输入事件实现与特定源元素相关联时,可以通过基于逻辑和可视化树对象的组合的事件路由的其余部分进行路由,并由应用程序代码进行处理。 通常,使用 UIElement 和 ContentElement 上的路由事件处理这些与设备有关的输入事件更为方便,因为可以使用 XAML 中和代码中更直观的事件处理程序语法。 你可以选择处理发起进程的附加事件,但将会面临几个问题:附加事件可能会被基元素类处理标记为已处理,并且你需要使用访问器方法(而不是真正的事件语法)才能为附加事件附加处理程序。
现在有多种方法来处理 WPF 中的输入。 你还应该对 WPF 使用的各种类型的输入事件和路由事件机制有进一步的了解。
也可以获取更详细说明 WPF 框架元素和事件路由的详细资源。 有关详细信息,请参阅以下概述:命令概述、焦点概述、基元素概述、WPF 中的树和路由事件概述。
路由事件概述
基元素概述