焦点概述

在 WPF 中,有两个主要概念与焦点相关:键盘焦点和逻辑焦点。 键盘焦点是指接收键盘输入的元素,逻辑焦点是指焦点范围中具有焦点的元素。 本概述中详细介绍了这些概念。 了解这些概念的差异对于创建具有多个区域(可以获取焦点)的复杂应用程序非常重要。

参与焦点管理的主要类是 Keyboard 类、FocusManager 类和基元素类,如 UIElementContentElement。 有关基元素的详细信息,请参阅 基元素概述

Keyboard 类主要关注键盘焦点,FocusManager 主要涉及逻辑焦点,但这不是绝对区别。 具有键盘焦点的元素也将具有逻辑焦点,但具有逻辑焦点的元素不一定具有键盘焦点。 使用 Keyboard 类设置具有键盘焦点的元素时,这一点很明显,因为它还会对元素设置逻辑焦点。

键盘焦点

键盘焦点是指当前正在接收键盘输入的元素。 整个桌面上只能有一个具有键盘焦点的元素。 在 WPF 中,具有键盘焦点的元素将 IsKeyboardFocused 设置为 trueKeyboard 类的静态属性 FocusedElement 用于获取当前键盘焦点所在的元素。

为了使元素获得键盘焦点,基元素上的 FocusableIsVisible 属性必须设置为 true。 某些类(如 Panel 基类)默认 Focusable 设置为 false;因此,如果希望此类元素能够获取键盘焦点,则必须将 Focusable 设置为 true

可以通过用户与 UI 交互来获取键盘焦点,例如使用 Tab 键导航到某个元素或点击某些元素。 也可以使用 Keyboard 类上的 Focus 方法以编程方式获取键盘焦点。 Focus 方法尝试提供指定的元素键盘焦点。 返回的元素是具有键盘焦点的元素,如果旧焦点对象或新焦点对象阻止请求,该元素可能与所请求的元素不同。

以下示例使用 Focus 方法将键盘焦点设置为 Button

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

基元素类上的 IsKeyboardFocused 属性获取一个值,该值指示元素是否具有键盘焦点。 基元素类上的 IsKeyboardFocusWithin 属性获取一个值,该值指示元素或其任何一个视觉子元素是否具有键盘焦点。

在应用程序启动时设置初始焦点时,接收焦点的元素必须在由应用程序加载的初始窗口的视觉树中,并且该元素的 FocusableIsVisible 都必须设置为 true。 设置初始焦点的建议位置位于 Loaded 事件处理程序中。 可以通过调用 InvokeBeginInvoke来使用 Dispatcher 回调功能。

逻辑焦点

逻辑焦点是指焦点范围内的 FocusManager.FocusedElement。 焦点范围是用于在其范围内跟踪 FocusedElement 的一个组件。 当键盘焦点离开焦点范围时,焦点元素将失去键盘焦点,但会保留逻辑焦点。 当键盘焦点返回到焦点范围时,焦点元素将获取键盘焦点。 这允许在多个聚焦范围之间切换键盘聚焦,但确保当焦点返回到该范围时,聚焦范围中的元素重新获得键盘聚焦。

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

在一个元素具有键盘焦点时,它在其所属的焦点范围内也具有逻辑焦点。

通过将 FocusManager 附加 IsFocusScope 属性设置为 true,可以将元素转换为可扩展应用程序标记语言(XAML)中的焦点范围。 在代码中,可以通过调用 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>
StackPanel focuseScope2 = new StackPanel();
FocusManager.SetIsFocusScope(focuseScope2, true);
Dim focuseScope2 As New StackPanel()
FocusManager.SetIsFocusScope(focuseScope2, True)

GetFocusScope 返回指定元素的焦点范围。

在 WPF 中,默认为焦点范围的类有 WindowMenuItemToolBarContextMenu

GetFocusedElement 获取指定焦点范围的焦点元素。 SetFocusedElement 设置指定焦点范围内的焦点元素。 SetFocusedElement 通常用于设置初始聚焦元素。

以下示例将设置焦点范围中的元素为焦点,并获取焦点范围内的焦点元素。

// Sets the focused element in focusScope1
// focusScope1 is a StackPanel.
FocusManager.SetFocusedElement(focusScope1, button2);

// Gets the focused element for focusScope 1
IInputElement focusedElement = FocusManager.GetFocusedElement(focusScope1);
' Sets the focused element in focusScope1
' focusScope1 is a StackPanel.
FocusManager.SetFocusedElement(focusScope1, button2)

' Gets the focused element for focusScope 1
Dim focusedElement As IInputElement = FocusManager.GetFocusedElement(focusScope1)

键盘导航

KeyboardNavigation 类负责在按下其中一个导航键时实现默认键盘焦点导航。 导航键包括:TAB、Shift+TAB、Ctrl+Tab、Ctrl+Shift+Tab、UPARROW、DOWNARROW、LEFTARROW 和 RIGHTARROW 键。

可以通过设置附加的 KeyboardNavigation 属性 TabNavigationControlTabNavigationDirectionalNavigation来更改导航容器的导航行为。 这些属性的类型为 KeyboardNavigationMode,可能的值为 ContinueLocalContainedCycleOnceNone。 默认值为 Continue,这意味着元素不是导航容器。

以下示例创建包含多个 MenuItem 对象的 MenuTabNavigation 附加属性设置为 Menu上的 Cycle。 使用 Menu中的 Tab 键更改焦点时,焦点将从每个元素移动,当到达最后一个元素时,焦点将返回到第一个元素。

<Menu KeyboardNavigation.TabNavigation="Cycle">
  <MenuItem Header="Menu Item 1" />
  <MenuItem Header="Menu Item 2" />
  <MenuItem Header="Menu Item 3" />
  <MenuItem Header="Menu Item 4" />
</Menu>
Menu navigationMenu = new Menu();
MenuItem item1 = new MenuItem();
MenuItem item2 = new MenuItem();
MenuItem item3 = new MenuItem();
MenuItem item4 = new MenuItem();

navigationMenu.Items.Add(item1);
navigationMenu.Items.Add(item2);
navigationMenu.Items.Add(item3);
navigationMenu.Items.Add(item4);

KeyboardNavigation.SetTabNavigation(navigationMenu,
    KeyboardNavigationMode.Cycle);
Dim navigationMenu As New Menu()
Dim item1 As New MenuItem()
Dim item2 As New MenuItem()
Dim item3 As New MenuItem()
Dim item4 As New MenuItem()

navigationMenu.Items.Add(item1)
navigationMenu.Items.Add(item2)
navigationMenu.Items.Add(item3)
navigationMenu.Items.Add(item4)

KeyboardNavigation.SetTabNavigation(navigationMenu, KeyboardNavigationMode.Cycle)

处理焦点的附加 API 是 MoveFocusPredictFocus

MoveFocus 将焦点更改为应用程序中的下一个元素。 TraversalRequest 用于指定方向。 传递给 MoveFocusFocusNavigationDirection 指定了可以移动焦点的不同方向,例如 FirstLastUpDown

以下示例使用 MoveFocus 更改重点元素。

// Creating a FocusNavigationDirection object and setting it to a
// local field that contains the direction selected.
FocusNavigationDirection focusDirection = _focusMoveValue;

// MoveFocus takes a TraveralReqest as its argument.
TraversalRequest request = new TraversalRequest(focusDirection);

// Gets the element with keyboard focus.
UIElement elementWithFocus = Keyboard.FocusedElement as UIElement;

// Change keyboard focus.
if (elementWithFocus != null)
{
    elementWithFocus.MoveFocus(request);
}
' Creating a FocusNavigationDirection object and setting it to a
' local field that contains the direction selected.
Dim focusDirection As FocusNavigationDirection = _focusMoveValue

' MoveFocus takes a TraveralReqest as its argument.
Dim request As New TraversalRequest(focusDirection)

' Gets the element with keyboard focus.
Dim elementWithFocus As UIElement = TryCast(Keyboard.FocusedElement, UIElement)

' Change keyboard focus.
If elementWithFocus IsNot Nothing Then
    elementWithFocus.MoveFocus(request)
End If

PredictFocus 返回将在焦点更改时接收焦点的对象。 目前,PredictFocus仅支持 UpDownLeftRight

焦点事件

与键盘焦点相关的事件是 PreviewGotKeyboardFocusGotKeyboardFocusPreviewLostKeyboardFocusLostKeyboardFocus。 这些事件被定义为 Keyboard 类的附加事件,但作为基元素类上的等效路由事件更容易访问。 有关事件的详细信息,请参阅 路由事件概述

当元素获取键盘焦点时,会触发 GotKeyboardFocus。 当元素失去键盘焦点时,会触发事件 LostKeyboardFocus。 如果处理 PreviewGotKeyboardFocus 事件或 PreviewLostKeyboardFocusEvent 事件,并且 Handled 设置为 true,则焦点不会更改。

以下示例将 GotKeyboardFocusLostKeyboardFocus 事件处理程序附加到 TextBox

<Border BorderBrush="Black" BorderThickness="1"
        Width="200" Height="100" Margin="5">
  <StackPanel>
    <Label HorizontalAlignment="Center" Content="Type Text In This TextBox" />
    <TextBox Width="175"
             Height="50" 
             Margin="5"
             TextWrapping="Wrap"
             HorizontalAlignment="Center"
             VerticalScrollBarVisibility="Auto"
             GotKeyboardFocus="TextBoxGotKeyboardFocus"
             LostKeyboardFocus="TextBoxLostKeyboardFocus"
             KeyDown="SourceTextKeyDown"/>
  </StackPanel>
</Border>

TextBox 获取键盘焦点时,TextBoxBackground 属性将更改为 LightBlue

private void TextBoxGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
    TextBox source = e.Source as TextBox;

    if (source != null)
    {
        // Change the TextBox color when it obtains focus.
        source.Background = Brushes.LightBlue;

        // Clear the TextBox.
        source.Clear();
    }
}
Private Sub TextBoxGotKeyboardFocus(ByVal sender As Object, ByVal e As KeyboardFocusChangedEventArgs)
    Dim source As TextBox = TryCast(e.Source, TextBox)

    If source IsNot Nothing Then
        ' Change the TextBox color when it obtains focus.
        source.Background = Brushes.LightBlue

        ' Clear the TextBox.
        source.Clear()
    End If
End Sub

TextBox 失去键盘焦点时,TextBoxBackground 属性将更改为白色。

private void TextBoxLostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
    TextBox source = e.Source as TextBox;

    if (source != null)
    {
        // Change the TextBox color when it loses focus.
        source.Background = Brushes.White;

        // Set the  hit counter back to zero and updates the display.
        this.ResetCounter();
    }
}
Private Sub TextBoxLostKeyboardFocus(ByVal sender As Object, ByVal e As KeyboardFocusChangedEventArgs)
    Dim source As TextBox = TryCast(e.Source, TextBox)

    If source IsNot Nothing Then
        ' Change the TextBox color when it loses focus.
        source.Background = Brushes.White

        ' Set the  hit counter back to zero and updates the display.
        Me.ResetCounter()
    End If
End Sub

与逻辑焦点相关的事件是 GotFocusLostFocus。 这些事件在 FocusManager 上定义为附加事件,但 FocusManager 不公开 CLR 事件包装器。 UIElementContentElement 更方便地公开这些事件。

另请参阅