Navigation en mode focus programmé

Clavier, remote et D-pad

Pour déplacer le focus par programmation dans votre application Windows, vous pouvez utiliser la méthode FocusManager.TryMoveFocus ou la méthode FindNextElement .

TryMoveFocus tente de remplacer le focus de l’élément avec focus sur l’élément focusable suivant dans le sens spécifié, tandis que FindNextElement récupère l’élément (en tant que DependencyObject) qui recevra le focus en fonction du sens de navigation spécifié (la navigation directionnelle uniquement, ne peut pas être utilisée pour émuler la navigation tab).

Notes

Nous vous recommandons d’utiliser la méthode FindNextElement au lieu de FindNextFocusableElement , car FindNextFocusableElement récupère un UIElement, qui retourne null si l’élément focusable suivant n’est pas un UIElement (tel qu’un objet Hyperlink).

Rechercher un candidat focus dans une étendue

Vous pouvez personnaliser le comportement de navigation du focus pour TryMoveFocus et FindNextElement, y compris la recherche du candidat focus suivant dans une arborescence d’interface utilisateur spécifique ou l’exclusion d’éléments spécifiques de la prise en compte.

Cet exemple utilise un jeu TicTacToe pour illustrer les méthodes TryMoveFocus et FindNextElement .

<StackPanel Orientation="Horizontal"
                VerticalAlignment="Center"
                HorizontalAlignment="Center" >
    <Button Content="Start Game" />
    <Button Content="Undo Movement" />
    <Grid x:Name="TicTacToeGrid" KeyDown="OnKeyDown">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="50" />
            <ColumnDefinition Width="50" />
            <ColumnDefinition Width="50" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="50" />
            <RowDefinition Height="50" />
            <RowDefinition Height="50" />
        </Grid.RowDefinitions>
        <myControls:TicTacToeCell 
            Grid.Column="0" Grid.Row="0" 
            x:Name="Cell00" />
        <myControls:TicTacToeCell 
            Grid.Column="1" Grid.Row="0" 
            x:Name="Cell10"/>
        <myControls:TicTacToeCell 
            Grid.Column="2" Grid.Row="0" 
            x:Name="Cell20"/>
        <myControls:TicTacToeCell 
            Grid.Column="0" Grid.Row="1" 
            x:Name="Cell01"/>
        <myControls:TicTacToeCell 
            Grid.Column="1" Grid.Row="1" 
            x:Name="Cell11"/>
        <myControls:TicTacToeCell 
            Grid.Column="2" Grid.Row="1" 
            x:Name="Cell21"/>
        <myControls:TicTacToeCell 
            Grid.Column="0" Grid.Row="2" 
            x:Name="Cell02"/>
        <myControls:TicTacToeCell 
            Grid.Column="1" Grid.Row="2" 
            x:Name="Cell22"/>
        <myControls:TicTacToeCell 
            Grid.Column="2" Grid.Row="2" 
            x:Name="Cell32"/>
    </Grid>
</StackPanel>
private void OnKeyDown(object sender, KeyRoutedEventArgs e)
{
    DependencyObject candidate = null;

    var options = new FindNextElementOptions ()
    {
        SearchRoot = TicTacToeGrid,
        XYFocusNavigationStrategyOverride = XYFocusNavigationStrategyOverride.Projection
    };

    switch (e.Key)
    {
        case Windows.System.VirtualKey.Up:
            candidate = 
                FocusManager.FindNextElement(
                    FocusNavigationDirection.Up, options);
            break;
        case Windows.System.VirtualKey.Down:
            candidate = 
                FocusManager.FindNextElement(
                    FocusNavigationDirection.Down, options);
            break;
        case Windows.System.VirtualKey.Left:
            candidate = FocusManager.FindNextElement(
                FocusNavigationDirection.Left, options);
            break;
        case Windows.System.VirtualKey.Right:
            candidate = 
                FocusManager.FindNextElement(
                    FocusNavigationDirection.Right, options);
            break;
    }
    // Also consider whether candidate is a Hyperlink, WebView, or TextBlock.
    if (candidate != null && candidate is Control)
    {
        (candidate as Control).Focus(FocusState.Keyboard);
    }
}

Utilisez FindNextElementOptions pour personnaliser davantage la façon dont les candidats sont identifiés. Cet objet fournit les propriétés suivantes :

  • SearchRoot : étendue de la recherche de candidats de navigation focus aux enfants de cet Objet De dépendance. Null indique de démarrer la recherche à partir de la racine de l’arborescence visuelle.

Important

Si une ou plusieurs transformations sont appliquées aux descendants de SearchRoot qui les placent en dehors de la zone directionnelle, ces éléments sont toujours considérés comme candidats.

  • ExclusionRect : les candidats de navigation focus sont identifiés à l’aide d’un rectangle englobant « fictif » où tous les objets qui se chevauchent sont exclus du focus de navigation. Ce rectangle est utilisé uniquement pour les calculs et n’est jamais ajouté à l’arborescence visuelle.
  • HintRect : les candidats de navigation focus sont identifiés à l’aide d’un rectangle englobant « fictif » qui identifie les éléments les plus susceptibles de recevoir le focus. Ce rectangle est utilisé uniquement pour les calculs et n’est jamais ajouté à l’arborescence visuelle.
  • XYFocusNavigationStrategyOverride : stratégie de navigation focus utilisée pour identifier le meilleur élément candidat à recevoir le focus.

L’image suivante illustre certains de ces concepts.

Lorsque l’élément B a le focus, FindNextElement identifie I comme candidat au focus lors de la navigation vers la droite. Les raisons sont les suivantes :

  • En raison du HintRect sur A, la référence de départ est A, et non B
  • C n’est pas un candidat, car MyPanel a été spécifié en tant que SearchRoot
  • F n’est pas un candidat, car ExclusionRect le chevauche

Comportement de navigation du focus personnalisé à l’aide d’indicateurs de navigation

Comportement de navigation du focus personnalisé à l’aide d’indicateurs de navigation

Événement NoFocusCandidateFound

L’événement UIElement.NoFocusCandidateFound est déclenché lorsque les touches de tabulation ou de direction sont enfoncées et qu’il n’existe aucun candidat de focus dans le sens spécifié. Cet événement n’est pas déclenché pour TryMoveFocus.

Étant donné qu’il s’agit d’un événement routé, il effectue des bulles de l’élément ciblé vers la racine de l’arborescence d’objets en passant par les objets parents successifs. Cela vous permet de gérer l’événement partout où cela est approprié.

Ici, nous montrons comment une grille ouvre un SplitView lorsque l’utilisateur tente de déplacer le focus à gauche du contrôle le plus à gauche pouvant être mis au point (voir Conception pour Xbox et TV).

<Grid NoFocusCandidateFound="OnNoFocusCandidateFound">
...
</Grid>
private void OnNoFocusCandidateFound (
    UIElement sender, NoFocusCandidateFoundEventArgs args)
{
    if(args.NavigationDirection == FocusNavigationDirection.Left)
    {
        if(args.InputDevice == FocusInputDeviceKind.Keyboard ||
        args.InputDevice == FocusInputDeviceKind.GameController )
            {
                OpenSplitPaneView();
            }
        args.Handled = true;
    }
}

Événements GotFocus et LostFocus

Les événements UIElement.GotFocus et UIElement.LostFocus sont déclenchés lorsqu’un élément obtient le focus ou perd le focus, respectivement. Cet événement n’est pas déclenché pour TryMoveFocus.

Étant donné qu’il s’agit d’événements routés, ils bullent de l’élément ciblé vers la racine de l’arborescence d’objets en passant par les objets parents successifs. Cela vous permet de gérer l’événement partout où cela est approprié.

Événements GettingFocus et LosingFocus

Les événements UIElement.GettingFocus et UIElement.LosingFocus se déclenchent avant les événements UIElement.GotFocus et UIElement.LostFocus respectifs.

Étant donné qu’il s’agit d’événements routés, ils bullent de l’élément ciblé vers la racine de l’arborescence d’objets en passant par les objets parents successifs. Comme cela se produit avant qu’un changement de focus n’a lieu, vous pouvez rediriger ou annuler le changement de focus.

GettingFocus et LosingFocus étant des événements synchrones, le focus ne sera pas déplacé tant que ces événements sont bouillonnants. Toutefois, GotFocus et LostFocus sont des événements asynchrones, ce qui signifie qu’il n’est pas garanti que le focus ne se déplace pas à nouveau avant l’exécution du gestionnaire.

Si le focus se déplace via un appel à Control.Focus, GettingFocus est déclenché pendant l’appel, tandis que GotFocus est déclenché après l’appel.

La cible de navigation focus peut être modifiée pendant les événements GettingFocus et LosingFocus (avant le déplacement du focus) via la propriété GettingFocusEventArgs.NewFocusedElement . Même si la cible est modifiée, l’événement continue de bulles et la cible peut être modifiée à nouveau.

Pour éviter les problèmes de réentrance, une exception est levée si vous essayez de déplacer le focus (à l’aide de TryMoveFocus ou de Control.Focus) pendant que ces événements sont en cours de bouillonnement.

Ces événements sont déclenchés quelle que soit la raison du déplacement du focus (y compris la navigation par onglet, la navigation directionnelle et la navigation par programme).

Voici l’ordre d’exécution des événements focus :

  1. LosingFocus Si le focus est réinitialisé sur l’élément de focus perdu ou si TryCancel réussit, aucun autre événement n’est déclenché.
  2. GettingFocus Si le focus est réinitialisé sur l’élément de focus perdu ou si TryCancel réussit, aucun autre événement n’est déclenché.
  3. LostFocus
  4. GotFocus

L’image suivante montre comment, lors d’un déplacement à droite à partir de A, le XYFocus choisit B4 comme candidat. B4 déclenche ensuite l’événement GettingFocus où ListView a la possibilité de réaffecter le focus à B3.

Modification de la cible de navigation focus sur l’événement GettingFocus

Modification de la cible de navigation focus sur l’événement GettingFocus

Ici, nous montrons comment gérer l’événement GettingFocus et le focus de redirection.

<StackPanel Orientation="Horizontal">
    <Button Content="A" />
    <ListView x:Name="MyListView" SelectedIndex="2" GettingFocus="OnGettingFocus">
        <ListViewItem>LV1</ListViewItem>
        <ListViewItem>LV2</ListViewItem>
        <ListViewItem>LV3</ListViewItem>
        <ListViewItem>LV4</ListViewItem>
        <ListViewItem>LV5</ListViewItem>
    </ListView>
</StackPanel>
private void OnGettingFocus(UIElement sender, GettingFocusEventArgs args)
{
    //Redirect the focus only when the focus comes from outside of the ListView.
    // move the focus to the selected item
    if (MyListView.SelectedIndex != -1 && 
        IsNotAChildOf(MyListView, args.OldFocusedElement))
    {
        var selectedContainer = 
            MyListView.ContainerFromItem(MyListView.SelectedItem);
        if (args.FocusState == 
            FocusState.Keyboard && 
            args.NewFocusedElement != selectedContainer)
        {
            args.TryRedirect(
                MyListView.ContainerFromItem(MyListView.SelectedItem));
            args.Handled = true;
        }
    }
}

Ici, nous montrons comment gérer l’événement LosingFocus pour un CommandBar et définir le focus lorsque le menu est fermé.

<CommandBar x:Name="MyCommandBar" LosingFocus="OnLosingFocus">
     <AppBarButton Icon="Back" Label="Back" />
     <AppBarButton Icon="Stop" Label="Stop" />
     <AppBarButton Icon="Play" Label="Play" />
     <AppBarButton Icon="Forward" Label="Forward" />

     <CommandBar.SecondaryCommands>
         <AppBarButton Icon="Like" Label="Like" />
         <AppBarButton Icon="Share" Label="Share" />
     </CommandBar.SecondaryCommands>
 </CommandBar>
private void OnLosingFocus(UIElement sender, LosingFocusEventArgs args)
{
    if (MyCommandBar.IsOpen == true && 
        IsNotAChildOf(MyCommandBar, args.NewFocusedElement))
    {
        if (args.TryCancel())
        {
            args.Handled = true;
        }
    }
}

Rechercher le premier et le dernier élément pouvant être focusé

Les méthodes FocusManager.FindFirstFocusableElement et FocusManager.FindLastFocusableElement déplacent le focus vers le premier ou le dernier élément pouvant être mis au point dans l’étendue d’un objet (l’arborescence d’éléments d’un UIElement ou l’arborescence de texte d’un TextElement). L’étendue est spécifiée dans l’appel (si l’argument est null, l’étendue est la fenêtre active).

Si aucun candidat de focus ne peut être identifié dans l’étendue, null est retourné.

Ici, nous montrons comment spécifier que les boutons d’une barre de commandes ont un comportement directionnel encapsulé (voir Interactions clavier).

<CommandBar x:Name="MyCommandBar" LosingFocus="OnLosingFocus">
    <AppBarButton Icon="Back" Label="Back" />
    <AppBarButton Icon="Stop" Label="Stop" />
    <AppBarButton Icon="Play" Label="Play" />
    <AppBarButton Icon="Forward" Label="Forward" />

    <CommandBar.SecondaryCommands>
        <AppBarButton Icon="Like" Label="Like" />
        <AppBarButton Icon="ReShare" Label="Share" />
    </CommandBar.SecondaryCommands>
</CommandBar>
private void OnLosingFocus(UIElement sender, LosingFocusEventArgs args)
{
    if (IsNotAChildOf(MyCommandBar, args.NewFocussedElement))
    {
        DependencyObject candidate = null;
        if (args.Direction == FocusNavigationDirection.Left)
        {
            candidate = FocusManager.FindLastFocusableElement(MyCommandBar);
        }
        else if (args.Direction == FocusNavigationDirection.Right)
        {
            candidate = FocusManager.FindFirstFocusableElement(MyCommandBar);
        }
        if (candidate != null)
        {
            args.NewFocusedElement = candidate;
            args.Handled = true;
        }
    }
}