Condividi tramite


Spostamento dello stato attivo a livello di codice

Tastiera, telecomando e D-pad

Per spostare lo stato attivo a livello di codice nell'applicazione Windows, è possibile usare il metodo FocusManager.TryMoveFocus o metodo FindNextElement.

TryMoveFocus tenta di spostare lo stato attivo da un elemento a un altro nella direzione specificata, mentre FindNextElement recupera l'elemento (come un DependencyObject) che riceverà lo stato attivo in base alla direzione di spostamento specificata (solo navigazione direzionale, non può essere usata per emulare la navigazione tramite tabulazione).

Nota

È consigliabile usare il metodo FindNextElement anziché FindNextFocusableElement perché FindNextFocusableElement recupera un UIElement, che restituisce null se il prossimo elemento con stato attivabile non è un UIElement (ad esempio un oggetto Hyperlink).

Trovare un candidato per lo stato attivo all'interno di un ambito

È possibile personalizzare il comportamento di spostamento dello stato attivo per TryMoveFocus e FindNextElement, includendo la ricerca del prossimo candidato allo stato attivo all'interno di una specifica struttura ad albero dell'interfaccia utente o escludendo alcuni elementi specifici.

Questo esempio usa un gioco tipo Tris per dimostrare i metodi TryMoveFocus e 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);
    }
}

Usare FindNextElementOptions per personalizzare ulteriormente la modalità di identificazione dei candidati allo stato attivo. Questo oggetto fornisce le seguenti proprietà:

  • SearchRoot: definire l'ambito della ricerca dei candidati allo stato attivo agli elementi figlio di DependencyObject. Null indica di avviare la ricerca dalla radice della struttura ad albero visuale.

Importante

Se una o più trasformazioni vengono applicate ai discendenti di SearchRoot che le posizionano all'esterno dell'area direzionale, questi elementi vengono comunque considerati candidati.

  • ExclusionRect: i candidati allo stato attivo vengono identificati usando un rettangolo di delimitazione fittizio in cui tutti gli oggetti sovrapposti vengono esclusi dallo stato attivo dello spostamento. Questo rettangolo viene usato solo per i calcoli e non viene mai aggiunto alla struttura ad albero visuale.
  • HintRect: i candidati allo stato attivo vengono identificati usando un rettangolo di delimitazione fittizio che identifica gli elementi che riceveranno con maggiore probabilità lo stato attivo. Questo rettangolo viene usato solo per i calcoli e non viene mai aggiunto alla struttura ad albero visuale.
  • XYFocusNavigationStrategyOverride: la strategia di spostamento dello stato attivo usata per identificare l'elemento candidato migliore per ricevere lo stato attivo.

L'immagine seguente mostra alcuni di questi concetti.

Quando l'elemento B ha lo stato attivo, FindNextElement identifica I come candidato allo stato attivo quando lo spostamento avviene a destra. per questi motivi:

  • Dato HintRect su A, il riferimento iniziale è A, non B
  • C non è un candidato perché MyPanel è stato specificato come SearchRoot
  • F non è un candidato perché ExclusionRect si sovrappone a esso

Comportamento di spostamento dello stato attivo personalizzato tramite hint di spostamento

Comportamento di spostamento dello stato attivo personalizzato tramite hint di spostamento

Evento NoFocusCandidateFound

L'evento UIElement.NoFocusCandidateFound viene generato quando vengono premuto i tasti di direzione o tab e non vi sono candidati allo stato attivo nella direzione specificata. Questo evento non viene generato per TryMoveFocus.

Poiché si tratta di un evento indirizzato, va dall'elemento con lo stato attivo fino alla radice della struttura a oggetti passando per oggetti padre successivi. In questo modo è possibile gestire l'evento ovunque sia appropriato.

Qui, si mostra come un oggetto Grid apre un oggetto SplitView quando l'utente tenta di spostare lo stato attivo a sinistra del controllo con stato attivabile più a sinistra (vedere Progettazione per Xbox e 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;
    }
}

Eventi GotFocus e LostFocus

Gli eventi UIElement.GotFocus e UIElement.LostFocus vengono generati quando un elemento ottiene o perde lo stato attivo. Questo evento non viene generato per TryMoveFocus.

Poiché si tratta di eventi indirizzati, vanno dall'elemento con lo stato attivo fino alla radice della struttura a oggetti passando per oggetti padre successivi. In questo modo è possibile gestire l'evento ovunque sia appropriato.

Eventi GettingFocus e LosingFocus

Gli eventi UIElement.GettingFocus e UIElement.LosingFocus vengono generati prima dei rispettivi eventi UIElement.GotFocus e UIElement.LostFocus.

Poiché si tratta di eventi indirizzati, vanno dall'elemento con lo stato attivo fino alla radice della struttura a oggetti passando per oggetti padre successivi. Poiché questo avviene prima che si verifichi un cambiamento dello stato attivo, è possibile reindirizzare o annullare un cambiamento dello stato attivo.

GettingFocus e LosingFocus sono eventi sincroni, pertanto lo stato attivo non verrà spostato finché questi eventi vengono propagati. Tuttavia, GotFocus e LostFocus sono eventi asincroni, il che significa che non vi è garanzia che lo stato attivo non venga nuovamente spostato prima dell'esecuzione del gestore.

Se lo stato attivo si sposta attraverso una chiamata a Control.Focus, GettingFocus viene generato durante la chiamata, mentre GotFocus viene generato dopo.

La destinazione dello spostamento dello stato attivo può essere modificata durante gli eventi GettingFocus e LosingFocus (prima dello spostamento dello stato attivo) attraverso la proprietà GettingFocusEventArgs.NewFocusedElement. Anche se la destinazione cambia, l'evento continua a essere propagato e la destinazione può essere nuovamente modificata.

Per evitare problemi di codice rientrante, viene generata un'eccezione se si tenta di spostare lo stato attivo (usando TryMoveFocus o Control.Focus) mentre questi eventi vengono propagati.

Questi eventi vengono generati indipendentemente dal motivo dello spostamento dello stato attivo (inclusi spostamento tramite tabulazioni, spostamento direzionale e spostamento programmatico).

Ecco l'ordine di esecuzione per gli eventi focus:

  1. LosingFocus Se lo stato attivo viene reimpostato sull'elemento senza stato attivo o TryCancel va a buon fine, non vengono generati altri eventi.
  2. GettingFocus Se lo stato attivo viene reimpostato sull'elemento senza stato attivo o TryCancel è andato a buon fine, non vengono generati altri eventi.
  3. LostFocus
  4. GotFocus

L'immagine seguente mostra il modo in cui, quando ci si sposta a destra da A, XYFocus sceglie B4 come candidato. B4 genera l'evento GettingFocus in cui ListView ha l'opportunità di riassegnare lo stato attivo su B3.

Modificare la destinazione di spostamento dello stato attivo sull'evento GettingFocus

Modificare la destinazione di spostamento dello stato attivo sull'evento GettingFocus

Qui, si mostra come gestire l'evento GettingFocus e reindirizzare lo stato attivo.

<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;
        }
    }
}

Qui, si mostra come gestire l'evento LosingFocus per un oggetto CommandBar e impostare lo stato attivo quando si chiude il menu.

<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;
        }
    }
}

Trovare il primo e l'ultimo elemento con stato attivabile

I metodi FocusManager.FindFirstFocusableElement e FocusManager.FindLastFocusableElement spostano lo stato attivo sul primo o sull'ultimo elemento con stato attivabile nell'ambito di un oggetto (la struttura di elementi di un oggetto UIElement o la struttura di testo di un oggetto TextElement). L'ambito viene specificato nella chiamata (se l'argomento è Null, l'ambito è la finestra corrente).

Se nell'ambito non è possibile identificare candidati allo stato attivo, viene restituito null.

Qui, si mostra come specificare che i pulsanti di un oggetto CommandBar tengano un comportamento direzionale wrap-around (vedere Interazioni tramite tastiera).

<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;
        }
    }
}