Windows Presentation Foundation (WPF) 子系统提供了一个功能强大的 API,用于从各种设备(包括鼠标、键盘、触摸和触笔)获取输入。 本主题介绍 WPF 提供的服务,并介绍了输入系统的体系结构。
输入 API
主要输入 API 暴露在基元素类上找到:UIElement、ContentElement、FrameworkElement和 FrameworkContentElement。 有关基元素的详细信息,请参阅 基本元素概述。 这些类提供与按键、鼠标按钮、鼠标滚轮、鼠标移动、焦点管理和鼠标捕获相关的输入事件的功能,例如几个。 通过在基元素上放置输入 API,而不是将所有输入事件视为服务,输入体系结构使输入事件能够由 UI 中的特定对象进行源,并支持事件路由方案,其中多个元素有机会处理输入事件。 许多输入事件都有一对与其关联的事件。 例如,按键按下事件与 KeyDown 和 PreviewKeyDown 事件相关联。 这些事件的差异在于如何路由到目标元素。 预览事件从根元素沿元素树结构向下传播到目标元素。 冒泡事件从目标元素向上冒泡到根元素。 本概述和 路由事件概述中将更详细地讨论 WPF 中的事件路由。
键盘与鼠标类
除了基元素类上的输入 API 外, Keyboard 类和 Mouse 类还提供用于处理键盘和鼠标输入的其他 API。
类上的 Keyboard 输入 API 示例是 Modifiers 返回当前按下的属性 ModifierKeys ,以及 IsKeyDown 确定是否按下指定键的方法。
下面的示例使用 GetKeyStates 该方法来确定 a Key 是否处于关闭状态。
// Uses the Keyboard.GetKeyStates to determine if a key is down.
// A bitwise AND operation is used in the comparison.
// e is an instance of KeyEventArgs.
if ((Keyboard.GetKeyStates(Key.Return) & KeyStates.Down) > 0)
{
btnNone.Background = Brushes.Red;
}
' Uses the Keyboard.GetKeyStates to determine if a key is down.
' A bitwise AND operation is used in the comparison.
' e is an instance of KeyEventArgs.
If (Keyboard.GetKeyStates(Key.Return) And KeyStates.Down) > 0 Then
btnNone.Background = Brushes.Red
类MiddleButton上的Mouse输入 API 示例,它获取中间鼠标按钮的状态,以及DirectlyOver获取鼠标指针当前悬停的元素。
以下示例将确定鼠标上的 LeftButton 是否处于 Pressed 状态。
if (Mouse.LeftButton == MouseButtonState.Pressed)
{
UpdateSampleResults("Left Button Pressed");
}
If Mouse.LeftButton = MouseButtonState.Pressed Then
UpdateSampleResults("Left Button Pressed")
End If
本概述中详细介绍了这些 Mouse 和 Keyboard 类。
触笔输入
WPF 已集成支持 Stylus。 Stylus这是平板电脑流行的笔输入。 WPF 应用程序可以使用鼠标 API 将触笔视为鼠标,但 WPF 还会公开触笔设备抽象,该抽象使用类似于键盘和鼠标的模型。 所有与触笔相关的 API 都包含“触笔”一词。
由于触笔可以充当鼠标,因此仅支持鼠标输入的应用程序仍可自动获得某种级别的触笔支持。 以这种方式使用触笔时,应用程序有机会处理相应的触笔事件,然后处理相应的鼠标事件。 此外,还可以通过触笔设备抽象使用更高级别的服务(如墨迹输入)。 有关墨迹作为输入的详细信息,请参阅 Ink 入门。
事件路由
A FrameworkElement 可以在其内容模型中包含其他元素作为子元素,从而形成元素树。 在 WPF 中,父元素可以通过处理事件来参与输入,参与对象包括其子元素与其他后代。 这对于从较小的控件(称为“控件组合”或“组合”)中生成控件特别有用。有关元素树以及元素树与事件路由的关系的详细信息,请参阅 WPF 中的树。
事件路由是将事件转发到多个元素的过程,以便路由中的特定对象或元素可以选择为可能由其他元素源的事件提供重要的响应(通过处理)。 路由事件使用三种路由机制之一:直达、冒泡和隧道式。 在直接路由中,源元素是唯一通知的元素,事件不会路由到任何其他元素。 但是,直接路由事件仍提供一些其他功能,这些功能仅适用于路由事件,而不是标准 CLR 事件。 事件冒泡沿着元素树向上进行,首先通知触发事件的元素,然后是父元素,依此类推。 遍历从元素树的根开始,向下传递,最终到达原始源元素。 有关路由事件的详细信息,请参阅 路由事件概述。
WPF 输入事件通常成对,由隧道事件和冒泡事件组成。 隧道事件与带有“预览”前缀的冒泡事件区分开来。 例如, PreviewMouseMove 是鼠标移动事件的隧道版本,并且 MouseMove 是此事件的冒泡版本。 此事件配对是在元素级别实现的约定,不是 WPF 事件系统的固有功能。 有关详细信息,请参阅 路由事件概述中的 WPF 输入事件部分。
处理输入事件
若要接收元素上的输入,事件处理程序必须与该特定事件相关联。 在 XAML 中,这非常简单:将事件的名称引用为将侦听此事件的元素的属性。 然后,将特性的值设置为基于委托定义的事件处理程序的名称。 事件处理程序必须以 C# 等代码编写,并且可以包含在代码隐藏文件中。
当操作系统报告键盘焦点位于某元素时发生的按键动作时,会产生键盘事件。 鼠标和触笔事件分别分为两个类别:报告指针位置相对于元素的指针位置更改的事件,以及报告设备按钮状态更改的事件。
键盘输入事件示例
以下示例侦听左箭头键的按下。 StackPanel 被创建,并且具有 Button。 侦听左箭头键按下的事件处理程序已附加到 Button 实例。
该示例的第一部分创建StackPanel和Button,并为KeyDown附加事件处理程序。
<StackPanel>
<Button Background="AliceBlue"
KeyDown="OnButtonKeyDown"
Content="Button1"/>
</StackPanel>
// Create the UI elements.
StackPanel keyboardStackPanel = new StackPanel();
Button keyboardButton1 = new Button();
// Set properties on Buttons.
keyboardButton1.Background = Brushes.AliceBlue;
keyboardButton1.Content = "Button 1";
// Attach Buttons to StackPanel.
keyboardStackPanel.Children.Add(keyboardButton1);
// Attach event handler.
keyboardButton1.KeyDown += new KeyEventHandler(OnButtonKeyDown);
' Create the UI elements.
Dim keyboardStackPanel As New StackPanel()
Dim keyboardButton1 As New Button()
' Set properties on Buttons.
keyboardButton1.Background = Brushes.AliceBlue
keyboardButton1.Content = "Button 1"
' Attach Buttons to StackPanel.
keyboardStackPanel.Children.Add(keyboardButton1)
' Attach event handler.
AddHandler keyboardButton1.KeyDown, AddressOf OnButtonKeyDown
第二节是用代码编写的,并定义事件处理程序。 按下向左箭头键并且 Button 具有键盘焦点时,处理程序将运行,并且 Button 的 Background 颜色将被更改。 如果按下了该键,但它不是左箭头键,则Button的Background颜色将被改回其初始颜色。
private void OnButtonKeyDown(object sender, KeyEventArgs e)
{
Button source = e.Source as Button;
if (source != null)
{
if (e.Key == Key.Left)
{
source.Background = Brushes.LemonChiffon;
}
else
{
source.Background = Brushes.AliceBlue;
}
}
}
Private Sub OnButtonKeyDown(ByVal sender As Object, ByVal e As KeyEventArgs)
Dim source As Button = TryCast(e.Source, Button)
If source IsNot Nothing Then
If e.Key = Key.Left Then
source.Background = Brushes.LemonChiffon
Else
source.Background = Brushes.AliceBlue
End If
End If
End Sub
鼠标输入事件示例
在以下示例中,当鼠标指针进入Button时,Button的颜色Background会被更改。 当鼠标离开Button时,颜色Background将还原。
该示例的第一部分创建了StackPanel和Button控件,并将MouseEnter和MouseLeave事件的事件处理程序附加到Button。
<StackPanel>
<Button Background="AliceBlue"
MouseEnter="OnMouseExampleMouseEnter"
MouseLeave="OnMosueExampleMouseLeave">Button
</Button>
</StackPanel>
// Create the UI elements.
StackPanel mouseMoveStackPanel = new StackPanel();
Button mouseMoveButton = new Button();
// Set properties on Button.
mouseMoveButton.Background = Brushes.AliceBlue;
mouseMoveButton.Content = "Button";
// Attach Buttons to StackPanel.
mouseMoveStackPanel.Children.Add(mouseMoveButton);
// Attach event handler.
mouseMoveButton.MouseEnter += new MouseEventHandler(OnMouseExampleMouseEnter);
mouseMoveButton.MouseLeave += new MouseEventHandler(OnMosueExampleMouseLeave);
' Create the UI elements.
Dim mouseMoveStackPanel As New StackPanel()
Dim mouseMoveButton As New Button()
' Set properties on Button.
mouseMoveButton.Background = Brushes.AliceBlue
mouseMoveButton.Content = "Button"
' Attach Buttons to StackPanel.
mouseMoveStackPanel.Children.Add(mouseMoveButton)
' Attach event handler.
AddHandler mouseMoveButton.MouseEnter, AddressOf OnMouseExampleMouseEnter
AddHandler mouseMoveButton.MouseLeave, AddressOf OnMosueExampleMouseLeave
该示例的第二部分是用代码编写的,并定义事件处理程序。 当鼠标进入 Button时, Background 颜色 Button 将更改为 SlateGray。 当鼠标离开Button时,Button的Background颜色将更改回AliceBlue。
private void OnMouseExampleMouseEnter(object sender, MouseEventArgs e)
{
// Cast the source of the event to a Button.
Button source = e.Source as Button;
// If source is a Button.
if (source != null)
{
source.Background = Brushes.SlateGray;
}
}
Private Sub OnMouseExampleMouseEnter(ByVal sender As Object, ByVal e As MouseEventArgs)
' Cast the source of the event to a Button.
Dim source As Button = TryCast(e.Source, Button)
' If source is a Button.
If source IsNot Nothing Then
source.Background = Brushes.SlateGray
End If
End Sub
private void OnMosueExampleMouseLeave(object sender, MouseEventArgs e)
{
// Cast the source of the event to a Button.
Button source = e.Source as Button;
// If source is a Button.
if (source != null)
{
source.Background = Brushes.AliceBlue;
}
}
Private Sub OnMosueExampleMouseLeave(ByVal sender As Object, ByVal e As MouseEventArgs)
' Cast the source of the event to a Button.
Dim source As Button = TryCast(e.Source, Button)
' If source is a Button.
If source IsNot Nothing Then
source.Background = Brushes.AliceBlue
End If
End Sub
文本输入
事件 TextInput 使你能够以独立于设备的方式侦听文本输入。 键盘是文本输入的主要方式,但语音、手写和其他输入设备也可以生成文本输入。
对于键盘输入,WPF 首先发送相应的 KeyDown/KeyUp 事件。 如果未处理这些事件,并且按键是文本键(而不是方向箭头或功能键这样的控制键),那么将会引发TextInput事件。 由于多个击键可以生成文本输入的单个字符,并且单个击键可以生成多字符字符串,因此在事件之间KeyDown/KeyUpTextInput并不总是有一个简单的一对一映射。 对于使用输入法编辑器(IME)生成数千个可能字符的语言(如中文、日语和朝鲜语)尤其如此。
当 WPF 发送 KeyUp/KeyDown 事件时,如果击键可能成为 TextInput 事件的一部分(例如,如果按下 ALT+S),则将 Key 设置为 Key.System。 这样,事件处理程序中的 KeyDown 代码就可以检查 Key.System ,如果找到,则将处理移交给随后触发的 TextInput 事件的处理程序。 在这些情况下,参数的各种属性 TextCompositionEventArgs 可用于确定原始击键。 同样,如果 IME 处于活动状态,Key 的值为 Key.ImeProcessed,而 ImeProcessedKey 则返回原始击键或击键序列。
以下示例定义事件的 Click 处理程序和事件的 KeyDown 处理程序。
代码或标记的第一段将创建用户界面。
<StackPanel KeyDown="OnTextInputKeyDown">
<Button Click="OnTextInputButtonClick"
Content="Open" />
<TextBox> . . . </TextBox>
</StackPanel>
// Create the UI elements.
StackPanel textInputStackPanel = new StackPanel();
Button textInputeButton = new Button();
TextBox textInputTextBox = new TextBox();
textInputeButton.Content = "Open";
// Attach elements to StackPanel.
textInputStackPanel.Children.Add(textInputeButton);
textInputStackPanel.Children.Add(textInputTextBox);
// Attach event handlers.
textInputStackPanel.KeyDown += new KeyEventHandler(OnTextInputKeyDown);
textInputeButton.Click += new RoutedEventHandler(OnTextInputButtonClick);
' Create the UI elements.
Dim textInputStackPanel As New StackPanel()
Dim textInputeButton As New Button()
Dim textInputTextBox As New TextBox()
textInputeButton.Content = "Open"
' Attach elements to StackPanel.
textInputStackPanel.Children.Add(textInputeButton)
textInputStackPanel.Children.Add(textInputTextBox)
' Attach event handlers.
AddHandler textInputStackPanel.KeyDown, AddressOf OnTextInputKeyDown
AddHandler textInputeButton.Click, AddressOf OnTextInputButtonClick
代码的第二段包含事件处理程序。
private void OnTextInputKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.O && Keyboard.Modifiers == ModifierKeys.Control)
{
handle();
e.Handled = true;
}
}
private void OnTextInputButtonClick(object sender, RoutedEventArgs e)
{
handle();
e.Handled = true;
}
public void handle()
{
MessageBox.Show("Pretend this opens a file");
}
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 Touch 的设备,例如触摸屏。
术语
讨论触摸时,将使用以下术语。
触摸 是 Windows 7 识别的用户输入类型。 通常,触摸是通过将手指放在触摸敏感屏幕上来启动的。 请注意,如果设备只是将手指的位置和移动作为鼠标输入转换,笔记本电脑上常用的触摸板等设备不支持触摸。
Multitouch 是同时从多个点发生的触摸。 Windows 7 和 WPF 支持多点触控。 每当在WPF文档中提到触摸时,这些概念同样适用于多点触控。
当触摸被解释为对物体进行物理操作时,就会发生操控。 在 WPF 中,操作事件将输入解释为平移、缩放或旋转操作。
例如触摸屏上的单个手指,
touch device
表示生成触摸输入的设备。
响应触摸的控件
如果控件的内容可以滚动至视图之外,则可通过在控件上拖动手指来滚动以下控件。
ScrollViewer 定义了 ScrollViewer.PanningMode 这样一个附加属性,使您能够指定触摸平移是水平启用、垂直启用、两者均启用还是均不启用。 ScrollViewer.PanningDeceleration 属性指定了当用户从触摸屏上抬起手指时,滚动减速的快慢。 ScrollViewer.PanningRatio附加属性指定滚动偏移量与转换偏移量的比率。
触摸事件
基类、 UIElement、 UIElement3D以及 ContentElement定义可以订阅的事件,以便应用程序响应触摸。 当应用程序将触摸解释为对象以外的其他用途时,触摸事件非常有用。 例如,允许用户使用一个或多个手指绘制的应用程序将订阅触摸事件。
所有三个类都定义以下事件,无论定义类如何,这些事件的行为都类似。
与键盘和鼠标事件一样,触摸事件是路由事件。 以 Preview
开头的事件是隧道事件,以 Touch
开头的事件是冒泡事件。 有关路由事件的详细信息,请参阅 路由事件概述。 处理这些事件时,可以通过调用 GetTouchPoint 或 GetIntermediateTouchPoints 方法获取输入相对于任何元素的位置。
若要了解触摸事件之间的交互,请考虑用户将一根手指放在元素上的场景,将手指移到元素中,然后从元素中抬起手指。 下图显示了浮泡事件的执行(为简单起见,省略隧道事件)。
触摸事件
以下列表描述了上图中事件的顺序。
用户将手指放在元素上时,该事件只发生一次。
事件TouchDown发生一次。
当用户 TouchMove 在元素内移动手指时多次发生该事件。
TouchUp当用户从元素中抬起手指时发生一次该事件。
事件发生 TouchLeave 一次。
使用两个以上手指时,每个手指都会触发事件。
操作事件
对于应用程序在某些情况下使用户能够操作对象,类 UIElement 定义操作事件。 与仅报告触摸位置的触摸事件不同,作事件报告如何解释输入。 有三种类型的操作:平移、扩展和旋转。 以下列表介绍了如何调用三种类型的操作。
将手指放在对象上,然后在触摸屏上移动手指,调用翻译操作。 这通常移动对象。
将两根手指放在一个对象上,并将手指移到一起或分开,以调用扩展手势。 这通常调整对象的大小。
将两根手指放在对象上,并互相旋转手指以调用旋转作。 通常,这会旋转物体。
可以同时发生多种类型的操作。
当你让对象对操作产生响应时,可以让对象看起来具有惯性。 这可以使对象模拟物理世界。 例如,当你用力把书在桌子上推时,如果推得够用力,那么在你松开后,书还会继续移动。 WPF 使你可以通过在用户手指释放对象后引发作事件来模拟此行为。
有关如何创建使用户能够移动、调整大小和旋转对象的应用程序的信息,请参阅 演练:创建第一个触控应用程序。
UIElement 定义了以下操作事件。
默认情况下,UIElement 不会收到这些操作事件。 若要接收UIElement的操作事件,请将UIElement.IsManipulationEnabled设置为true
。
操作事件的执行路径
请考虑用户“投掷”对象的情况。 用户将手指放在对象上,在触摸屏上移动手指短距离,然后在移动时抬起手指。 结果是对象将在用户的手指下移动,并在用户抬起手指后继续移动。
下图显示了操作事件的执行路径以及有关每个事件的重要信息。
操控事件
以下列表描述了上图中事件的顺序。
该事件ManipulationStarting发生在用户在对象上放置手指时。 除此之外,此事件还允许设置属性 ManipulationContainer 。 在后续事件中,操作的位置将相对于ManipulationContainer。 在非 ManipulationStarting事件中,此属性是只读的,因此事件 ManipulationStarting 是唯一可以设置此属性的时间。
接下来将发生 ManipulationStarted 事件。 这个事件报告操控的起源。
当用户 ManipulationDelta 的手指在触摸屏上移动时多次发生该事件。 类 DeltaManipulation 的属性 ManipulationDeltaEventArgs 用于报告操作是被解释为移动、扩展还是平移。 这是你执行大部分操作对象工作的地方。
ManipulationInertiaStarting当用户的手指失去与对象的接触时发生该事件。 此事件允许你指定惯性期间的操作减速。 因此,如果你选择,你的对象可以模拟不同的物理空间或属性。 例如,假设应用程序有两个对象表示物理世界中的项,一个对象比另一个对象重。 可以使较重的对象减速速度比较轻的对象快。
ManipulationDelta 事件在惯性作用下发生多次。 请注意,当用户的手指在触摸屏上移动以及 WPF 模拟惯性时,会发生此事件。 换句话说,ManipulationDelta 发生在 ManipulationInertiaStarting 事件的前后。 ManipulationDeltaEventArgs.IsInertial 属性报告 ManipulationDelta 事件是否在惯性期间发生,因此你可以检查该属性并根据其值执行不同的操作。
ManipulationCompleted 事件会在操作和任何惯性结束时发生。 也就是说,在发生所有 ManipulationDelta 事件后,事件 ManipulationCompleted 发生以指示操作已完成。
它还定义了 UIElement 的 ManipulationBoundaryFeedback 事件。 在ManipulationDelta事件中调用ReportBoundaryFeedback方法时,将发生这一事件。 该 ManipulationBoundaryFeedback 事件使应用程序或组件能够在对象达到边界时提供视觉反馈。 例如,Window 类通过处理 ManipulationBoundaryFeedback 事件来导致窗口在遇到其边缘时稍微移动。
可以通过在任何操作事件(除ManipulationBoundaryFeedback事件)中调用Cancel方法来取消操作。 调用Cancel时,操作事件不再触发,鼠标事件将用于触摸。 下表描述了取消操作的时间与鼠标事件发生之间的关系。
调用“Cancel”时的事件 | 已发生输入的鼠标事件 |
---|---|
ManipulationStarting 和 ManipulationStarted | 鼠标向下事件。 |
ManipulationDelta | 鼠标向下移动和鼠标移动事件。 |
ManipulationInertiaStarting 和 ManipulationCompleted | 鼠标向下、鼠标移动和鼠标向上事件。 |
请注意,如果在作处于惯性状态时调用 Cancel ,该方法将 false
返回,并且输入不会引发鼠标事件。
触摸和操作事件之间的关系
一个 UIElement 始终可以接收触摸事件。 当属性 IsManipulationEnabled 设置为 true
时,可以 UIElement 同时接收触摸和作事件。 如果TouchDown事件未处理(即Handled属性为false
),操作逻辑将捕获触摸到元素的事件并生成操作事件。 如果在TouchDown事件中将Handled属性设置为true
,则操作逻辑不会生成操作事件。 下图显示了触摸事件与操控事件之间的关系。
触摸和操控事件
以下列表描述了上面插图中触摸事件和操作事件之间的关系。
当第一个 TouchDown 触摸设备在 a UIElement上生成事件时,作逻辑将调用 CaptureTouch 该方法,该方法将生成该 GotTouchCapture 事件。
GotTouchCapture发生时,作逻辑将调用Manipulation.AddManipulator生成ManipulationStarting事件的方法。
当TouchMove事件发生时,操作逻辑将生成在ManipulationInertiaStarting事件之前发生的ManipulationDelta事件。
当元素上的最后一个触摸设备引发 TouchUp 事件时,操作逻辑将生成 ManipulationInertiaStarting 事件。
重点
WPF 中的焦点有两个主要概念:键盘焦点和逻辑焦点。
键盘焦点
键盘焦点是指接收键盘输入的元素。 整个桌面上只能有一个具有键盘焦点的元素。 在 WPF 中,具有键盘焦点的元素将 IsKeyboardFocused 设置为 true
。 静态 Keyboard 方法 FocusedElement 返回当前具有键盘焦点的元素。
可以通过按 Tab 键键获取键盘焦点,也可以通过单击某些元素上的鼠标来获取键盘焦点,例如 TextBox。 还可以使用 Focus 类上的 Keyboard 方法以编程方式获取键盘焦点。 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来将元素转换为可扩展应用程序标记语言(XAML)中的焦点范围,或者在代码中通过使用SetIsFocusScope方法设置附加属性。
以下示例通过设置StackPanel附加属性,将IsFocusScope转换为焦点范围。
<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和 ToolBarContextMenu。
具有键盘焦点的元素也将具备其所属焦点范围的逻辑焦点;因此,通过在 Focus 类或基元素类上使用 Keyboard 方法设置焦点时,将尝试为该元素提供键盘焦点和逻辑焦点。
若要确定焦点范围内的焦点元素,请使用 GetFocusedElement。 若要更改焦点范围的焦点元素,请使用 SetFocusedElement。
有关逻辑焦点的详细信息,请参阅 焦点概述。
鼠标位置
WPF 输入 API 提供有关坐标空间的有用信息。 例如,坐标 (0,0)
是左上角坐标,但树中哪个元素的左上角? 哪个元素是输入的目标? 你附加了事件处理程序的元素是什么? 还是别的? 为了避免混淆,WPF 输入 API 要求在处理通过鼠标获取的坐标时指定引用帧。 该方法 GetPosition 返回鼠标指针相对于指定元素的坐标。
鼠标捕获
鼠标设备专门保存称为鼠标捕获的模式特征。 鼠标捕获用于在启动拖放操作时保持过渡输入状态,以避免发生其他涉及鼠标指针屏幕名义位置的操作。 在拖动期间,用户无法单击而不中止拖放,这使得大多数鼠标悬停提示在鼠标捕获由拖动原点保持时不合适。 输入系统公开了可以确定鼠标捕获状态的 API,以及可以强制将鼠标捕获到特定元素的 API,或清除鼠标捕获状态。 有关拖放操作的详细信息,请参阅 拖放概述。
指令
命令允许输入在比设备输入更高的语义层次上进行处理。 命令是简单的指令,例如Cut
,Copy
或Paste
Open
。 命令可用于集中命令逻辑。 同一Menu命令可以通过Menu、ToolBar或通过键盘快捷方式进行访问。 命令还提供一种机制,用于在命令不可用时禁用控件。
RoutedCommand 是 ICommand 的 WPF 实现。 执行RoutedCommand时,在命令目标上会引发PreviewExecuted事件和Executed事件,这些事件像其他输入一样在元素树中传递和冒泡。 如果未设置命令目标,具有键盘焦点的元素将是命令目标。 执行命令的逻辑附加到CommandBinding。 当某个事件达到该特定命令的CommandBinding时,将调用ExecutedRoutedEventHandler在CommandBinding上。 此处理程序执行命令的操作。
有关命令的详细信息,请参阅 命令概述。
WPF 提供一个常用命令库,由 ApplicationCommands、MediaCommands、ComponentCommands、NavigationCommands 和 EditingCommands 组成,或者您也可以定义自己的命令。
假设 TextBox 具有键盘焦点,下面的示例演示如何设置一个 MenuItem,以便在被单击时在 TextBox 上调用 Paste 命令。
<StackPanel>
<Menu>
<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 重新公开为新的路由事件。 基元素路由事件由处理原始附加事件的类生成,并重用事件数据。
当输入事件通过其基元素输入事件实现与特定源元素关联时,可以通过基于逻辑和可视化树对象组合的事件路由的其余部分进行路由,并由应用程序代码处理。 通常,使用路由事件处理这些与设备相关的输入事件UIElementContentElement更为方便,因为可以在 XAML 和代码中使用更直观的事件处理程序语法。 可以选择处理启动进程的附加事件,但会遇到几个问题:附加事件可能由基元素类处理进行标记,并且需要使用访问器方法而不是真正的事件语法来附加附加事件的处理程序。
后续步骤
现在,可以使用多种方法来处理 WPF 中的输入。 还应更好地了解 WPF 使用的各种类型的输入事件和路由事件机制。
提供了其他资源来更详细地解释 WPF 框架元素和事件路由。 有关详细信息,请参阅以下概述: 命令概述、 焦点概述、 基本元素概述、 WPF 中的树和 路由事件概述。