焦点概述
在 WPF 中,有两个主要概念与焦点相关:键盘焦点和逻辑焦点。 键盘焦点是指接收键盘输入的元素,逻辑焦点是指焦点范围中具有焦点的元素。 本概述中详细介绍了这些概念。 了解这些概念的差异对于创建具有多个区域(可以获取焦点)的复杂应用程序非常重要。
参与焦点管理的主要类是 Keyboard 类、FocusManager 类和基元素类,如 UIElement 和 ContentElement。 有关基元素的详细信息,请参阅 基元素概述。
Keyboard 类主要关注键盘焦点,FocusManager 主要涉及逻辑焦点,但这不是绝对区别。 具有键盘焦点的元素也将具有逻辑焦点,但具有逻辑焦点的元素不一定具有键盘焦点。 使用 Keyboard 类设置具有键盘焦点的元素时,这一点很明显,因为它还会对元素设置逻辑焦点。
键盘焦点
键盘焦点是指当前正在接收键盘输入的元素。 整个桌面上只能有一个具有键盘焦点的元素。 在 WPF 中,具有键盘焦点的元素将 IsKeyboardFocused 设置为 true
。 Keyboard 类的静态属性 FocusedElement 用于获取当前键盘焦点所在的元素。
为了使元素获得键盘焦点,基元素上的 Focusable 和 IsVisible 属性必须设置为 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 属性获取一个值,该值指示元素或其任何一个视觉子元素是否具有键盘焦点。
在应用程序启动时设置初始焦点时,接收焦点的元素必须在由应用程序加载的初始窗口的视觉树中,并且该元素的 Focusable 和 IsVisible 都必须设置为 true
。 设置初始焦点的建议位置位于 Loaded 事件处理程序中。 可以通过调用 Invoke 或 BeginInvoke来使用 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 中,默认为焦点范围的类有 Window、MenuItem、ToolBar和 ContextMenu。
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 属性 TabNavigation、ControlTabNavigation和 DirectionalNavigation来更改导航容器的导航行为。 这些属性的类型为 KeyboardNavigationMode,可能的值为 Continue、Local、Contained、Cycle、Once和 None。 默认值为 Continue,这意味着元素不是导航容器。
以下示例创建包含多个 MenuItem 对象的 Menu。 TabNavigation 附加属性设置为 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 是 MoveFocus 和 PredictFocus。
MoveFocus 将焦点更改为应用程序中的下一个元素。 TraversalRequest 用于指定方向。 传递给 MoveFocus 的 FocusNavigationDirection 指定了可以移动焦点的不同方向,例如 First、Last、Up 和 Down。
以下示例使用 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仅支持 Up、Down、Left和 Right。
焦点事件
与键盘焦点相关的事件是 PreviewGotKeyboardFocus、GotKeyboardFocus 和 PreviewLostKeyboardFocus,LostKeyboardFocus。 这些事件被定义为 Keyboard 类的附加事件,但作为基元素类上的等效路由事件更容易访问。 有关事件的详细信息,请参阅 路由事件概述。
当元素获取键盘焦点时,会触发 GotKeyboardFocus。 当元素失去键盘焦点时,会触发事件 LostKeyboardFocus。 如果处理 PreviewGotKeyboardFocus 事件或 PreviewLostKeyboardFocusEvent 事件,并且 Handled 设置为 true
,则焦点不会更改。
以下示例将 GotKeyboardFocus 和 LostKeyboardFocus 事件处理程序附加到 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 获取键盘焦点时,TextBox 的 Background 属性将更改为 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 失去键盘焦点时,TextBox 的 Background 属性将更改为白色。
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
与逻辑焦点相关的事件是 GotFocus 和 LostFocus。 这些事件在 FocusManager 上定义为附加事件,但 FocusManager 不公开 CLR 事件包装器。 UIElement 和 ContentElement 更方便地公开这些事件。