输入概述

Windows Presentation Foundation (WPF) 子系统提供了一个功能强大的 API,用于从各种设备(包括鼠标、键盘、触摸和触笔)获取输入。 本主题介绍了 WPF 提供的服务,并说明了输入系统的体系结构。

输入 API

主要输入 API 公开存在于以下基元素类上:UIElementContentElementFrameworkElementFrameworkContentElement。 有关基元素的详细信息,请参阅基元素概述。 这些类提供有关输入事件(例如按键、鼠标按钮、鼠标滚轮、鼠标移动、焦点管理和鼠标捕获等)的功能。 通过将输入 API 放置在基元素上,而不是将所有输入事件视作一项服务,该输入体系结构使输入事件可以由 UI 中的特定对象指明其出处,并支持事件路由方案,从而使得多个元素有机会处理输入事件。 许多输入事件都具有与之相关联的一对事件。 例如,键盘按下事件与 KeyDownPreviewKeyDown 事件相关联。 这些事件的区别在于它们如何路由至目标元素。 预览事件将元素树从根元素到目标元素向下进行隧道操作。 冒泡事件从目标元素到根元素向上进行冒泡操作。 WPF 中的事件路由在本概述的后面和路由事件概述中有更详细的讨论。

键盘和鼠标类

除了基元素类上的输入 API 之外,Keyboard 类和 Mouse 类还提供了更多 API 来处理键盘和鼠标输入。

Keyboard 类上的输入 API 示例包括可返回当前按下的 ModifierKeysModifiers 属性和可确定是否按下了特定键的 IsKeyDown 方法。

以下示例使用 GetKeyStates 方法来确定 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

Mouse 类上的输入 API 示例包括可获取鼠标中键状态的 MiddleButton 和可获得鼠标指针当前指向的元素的 DirectlyOver

以下示例确定了鼠标的 LeftButton 是否处于 Pressed 状态。

if (Mouse.LeftButton == MouseButtonState.Pressed)
{
    UpdateSampleResults("Left Button Pressed");
}
If Mouse.LeftButton = MouseButtonState.Pressed Then
    UpdateSampleResults("Left Button Pressed")
End If

MouseKeyboard 类在本概述中有更详细的介绍。

触笔输入

WPF 集成了对 Stylus 的支持。 Stylus 是因 Tablet PC 而变得普及的一种笔输入。 WPF 应用程序可以通过使用鼠标 API 将触笔视为鼠标,但 API 也公开了触笔设备抽象,其使用的模型与键盘和鼠标类似。 所有与触笔相关的 API 都包含单词“Stylus”(触笔)。

由于触笔可充当鼠标,因此仅支持鼠标输入的应用程序仍可以自动获得一定程度的触笔支持。 以这种方式使用触笔时,应用程序有能力处理相应的触笔事件,然后处理相应的鼠标事件。 此外,通过触笔设备抽象也可以使用墨迹输入等较高级别的服务。 有关墨迹输入的详细信息,请参阅墨迹入门

事件路由

FrameworkElement 可以在其内容模型中包含其他元素作为子元素,从而形成一个元素树。 在 WPF 中,父元素可以通过处理事件来参与定向到其子元素或其他后代的输入。 这对于从较小的控件生成控件(该过程称为“控件组合”或“组合”)特别有用。有关元素树以及元素树与事件路由的关系的详细信息,请参阅 WPF 中的树

事件路由是将事件转发到多个元素的过程,以便使路由中的特定对象或元素可以选择对已由其他元素指明来源的事件提供重要响应(通过处理)。 路由事件使用三种路由机制的其中一种:直接、浮升和隧道。 在直接路由中,源元素是收到通知的唯一元素,事件不会路由至任何其他元素。 但是相对于标准 CLR 事件,直接路由事件仍然提供一些仅针对路由事件而存在的其他功能。 浮升操作在元素树中向上进行,首先通知指明了事件来源的第一个元素,然后是父元素等等。 隧道操作从元素树的根开始,然后向下进行,以原始的源元素结束。 有关路由事件的详细信息,请参阅路由事件概述

WPF 输入事件通常成对出现,由一个隧道事件和一个浮升事件组成。 隧道事件与冒泡事件的不同之处在于它有“预览”前缀。 例如,PreviewMouseMove 是鼠标移动事件的隧道版本,MouseMove 是此事件的浮升版本。 此事件配对是在元素级别实现的一种约定,不是 WPF 事件系统的固有功能。 有关详细信息,请参阅路由事件概述中的 WPF 输入事件部分。

处理输入事件

若要在元素上接收输入,必须将事件处理程序与该特定事件关联。 在 XAML 中,这很简单:将事件的名称作为要侦听此事件的元素的特性进行引用。 然后,根据委托,将特性的值设置为所定义的事件处理程序的名称。 事件处理程序必须用代码(例如 C#)编写,并且可以包含在代码隐藏文件中。

当操作系统报告发生键操作时,如果键盘焦点正处在元素上,则将发生键盘事件。 鼠标和触笔事件分别分为两类:报告指针位置相对于元素的变化的事件,和报告设备按钮状态的变化的事件。

键盘输入事件示例

以下示例侦听按下向左键的操作。 创建了一个具有 ButtonStackPanel。 用于侦听按下向左键的事件处理程序附加到了 Button 实例。

示例的第一部分创建了 StackPanelButton,并且附加了 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 具有键盘焦点时,处理程序会运行,且 ButtonBackground 颜色会发生变化。 如果按下了一个键,但不是向左键,ButtonBackground 颜色将变回其初始颜色。

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 时,ButtonBackground 颜色会发生变化。 当鼠标离开 Button 时,Background 颜色将还原。

示例的第一部分创建了 StackPanelButton 控件,并将 MouseEnterMouseLeave 事件附加到了 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 时,ButtonBackground 颜色将变为 SlateGray。 当鼠标离开 Button 时,ButtonBackground 颜色将变回 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 触控的设备,如触摸屏。

术语

讨论触摸时使用了以下术语。

  • 触摸是 Windows 7 可识别的一种用户输入。 通常,将手指放在触敏式屏幕上会触发触摸。 请注意,如果设备仅将手指的位置和移动转换为鼠标输入,则笔记本电脑上常用的触摸板等设备不支持触摸。

  • 多点触摸是同时发生在多个点上的触摸。 Windows 7 和 WPF 支持多点触摸。 WPF 文档中每当论及触摸时,相关概念均适用于多点触摸。

  • 当触摸被解释为应用于对象的实际操作时,就发生了操作。 在 WPF 中,操作事件将输入解释为平移、展开或旋转操作。

  • touch device 表示产生触摸输入的设备,例如触摸屏上的一根手指。

响应触摸的控件

如果以下控件的内容延伸到视图之外,则可以通过在控件上拖动手指来滚动该控件。

ScrollViewer 定义了 ScrollViewer.PanningMode 附加属性,该属性让你可以指定是水平、垂直、同时以这两种方式还是不以任何一种方式启用触摸移动。 ScrollViewer.PanningDeceleration 属性指定当用户从触摸屏上抬起手指时滚动速度减慢的速度。 ScrollViewer.PanningRatio 附加属性指定滚动偏移与平移操作偏移的比率。

触摸事件

基类 UIElementUIElement3DContentElement 定义你可以订阅的事件,以便你的应用程序对触摸做出响应。 当应用程序将触摸解释为操作对象以外的其他操作时,触摸事件非常有用。 例如,使用户能够以一个或多个手指绘制的应用程序将订阅触摸事件。

所有三个类都定义了以下事件,其行为类似,而无论定义类是什么。

像键盘和鼠标事件一样,触摸事件也是路由事件。 以 Preview 开头的事件是隧道事件,以 Touch 开头的事件是冒泡事件。 有关路由事件的详细信息,请参阅路由事件概述。 当你处理这些事件时,可以通过调用 GetTouchPointGetIntermediateTouchPoints 方法获得输入相对于任何元素的位置。

为了理解触控事件之间的交互,请考虑以下这种情况:用户将一个手指放在元素上,在该元素中移动手指,然后将手指从该元素上移开。 下图显示了冒泡事件的执行(为简单起见,省略了隧道事件)。

The sequence of touch events. 触摸事件

下列内容描述了上图中的事件顺序。

  1. 当用户将手指放在元素上时,会发生一次 TouchEnter 事件。

  2. 会发生一次 TouchDown 事件。

  3. 当用户在元素中移动手指时,会发生多次 TouchMove 事件。

  4. 当用户从元素抬起手指时,会发生一次 TouchUp 事件。

  5. 会发生一次 TouchLeave 事件。

当使用两根以上的手指时,每根手指都会发生事件。

操作事件

对于应用程序支持用户操作对象的情况,UIElement 类定义了操作事件。 与只是报告触摸位置的触摸事件不同,操作事件会报告可采用何种方式解释输入。 有三种类型的操作:转换、扩展和旋转。 下列内容介绍了如何调用这三种类型的操作。

  • 将一根手指放在对象上,并在触摸屏上拖动手指以调用转换操作。 此操作通常会移动对象。

  • 将两根手指放在物体上,并将手指相互靠拢或分开以调用扩展操作。 此操作通常会调整对象的大小。

  • 将两根手指放在对象上,并将一个手指围绕另一个手指旋转以调用旋转操作。 此操作通常会旋转对象。

多种类型的操作可以同时发生。

使对象响应操作时,可以让对象看起来具有惯性。 这样可以使对象模拟真实的世界。 例如,在桌子上推一本书时,如果你足够用力,书将在你松手后继续移动。 利用 WPF,可以通过在用户的手指松开对象后引发操作事件来模拟这种行为。

如需深入了解如何创建使用户可以对对象进行移动、调整大小和旋转的应用程序,请参阅演练:创建你的第一个触控应用程序

UIElement 定义了以下操作事件。

默认情况下,UIElement 不会收到这些操作事件。 若要在 UIElement 上收到操作事件,请将 UIElement.IsManipulationEnabled 设置为 true

操作事件的执行路径

考虑用户“抛出”一个对象的情况。 用户将手指放在对象上,将手指在触摸屏上移动一段短距离,然后在移动的同时抬起手指。 此操作的结果是,该对象将在用户的手指下方移动,并在用户抬起手指后继续移动。

下图显示了操作事件的执行路径和每个事件的重要信息。

The sequence of manipulation events. 操作事件

下列内容描述了上图中的事件顺序。

  1. 当用户将手指放在对象上时,会发生 ManipulationStarting 事件。 此外,此事件还允许你设置 ManipulationContainer 属性。 在后续事件中,操作的位置将相对于 ManipulationContainer。 在除 ManipulationStarting 之外的事件中,此属性是只读的,因此你只能在发生 ManipulationStarting 事件时设置此属性。

  2. 接下来会发生 ManipulationStarted 事件。 此事件报告操作的原始位置。

  3. 当用户的手指在触摸屏上移动时,会发生多次 ManipulationDelta 事件。 ManipulationDeltaEventArgs 类的 DeltaManipulation 属性报告操作是解释为移动、展开还是平移。 这是你执行操作对象的大部分工作的地方。

  4. 当用户的手指与对象失去接触时,会发生 ManipulationInertiaStarting 事件。 此事件使你可以指定操作在惯性期间的减速。 这样,选择时对象就可以模拟不同的物理空间或特性。 例如,假设应用程序有两个表示真实世界中的物品的对象,并且一个物品比另一个物品重。 你可以使较重的对象比较轻的对象减速更快。

  5. 在发生惯性时,会发生多次 ManipulationDelta 事件。 请注意,当用户的手指在触摸屏上移动并且 WPF 模拟惯性时,将发生此事件。 换句话说,在 ManipulationInertiaStarting 事件之前和之后,会发生 ManipulationDeltaManipulationDeltaEventArgs.IsInertial 属性报告在惯性期间是否发生了 ManipulationDelta 事件,以便你可以检查该属性并根据其值执行不同的操作。

  6. 当操作和任何惯性结束时,会发生 ManipulationCompleted 事件。 也就是说,在所有 ManipulationDelta 事件发生后,会发生 ManipulationCompleted 事件以指示操作已完成。

UIElement 还定义了 ManipulationBoundaryFeedback 事件。 在 ManipulationDelta 事件中调用 ReportBoundaryFeedback 方法时,会发生此事件。 ManipulationBoundaryFeedback 事件使应用程序或组件可以在对象到达边界时提供可视反馈。 例如,Window 类会处理 ManipulationBoundaryFeedback 事件,以便在到达窗口边缘时使窗口轻微移动。

你可以通过对任意操作事件中的事件参数调用 Cancel 方法来取消操作,ManipulationBoundaryFeedback 除外。 当你调用 Cancel 时,不再引发操作事件,触摸会发生鼠标事件。 下表描述了取消操作的时间与所发生的鼠标事件之间的关系。

在其中调用取消的事件 针对已经发生的输入发生的鼠标事件
ManipulationStartingManipulationStarted 鼠标按下事件。
ManipulationDelta 鼠标按下和鼠标移动事件。
ManipulationInertiaStartingManipulationCompleted 鼠标按下、鼠标移动和鼠标弹起事件。

请注意,如果你在操作处于惯性期间调用 Cancel,则该方法返回 false,并且输入不会引发鼠标事件。

触摸事件和操作事件之间的关系

UIElement 可以始终收到触摸事件。 当 IsManipulationEnabled 属性设置为 true 时,UIElement 会同时收到触摸和操作事件。 如果未处理 TouchDown 事件(即 Handled 属性为 false),则操作逻辑会将触摸捕获到元素并生成操作事件。 如果 Handled 属性在 TouchDown 事件中设置为 true,则操作逻辑不会生成操作事件。 下图显示了触摸事件和操作事件之间的关系。

Relationship between touch and manipulation events 触摸事件和操作事件

下列内容描述了上图中所示的触摸事件和操作事件之间的关系。

侧重点

在 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 中默认为焦点范围的类是 WindowMenuToolBarContextMenu

具有键盘焦点的元素还具有它所属的焦点范围的逻辑焦点;因此,在 Keyboard 类或基元素类上使用 Focus 方法将焦点设置在一个元素上将尝试赋予该元素键盘焦点和逻辑焦点。

要确定焦点范围中的焦点元素,请使用 GetFocusedElement。 若要更改焦点范围的焦点元素,请使用 SetFocusedElement

有关逻辑焦点的详细信息,请参阅焦点概述

鼠标位置

WPF 输入 API 提供了与坐标空间有关的有用信息。 例如,坐标 (0,0) 为左上角坐标,但该坐标是树中那一个元素的左上角坐标? 是属于输入目标的元素? 是在其上附加事件处理程序的元素? 还是其他内容? 为了避免混淆,WPF 输入 API 要求,在处理通过鼠标获取的坐标时,应指定参考框架。 GetPosition 方法返回鼠标指针相对于指定元素的坐标。

鼠标捕获

鼠标设备专门保留称为鼠标捕获的模式特征。 鼠标捕获用于在拖放操作开始时保持转换的输入状态,从而不一定发生涉及鼠标指针的标称屏幕位置的其他操作。 拖动过程中,未终止拖放时用户无法单击,这使得大多数鼠标悬停提示在拖动来源拥有鼠标捕获时是不合适的。 输入系统公开了可确定鼠标捕获状态的 API 以及可强制在特定元素上捕获鼠标或清除鼠标捕获状态的 API。 有关拖放操作的详细信息,请参阅拖放概述

命令

使用命令,输入处理可以更多地在语义级别(而不是在设备输入级别)进行。 命令是简单的指令,如 CutCopyPasteOpen。 命令可用于集中命令逻辑。 可从 Menu、在 ToolBar 上或者通过键盘快捷方式使用相同的命令。 命令还提供一种机制,用于在命令不可用时禁用控件。

RoutedCommandICommand 的 WPF 实现。 当执行 RoutedCommand 时,将在命令目标上引发 PreviewExecutedExecuted 事件,这会像其他输入一样,在元素树中发生隧道操作和浮升操作。 如果未设置命令目标,则具有键盘焦点的元素将成为命令目标。 执行命令的逻辑将附加到 CommandBinding。 当 Executed 事件到达该特定命令的 CommandBinding 时,将调用 CommandBinding 上的 ExecutedRoutedEventHandler。 此处理程序执行该命令的操作。

有关命令的详细信息,请参阅命令概述

WPF 提供由 ApplicationCommandsMediaCommandsComponentCommandsNavigationCommandsEditingCommands 组成的常用命令库,你也可以定义自己的命令。

以下示例显示了如何设置 MenuItem,以便在单击时它将调用 TextBox 上的 Paste 命令,假定 TextBox 具有键盘焦点。

<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 中的命令的详细信息,请参阅命令概述

输入系统和基元素

输入事件(例如由 MouseKeyboardStylus 类定义的附加事件)由输入系统引发,并基于在运行时命中测试可视化树来注入到对象模型中的某个特定位置。

MouseKeyboardStylus 定义为附加事件的每个事件也会由基元素类 UIElementContentElement 重新公开为新路由事件。 基元素路由事件由处理原始附加事件并重用事件数据的类生成。

当输入事件通过其基元素输入事件实现与特定源元素相关联时,可以通过基于逻辑和可视化树对象的组合的事件路由的其余部分进行路由,并由应用程序代码进行处理。 通常,使用 UIElementContentElement 上的路由事件处理这些与设备有关的输入事件更为方便,因为可以使用 XAML 中和代码中更直观的事件处理程序语法。 你可以选择处理发起进程的附加事件,但将会面临几个问题:附加事件可能会被基元素类处理标记为已处理,并且你需要使用访问器方法(而不是真正的事件语法)才能为附加事件附加处理程序。

后续步骤

现在有多种方法来处理 WPF 中的输入。 你还应该对 WPF 使用的各种类型的输入事件和路由事件机制有进一步的了解。

也可以获取更详细说明 WPF 框架元素和事件路由的详细资源。 有关详细信息,请参阅以下概述:命令概述焦点概述基元素概述WPF 中的树路由事件概述

另请参阅