焦点概述

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

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

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

键盘焦点

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

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

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

下面的示例使用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。 设置初始焦点的建议位置位于事件处理程序中 LoadedDispatcher回调还可以通过调用InvokeBeginInvoke来使用。

逻辑焦点

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

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

具有键盘焦点的元素具有其所属焦点范围的逻辑焦点。

通过将附加属性IsFocusScope设置为 FocusManagertrue,可以将元素转换为可扩展应用程序标记语言(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 键。

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

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

<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 将焦点更改为应用程序中的下一个元素。 A TraversalRequest 用于指定方向。 FocusNavigationDirection传递给MoveFocus来指定焦点可以移动的不同方向,例如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 返回将焦点更改时接收焦点的对象。 目前,只有UpDownLeftRight受支持PredictFocus

焦点事件

与键盘焦点相关的事件是PreviewGotKeyboardFocusGotKeyboardFocus以及PreviewLostKeyboardFocusLostKeyboardFocus 这些事件在类中定义为 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获取键盘焦点时,Background属性TextBox将更改为 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 更方便地暴露这些事件。

另请参阅