Programmgesteuerte Fokusnavigation

Tastatur, Remote und D-Pad

Um den Fokus programmgesteuert in Ihrer Windows-Anwendung zu verschieben, können Sie entweder die FocusManager.TryMoveFocus-Methode oder die FindNextElement-Methode verwenden.

TryMoveFocus versucht, den Fokus vom Element mit Fokus auf das nächste fokussierbare Element in der angegebenen Richtung zu ändern, während FindNextElement das Element (als DependencyObject) abruft, das den Fokus basierend auf der angegebenen Navigationsrichtung erhält (nur die Richtungsnavigation kann nicht verwendet werden, um die Registerkartennavigation zu emulieren).

Hinweis

Es wird empfohlen, die FindNextElement-Methode anstelle von FindNextFocusableElement zu verwenden, da FindNextFocusableElement ein UIElement abruft, das NULL zurückgibt, wenn das nächste fokussierbare Element kein UIElement ist (z. B. ein Hyperlink-Objekt).

Suchen eines Fokuskandidaten innerhalb eines Bereichs

Sie können das Fokusnavigationsverhalten sowohl für TryMoveFocus als auch für FindNextElement anpassen, einschließlich der Suche nach dem nächsten Fokuskandidaten in einer bestimmten Ui-Struktur oder dem Ausschließen bestimmter Elemente aus der Betrachtung.

In diesem Beispiel wird ein TicTacToe-Spiel verwendet, um die TryMoveFocus - und FindNextElement-Methoden zu veranschaulichen.

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

Verwenden Sie FindNextElementOptions , um die Identifizierung von Fokuskandidaten weiter anzupassen. Dieses Objekt stellt die folgenden Eigenschaften bereit:

  • SearchRoot : Richten Sie die Suche nach Fokusnavigationskandidaten auf die untergeordneten Elemente dieses DependencyObject ein. Null gibt an, dass die Suche über den Stamm der visuellen Struktur gestartet wird.

Wichtig

Wenn eine oder mehrere Transformationen auf die Nachfolger von SearchRoot angewendet werden, die sie außerhalb des Richtungsbereichs platzieren, werden diese Elemente weiterhin als Kandidaten betrachtet.

  • ExclusionRect : Fokusnavigationskandidaten werden mithilfe eines "fiktiven" begrenzungsenden Rechtecks identifiziert, bei dem alle sich überlappenden Objekte aus dem Navigationsfokus ausgeschlossen werden. Dieses Rechteck wird nur für Berechnungen verwendet und nie der visuellen Struktur hinzugefügt.
  • HintRect : Fokusnavigationskandidaten werden mithilfe eines "fiktiven" begrenzungsenden Rechtecks identifiziert, das die Elemente identifiziert, die am wahrscheinlichsten den Fokus erhalten. Dieses Rechteck wird nur für Berechnungen verwendet und nie der visuellen Struktur hinzugefügt.
  • XYFocusNavigationStrategyOverride : Die Fokusnavigationsstrategie, die verwendet wird, um das beste Kandidatenelement zu identifizieren, das den Fokus erhalten soll.

Die folgende Abbildung veranschaulicht einige dieser Konzepte.

Wenn Element B den Fokus hat, identifiziert FindNextElement I als Fokuskandidat beim Navigieren nach rechts. Die Gründe hierfür sind:

  • Aufgrund von HintRect auf A lautet der Anfangsverweis A, nicht B.
  • C ist kein Kandidat, da MyPanel als SearchRoot angegeben wurde.
  • F ist kein Kandidat, da der ExclusionRect ihn überschneidet.

Benutzerdefiniertes Fokusnavigationsverhalten mithilfe von Navigationshinweisen

Benutzerdefiniertes Fokusnavigationsverhalten mithilfe von Navigationshinweisen

NoFocusCandidateFound-Ereignis

Das UIElement.NoFocusCandidateFound-Ereignis wird ausgelöst, wenn die Registerkarten- oder Pfeiltasten gedrückt werden und kein Fokuskandidat in der angegebenen Richtung vorhanden ist. Dieses Ereignis wird für TryMoveFocus nicht ausgelöst.

Da es sich um ein Routingereignis handelt, wird es vom fokussierten Element über aufeinanderfolgende übergeordnete Objekte zum Stamm der Objektstruktur weitergeleitet. Auf diese Weise können Sie das Ereignis nach Bedarf behandeln.

Hier wird gezeigt, wie ein Raster eine SplitView öffnet, wenn der Benutzer versucht, den Fokus links neben dem fokussierbaren Steuerelement links zu verschieben (siehe Entwerfen für Xbox und 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;
    }
}

GotFocus- und LostFocus-Ereignisse

Die Ereignisse UIElement.GotFocus und UIElement.LostFocus werden ausgelöst, wenn ein Element den Fokus erhält oder den Fokus verliert. Dieses Ereignis wird für TryMoveFocus nicht ausgelöst.

Da es sich hierbei um Routingereignisse handelt, werden sie vom fokussierten Element über aufeinander folgende übergeordnete Objekte zum Stamm der Objektstruktur weitergeleitet. Auf diese Weise können Sie das Ereignis nach Bedarf behandeln.

GettingFocus- und LosingFocus-Ereignisse

Die Ereignisse UIElement.GettingFocus und UIElement.LosingFocus werden vor den jeweiligen UIElement.GotFocus - und UIElement.LostFocus-Ereignissen ausgelöst.

Da es sich hierbei um Routingereignisse handelt, werden sie vom fokussierten Element über aufeinander folgende übergeordnete Objekte zum Stamm der Objektstruktur weitergeleitet. Da dies vor einer Fokusänderung geschieht, können Sie die Fokusänderung umleiten oder abbrechen.

GettingFocus und LosingFocus sind synchrone Ereignisse, sodass der Fokus nicht verschoben wird, während diese Ereignisse sprudeln. GotFocus und LostFocus sind jedoch asynchrone Ereignisse, was bedeutet, dass es keine Garantie gibt, dass sich der Fokus nicht erneut bewegt, bevor der Handler ausgeführt wird.

Wenn sich der Fokus durch einen Aufruf von Control.Focus bewegt, wird GettingFocus während des Anrufs ausgelöst, während GotFocus nach dem Anruf ausgelöst wird.

Das Fokusnavigationsziel kann während der Ereignisse GettingFocus und LosingFocus (bevor sich der Fokus bewegt) über die GettingFocusEventArgs.NewFocusedElement-Eigenschaft geändert werden. Auch wenn das Ziel geändert wird, wird das Ereignis immer noch blasen und das Ziel kann erneut geändert werden.

Wenn Sie versuchen, den Fokus (mit TryMoveFocus oder Control.Focus) zu verschieben, wird eine Ausnahme ausgelöst, wenn Sie versuchen, den Fokus zu verschieben, während diese Ereignisse brodeln.

Diese Ereignisse werden unabhängig vom Grund für die Fokusverschiebung ausgelöst (einschließlich Registerkartennavigation, Richtungsnavigation und programmgesteuerte Navigation).

Hier ist die Reihenfolge der Ausführung für die Fokusereignisse:

  1. LosingFocus Wenn der Fokus wieder auf das verlorene Fokuselement zurückgesetzt wird oder TryCancel erfolgreich ist, werden keine weiteren Ereignisse ausgelöst.
  2. GettingFocus Wenn der Fokus wieder auf das verlorene Fokuselement zurückgesetzt wird oder TryCancel erfolgreich ist, werden keine weiteren Ereignisse ausgelöst.
  3. LostFocus
  4. GotFocus

Die folgende Abbildung zeigt, wie der XYFocus beim Wechsel von A nach rechts B4 als Kandidaten auswählt. B4 löst dann das GettingFocus-Ereignis aus, bei dem die ListView die Möglichkeit hat, den Fokus erneut auf B3 zuzuweisen.

Ändern des Fokusnavigationsziels für das GettingFocus-Ereignis

Ändern des Fokusnavigationsziels für das GettingFocus-Ereignis

Hier zeigen wir, wie Sie das GettingFocus-Ereignis behandeln und den Fokus umleiten.

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

Hier wird gezeigt, wie sie das LosingFocus-Ereignis für eine CommandBar behandeln und den Fokus festlegen, wenn das Menü geschlossen wird.

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

Suchen des ersten und letzten fokusierbaren Elements

Die Methoden FocusManager.FindFirstFocusableElement und FocusManager.FindLastFocusableElement verschieben den Fokus auf das erste oder letzte fokussierbare Element innerhalb des Bereichs eines Objekts (die Elementstruktur eines UIElement oder die Textstruktur eines TextElements). Der Bereich wird im Aufruf angegeben (wenn das Argument NULL ist, ist der Bereich das aktuelle Fenster).

Wenn keine Fokuskandidaten im Bereich identifiziert werden können, wird NULL zurückgegeben.

Hier wird gezeigt, wie Sie angeben, dass die Schaltflächen einer Befehlsleiste ein umschließendes Richtungsverhalten aufweisen (siehe Tastaturinteraktionen).

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