输入概述

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

本主题包括下列各节。

  • 输入 API
  • 事件路由
  • 处理输入事件
  • 文本输入
  • 触控和操作
  • 焦点
  • 鼠标位置
  • 鼠标捕获
  • 命令
  • 输入系统和基元素
  • 接下来的内容
  • 相关主题

输入 API

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

键盘和鼠标类

除了基元素类上的输入 API,Keyboard 类和 Mouse 类还提供了用于处理键盘和鼠标输入的其他 API。

Keyboard 类上的输入 API 的示例有 Modifiers 属性(用于返回当前按下的 ModifierKeys)和 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) And KeyStates.Down) > 0 Then
                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) & KeyStates.Down) > 0)
{
    btnNone.Background = Brushes.Red;
}

Mouse 类上的输入 API 的示例有 MiddleButton(用于获取鼠标中键的状态)和 DirectlyOver(用于获取鼠标指针当前位于其上的元素)。

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

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

本概述中将更详细地介绍 MouseKeyboard 类。

触笔输入

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

由于触笔可充当鼠标,因此仅支持鼠标输入的应用程序仍可自动获取一定程度的触笔支持。 通过这样的方式使用触笔时,将使应用程序有机会处理相应的触笔事件,然后处理对应的鼠标事件。 此外,通过触笔设备抽象也可以使用较高级别的服务,例如墨迹输入。 有关墨迹作为输入的更多信息,请参见墨迹入门

事件路由

FrameworkElement 可以在其内容模型中包含其他元素作为子元素,从而形成一个元素树。 在 WPF 中,父元素可通过处理事件来参与定向到其子元素或其他子代的输入。 这对于生成进行较少控制的控件(一个称为“控件组合”或“合成”的过程)尤其有用。有关元素树和元素树与事件路由相关的方式的更多信息,请参见 WPF 中的树

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

WPF 输入事件通常成对出现,由一个隧道事件和一个冒泡事件组成。 隧道事件与冒泡事件在“Preview”前缀上存在区别。 例如,PreviewMouseMove 是隧道版本的鼠标移动事件,MouseMove 是冒泡版本的鼠标移动事件。 此事件配对是在元素级别实现的一种约定,不是 WPF 事件系统的固有功能。 有关详细信息,请参见路由事件概述中的“WPF 输入事件”一节。

处理输入事件

若要在元素上接收输入,必须将事件处理程序与该特定事件关联。 在 XAML 中,此操作非常简单:将事件的名称作为将侦听此事件的元素的特性来引用。 然后,基于委托,将特性的值设置为所定义的事件处理程序的名称。 事件处理程序必须使用代码(如 C#)编写,并可包括在代码隐藏文件中。

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

键盘输入事件示例

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

示例的第一部分创建 StackPanelButton,并附加 KeyDown 的事件处理程序。

<StackPanel>
  <Button Background="AliceBlue"
          KeyDown="OnButtonKeyDown"
          Content="Button1"/>
</StackPanel>
            ' 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
// 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);

第二部分使用代码编写并定义事件处理程序。 当按下向左键并且 Button 具有键盘焦点时,处理程序将运行并且 ButtonBackground 颜色将发生改变。 如果按下了键,但该键不是向左键,则 ButtonBackground 颜色将变回其开始颜色。

        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
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;
        }
    }
}

鼠标输入事件示例

在下面的示例中,当鼠标指针进入 Button 时,ButtonBackground 颜色将发生改变。 当鼠标离开 Button 时,Background 颜色将还原。

示例的第一部分创建 StackPanelButton 控件,并将 MouseEnterMouseLeave 事件的事件处理程序附加到 Button

<StackPanel>
  <Button Background="AliceBlue"
          MouseEnter="OnMouseExampleMouseEnter"
          MouseLeave="OnMosueExampleMouseLeave">Button

  </Button>
</StackPanel>
            ' 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
// 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);

该示例的第二部分是使用代码编写的,用来定义事件处理程序。 当鼠标进入 Button 时,ButtonBackground 颜色将改变为 SlateGray。 当鼠标离开 Button 时,ButtonBackground 颜色将变回为 AliceBlue

        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 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 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
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;
    }
}

文本输入

通过 TextInput 事件,您可以借助与设备无关的方式侦听文本输入。 键盘是文本输入的主要方式,但也可以通过语音、手写和其他输入设备输入文本。

对于键盘输入,WPF 首先发送相应的 KeyDown/KeyUp 事件。 如果未处理这些事件并且按键为文本键(而不是控制键,例如方向键或功能键),则将引发 TextInput 事件。 KeyDown/KeyUpTextInput 事件之间不会始终存在简单的一对一映射,因为多次击键可以产生一个字符的文本输入,一次击键也可以产生多字符的字符串。 对于中文、日语和朝鲜语这样的语言尤其如此,这些语言使用Input Method Editors (IMEs) 生成由其对应的字母组成的成千上万个可能的字符。

当 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.
            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
// 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);

第二段代码包含事件处理程序。

        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
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");
}

由于输入事件在事件路由中向上冒泡,因此不管哪个元素具有键盘焦点,StackPanel 都将接收输入。 TextBox 控件首先得到通知,而只有在 TextBox 未处理输入时才会调用 OnTextInputKeyDown 处理程序。 如果使用 PreviewKeyDown 事件而不是 KeyDown 事件,则将首先调用 OnTextInputKeyDown 处理程序。

在此示例中,处理逻辑写入了两次,一次针对 Ctrl+O,另一次针对按钮的单击事件。 使用命令,而不是直接处理输入事件,可简化此过程。 本概述和命令概述中将讨论这些命令。

触控和操作

Windows 7 操作系统中新的硬件和 API 使应用程序能够同时接收来自多个触控的输入。 利用 WPF,应用程序能够在发生触控时引发事件,从而检测到触控并采用与响应其他输入(例如鼠标或键盘)类似的方式来响应触控。

WPF 在发生触控时公开两种类型的事件:触控事件和操作事件。 触控事件提供有关触摸屏上的每个手指及其移动的原始数据。 操作事件将输入解释为特定操作。 本节讨论这两种类型的事件。

系统必备组件

需要以下组件才能开发响应触控的应用程序。

  • Microsoft 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 方法,获取输入相对于任何元素的位置。

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

触控事件

触控事件的序列。

下面的列表描述上图中的事件序列。

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

  2. TouchDown 事件发生一次。

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

  4. 当用户将手指从元素上移开时,TouchUp 事件发生一次。

  5. TouchLeave 事件发生一次。

如果使用两个以上的手指,则这些事件将针对每个手指发生。

操作事件

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

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

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

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

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

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

有关如何创建使用户能够移动对象、调整对象大小和旋转对象的应用程序的信息,请参见演练:创建您的第一个触控应用程序

UIElement 定义以下操作事件。

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

操作事件的执行路径

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

下图显示了操作事件的执行路径,以及有关每个事件的重要信息。

操作事件

操作事件的序列。

下面的列表描述上图中的事件序列。

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

  2. 接下来发生 ManipulationStarted 事件。 此事件报告操作的原点。

  3. 当用户的手指在触摸屏上移动时,ManipulationDelta 事件发生多次。 ManipulationDeltaEventArgs 类的 DeltaManipulation 属性报告是将操作解释为移动、扩展还是转换。 您将在其中执行大部分操作对象的工作。

  4. 当用户的手指从对象上移开时,ManipulationInertiaStarting 事件发生。 此事件使您能够指定操作在惯性期间的减速。 这样,对象将能够模拟不同的物理空间和特性(如果您选择)。 例如,假设您的应用程序有两个表示真实世界中的物品的对象,并且一个物品比另一个物品重。 您可以使较重对象的减速比较轻对象快。

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

  6. 当操作和任何惯性结束时,ManipulationCompleted 事件发生。 也就是说,在所有 ManipulationDelta 事件发生后,ManipulationCompleted 事件将发生,以发出操作完成的信号。

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

在除 ManipulationBoundaryFeedback 事件外的任何操作事件中,您可以通过对事件参数调用 Cancel 方法来取消操作。 如果调用 Cancel,则不再引发操作事件,并且对于触控将发生鼠标事件。 下表描述了取消操作的时间和所发生鼠标事件之间的关系。

在其中调用 Cancel 的事件

将针对已发生的输入发生的鼠标事件

ManipulationStartingManipulationStarted

鼠标按下事件。

ManipulationDelta

鼠标按下和鼠标移动事件。

ManipulationInertiaStartingManipulationCompleted

鼠标按下、鼠标移动和鼠标弹起事件。

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

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

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

触控事件和操作事件

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

下表描述了上图中所示的触控事件和操作事件之间的关系。

焦点

在 WPF 中,有两个与焦点有关的主要概念:键盘焦点和逻辑焦点。

键盘焦点

键盘焦点指正在接收键盘输入的元素。 在整个桌面上,只能有一个具有键盘焦点的元素。 在 WPF 中,具有键盘焦点的元素会将 IsKeyboardFocused 设置为 true。 静态 Keyboard 方法 FocusedElement 返回当前具有键盘焦点的元素。

可以通过按 Tab 键定位到某个元素或者在某些元素(如 TextBox)上单击鼠标来获取键盘焦点。 还可以通过使用 Keyboard 类的 Focus 方法,以编程方式获取键盘焦点。 Focus 尝试将键盘焦点给予指定的元素。 Focus 返回的元素是当前具有键盘焦点的元素。

为了使元素能够获取键盘焦点,Focusable 属性和 IsVisible 属性必须设置为 true。 某些类(如 Panel)默认情况下将 Focusable 设置为 false;因此,如果您希望该元素能够获取焦点,则必须将此属性设置为 true。

下面的示例使用 FocusButton 上设置键盘焦点。 建议在应用程序中的 Loaded 事件处理程序中设置初始焦点。

        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
private void OnLoaded(object sender, RoutedEventArgs e)
{
    // Sets keyboard focus on the first Button in the sample.
    Keyboard.Focus(firstButton);
}

有关键盘焦点的更多信息,请参见焦点概述

逻辑焦点

逻辑焦点指焦点范围中的 FocusManager.FocusedElement。 一个应用程序中可以有多个具有逻辑焦点的元素,但在一个特定的焦点范围中只能有一个具有逻辑焦点的元素。

焦点范围是一个容器元素,该元素跟踪其焦点范围内的 FocusedElement。 当焦点离开焦点范围时,焦点元素会失去键盘焦点,但保留逻辑焦点。 当焦点返回到焦点范围时,焦点元素会再次获得键盘焦点。 这使得键盘焦点可以在多个焦点范围之间切换,但将确保在焦点返回时,焦点范围中的焦点元素可再次获得焦点。

在Extensible Application Markup Language (XAML) 中,通过将 FocusManager 的附加属性 IsFocusScope 设置为 true,或者在代码中使用 SetIsFocusScope 方法设置附加属性,可以将元素转变为焦点范围。

下面的示例通过设置 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>
            Dim focuseScope2 As New StackPanel()
            FocusManager.SetIsFocusScope(focuseScope2, True)
StackPanel focuseScope2 = new StackPanel();
FocusManager.SetIsFocusScope(focuseScope2, true);

WPF 中默认情况下即为焦点范围的类有 WindowMenuToolBarContextMenu

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

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

有关逻辑焦点更多信息,请参见焦点概述

鼠标位置

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

鼠标捕获

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

命令

使用命令,输入处理可以更多地在语义级别(而不是在设备输入级别)进行。 命令是简单的指令,如 Cut、Copy、Paste 或 Open。 命令对于集中命令逻辑很有用。 同一命令可通过 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
            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
// 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;

有关 WPF 中的命令的更多信息,请参见命令概述

输入系统和基元素

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

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

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

接下来的内容

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

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

请参见

概念

焦点概述

命令概述

路由事件概述

基元素概述

其他资源

属性