Información general sobre el foco

En WPF, hay dos conceptos principales relacionados con el foco: el foco de teclado y el foco lógico. El foco de teclado hace referencia al elemento que recibe la entrada del teclado, y el foco lógico hace referencia al elemento que tiene el foco en un ámbito de foco. Estos conceptos se describen con detalle en esta información general. Entender la diferencia entre estos conceptos es importante para crear aplicaciones complejas que tengan varias regiones donde se pueda obtener el foco.

Las principales clases que participan en la gestión del foco son la clase Keyboard, la clase FocusManager y las clases de elementos base, como UIElement y ContentElement. Para obtener más información sobre los elementos base, consulte Información general sobre los elementos base.

La clase Keyboard se ocupa principalmente del foco de teclado y la clase FocusManager, del foco lógico, aunque esta no es una distinción absoluta. Un elemento que tiene el foco de teclado también tendrá el foco lógico, pero un elemento que tiene el foco lógico no tendrá necesariamente el foco de teclado. Esto resulta evidente cuando se utiliza la clase Keyboard para establecer el elemento que tiene el foco de teclado, ya que también establece el foco lógico en el elemento.

Foco de teclado

El foco de teclado hace referencia al elemento que recibe actualmente la entrada del teclado. Puede haber un único elemento en todo el escritorio que tenga el foco de teclado. En WPF, el elemento que tiene el foco de teclado tendrá IsKeyboardFocused establecido en true. La propiedad estática FocusedElement de la clase Keyboard obtiene el elemento que actualmente tiene el foco de teclado.

Para que un elemento obtenga el foco de teclado, las propiedades Focusable y IsVisible de los elementos base deben estar establecidas en true. Algunas clases, como la clase base Panel, tienen Focusable establecido por defecto en false; por lo tanto, debe establecer Focusable en true si quiere que dicho elemento pueda obtener el foco de teclado.

El foco de teclado puede obtenerse a través de la interacción del usuario con la UI, como desplazarse mediante la tecla TAB hasta un elemento o hacer clic con el mouse en determinados elementos. El foco de teclado también se puede obtener mediante programación utilizando el método Focus de la clase Keyboard. El método Focus intenta dar al elemento especificado el foco de teclado. El elemento devuelto es el elemento que tiene el foco de teclado, que puede ser un elemento diferente al solicitado si el objeto que tenía el foco antes o el que tiene el foco ahora bloquean la solicitud.

El siguiente ejemplo utiliza el método Focus para fijar el foco de teclado en un 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

La propiedad IsKeyboardFocused en las clases de elementos base obtiene un valor que indica si el elemento tiene el foco de teclado. La propiedad IsKeyboardFocusWithin en las clases de elementos base obtiene un valor que indica si el elemento o alguno de sus elementos visuales secundarios tiene el foco de teclado.

Al establecer el foco inicial al iniciarse una aplicación, el elemento que va a recibir el foco debe encontrarse en el árbol visual de la ventana inicial cargada por la aplicación, y el elemento debe tener Focusable y IsVisible establecidas en true. El lugar recomendado para establecer el foco inicial es en el controlador de eventos Loaded. Una devolución de llamada Dispatcher también puede utilizarse llamando a Invoke o BeginInvoke.

Foco lógico

El foco lógico se refiere al FocusManager.FocusedElement en un ámbito de foco. Un ámbito de foco es un elemento que lleva la cuenta de FocusedElement dentro de su ámbito. Cuando el foco de teclado sale de un ámbito de foco, el elemento enfocado pierde el foco de teclado, pero conserva el foco lógico. Cuando el foco de teclado vuelve al ámbito de foco, el elemento enfocado recibe el foco de teclado. Esto permite cambiar el foco de teclado entre varios ámbitos de foco, pero garantiza que el elemento enfocado dentro del ámbito de foco vuelva a obtener el foco de teclado cuando el foco vuelva al ámbito de foco.

Puede haber varios elementos que tengan el foco lógico en una aplicación, pero solo puede haber uno con el foco lógico en un ámbito de foco concreto.

Un elemento que tiene el foco de teclado tiene el foco lógico para el ámbito de foco al que pertenece.

Un elemento puede convertirse en un ámbito de foco en el Lenguaje de marcado de aplicaciones extensibles (XAML) estableciendo la propiedad FocusManager adjunta IsFocusScope en true. En el código, un elemento puede convertirse en un ámbito de enfoque llamando a SetIsFocusScope.

El siguiente ejemplo convierte un StackPanel en un ámbito de enfoque estableciendo la propiedad adjunta IsFocusScope.

<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 devuelve el ámbito de foco del elemento especificado.

Las clases de WPF que son ámbitos de foco por defecto son Window, MenuItem, ToolBar y ContextMenu.

GetFocusedElement obtiene el elemento enfocado para el ámbito de foco especificado. SetFocusedElement establece el elemento enfocado en el ámbito de foco especificado. SetFocusedElement se utiliza normalmente para establecer el elemento enfocado inicial.

En el ejemplo siguiente se establece el elemento con foco en un ámbito de foco y se obtiene el elemento con foco de un ámbito de foco.

// 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)

Navegación mediante teclado

La clase KeyboardNavigation es responsable de implementar la navegación del foco de teclado predeterminada cuando se presiona una de las teclas de navegación. Las teclas de navegación son: TAB, MAYÚS+TAB, CTRL+TAB, CTRL+MAYÚS+TAB, flecha arriba, flecha abajo, flecha izquierda y flecha derecha.

El comportamiento de navegación de un contenedor de navegación se puede cambiar estableciendo las propiedades KeyboardNavigation adjuntas TabNavigation, ControlTabNavigation y DirectionalNavigation. Estas propiedades son de tipo KeyboardNavigationMode y los valores posibles son Continue, Local, Contained, Cycle, Once y None. El valor por defecto es Continue, lo que significa que el elemento no es un contenedor de navegación.

El siguiente ejemplo crea un Menu con un número de objetos MenuItem. La propiedad adjunta TabNavigation se establece en Cycle en Menu. Cuando se cambie el foco mediante la tecla TAB dentro de Menu, el foco se desplazará por cada elemento y, cuando se llegue al último elemento, el foco volverá al primer elemento.

<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)

La API adicional para trabajar con el foco es MoveFocus y PredictFocus.

MoveFocus cambia el foco al siguiente elemento de la aplicación. Se usa TraversalRequest para especificar la dirección. FocusNavigationDirection pasado a MoveFocus especifica las diferentes direcciones en las que se puede mover el foco, como First, Last, Up y Down.

El siguiente ejemplo utiliza MoveFocus para cambiar el elemento enfocado.

// 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 devuelve el objeto que recibiría el foco si se cambiara el foco. Actualmente, solo Up, Down, Left y Right son compatibles con PredictFocus.

Eventos de foco

Los eventos relacionados con el foco de teclado son PreviewGotKeyboardFocus, GotKeyboardFocus y PreviewLostKeyboardFocus, LostKeyboardFocus. Los eventos se definen como eventos adjuntos en la clase Keyboard, pero se puede acceder a ellos más fácilmente como eventos enrutados equivalentes de las clases de elementos base. Para obtener más información sobre los eventos, consulte Información general sobre eventos enrutados.

GotKeyboardFocus se inicia cuando el elemento obtiene el foco de teclado. LostKeyboardFocus se inicia cuando el elemento pierde el foco de teclado. Si se controla el evento PreviewGotKeyboardFocus o el evento PreviewLostKeyboardFocusEvent y Handled se establece en true, entonces el foco no cambiará.

El siguiente ejemplo adjunta los controladores de eventos GotKeyboardFocus y LostKeyboardFocus a un 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>

Cuando TextBox obtiene el foco de teclado, la propiedad Background de TextBox se cambia a 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

Cuando TextBox pierde el foco de teclado, la propiedad Background de TextBox vuelve a ser blanca.

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

Los eventos relacionados con el foco lógico son GotFocus y LostFocus. Estos eventos se definen en FocusManager como eventos adjuntos, pero FocusManager no expone contenedores de eventos CLR. UIElement y ContentElement exponen estos eventos de manera más conveniente.

Vea también