Partager via


Navigation en mode focus programmé

Clavier, télécommande, et croix directionnelle

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 changer le focus de l'élément actuellement focalisé vers l’élément focusable suivant dans la direction spécifiée, tandis que FindNextElement récupère l’élément (en tant que DependencyObject) qui recevra le focus en fonction de la direction de navigation spécifiée (navigation directionnelle uniquement, ne peut pas être utilisé pour simuler la navigation par tabulation).

Note

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 élément UIElement (tel qu’un objet Hyperlink).

Rechercher un candidat de mise au point dans un périmètre

Vous pouvez personnaliser le comportement de navigation du focus pour TryMoveFocus et FindNextElement, notamment la recherche du candidat focus suivant dans une arborescence d’interface utilisateur spécifique ou l’exclusion d’éléments spécifiques à prendre 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 focus sont identifiés. Cet objet fournit les propriétés suivantes :

  • SearchRoot - Limiter la recherche des candidats de navigation de focus aux enfants de ce DependencyObject. 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 au focus de navigation sont identifiés à l'aide d'un rectangle de délimitation « fictif » où tous les objets se chevauchant 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 pour la navigation du point de focus sont identifiés par l'intermédiaire 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 de focus utilisée pour identifier le meilleur élément candidat à recevoir la mise au point.

L’image suivante illustre certains de ces concepts.

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

  • En raison de 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é comme SearchRoot
  • F n’est pas candidat, car l’ExclusionRect le chevauche.

Comportement personnalisé de mise au point de navigation à l’aide d’indices de navigation

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

Événement NoFocusCandidateFound

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

Étant donné qu’il s’agit d’un événement routé, il se propage de l’élément focalisé vers le haut par des objets parents successifs jusqu’à la racine de l’arborescence d’objets. Cela vous permet de gérer l’événement, où qu’il soit approprié.

Ici, nous montrons comment une grille ouvre un SplitView lorsque l’utilisateur tente de déplacer le focus vers la gauche du premier contrôle focusable (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 se propagent de l’élément ciblé jusqu’à la racine de la hiérarchie d’objets. Cela vous permet de gérer l’événement, où qu’il soit 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 remontent de l'élément focalisé à travers les objets parents successifs jusqu'à la racine de l'arborescence d'objets. Comme cela se produit avant qu’une modification de focus ne se produise, vous pouvez rediriger ou annuler la modification du focus.

GettingFocus et LosingFocus sont des événements synchrones de sorte que le focus ne soit pas déplacé pendant que ces événements sont en propagation. Toutefois, GotFocus et LostFocus sont des événements asynchrones, ce qui signifie qu’il n’existe aucune garantie que le focus ne se déplace plus avant l’exécution du gestionnaire.

Si le focus passe par 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 à se propager, 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 Control.Focus) pendant que ces événements sont en cours de propagation.

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

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

  1. LosingFocus Si le focus est réinitialisé à l’élément perdant le focus 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 qui a perdu le focus ou si TryCancel réussit, aucun autre événement n’est déclenché.
  3. LostFocus
  4. GotFocus

L’image suivante montre comment, lorsque vous passez à droite à partir de A, le XYFocus choisit B4 en tant que 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 rediriger le focus.

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

Les méthodes FocusManager.FindFirstFocusableElement et FocusManager.FindLastFocusableElement déplacent le focus sur le premier ou le dernier élément focusable 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 a la valeur Null, l’étendue est la fenêtre active).

Si aucun candidat de focus ne peut être identifié dans la portée, la valeur null est retournée.

Nous expliquons comment spécifier que les boutons d'un CommandBar ont un comportement directionnel circulaire (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;
        }
    }
}