Programmgesteuerte Fokusnavigation

Tastatur, Fernbedienung 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 direktionale Navigation, kann nicht zum Emulieren der Registerkartennavigation verwendet werden).

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 ausschließen von bestimmten Elementen.

In diesem Beispiel wird ein TicTacToe-Spiel verwendet, um die Methoden TryMoveFocus und FindNextElement 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 – Bereich der Suche nach Fokusnavigationskandidaten auf die untergeordneten Elemente dieses Abhängigkeitsobjekts. Null gibt an, dass die Suche vom Stamm der visuellen Struktur aus gestartet wird.

Wichtig

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

  • ExclusionRect – Fokusnavigationskandidaten werden mithilfe eines "fiktiven" umgebenden Rechtecks identifiziert, in dem alle überlappenden Objekte vom Navigationsfokus ausgeschlossen werden. Dieses Rechteck wird nur für Berechnungen verwendet und der visuellen Struktur nie hinzugefügt.
  • HintRect – Fokusnavigationskandidaten werden mithilfe eines "fiktiven" umgebenden Rechtecks identifiziert, das die Elemente identifiziert, die wahrscheinlich den Fokus erhalten. Dieses Rechteck wird nur für Berechnungen verwendet und der visuellen Struktur nie hinzugefügt.
  • XYFocusNavigationStrategyOverride – Die Fokusnavigationsstrategie, die verwendet wird, um das beste Kandidatenelement zu identifizieren, um den Fokus zu erhalten.

Die folgende Abbildung veranschaulicht einige dieser Konzepte.

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

Benutzerdefiniertes Fokusnavigationsverhalten mithilfe von Navigationshinweisen

Benutzerdefiniertes Fokusnavigationsverhalten mithilfe von Navigationshinweisen

NoFocusCandidateFound-Ereignis

Das UIElement.NoFocusCandidateFound-Ereignis wird ausgelöst, wenn die Tab- oder Pfeiltasten gedrückt werden und es keinen Fokuskandidaten in der angegebenen Richtung gibt. Dieses Ereignis wird für TryMoveFocus nicht ausgelöst.

Da es sich um ein routingfähiges Ereignis handelt, wird es vom fokussierten Element über aufeinander folgende übergeordnete Objekte bis zum Stamm der Objektstruktur weitergeleitet. Auf diese Weise können Sie das Ereignis wo immer zutreffend behandeln.

Hier zeigen wir, wie ein Raster eine SplitView öffnet, wenn der Benutzer versucht, den Fokus links vom fokussierbaren Steuerelement 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 diese Routingereignisse sind, blasen sie vom fokussierten Element über aufeinander folgende übergeordnete Objekte bis zum Stamm der Objektstruktur. Auf diese Weise können Sie das Ereignis wo immer zutreffend behandeln.

GettingFocus- und LosingFocus-Ereignisse

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

Da diese Routingereignisse sind, blasen sie vom fokussierten Element über aufeinander folgende übergeordnete Objekte bis zum Stamm der Objektstruktur. Wie dies geschieht, bevor eine Fokusänderung stattfindet, 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 bubbling. GotFocus und LostFocus sind jedoch asynchrone Ereignisse, was bedeutet, dass der Fokus nicht erneut verschoben wird, bevor der Handler ausgeführt wird.

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

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

Um Reentrancy-Probleme zu vermeiden, wird eine Ausnahme ausgelöst, wenn Sie versuchen, den Fokus (mithilfe von TryMoveFocus oder Control.Focus) zu verschieben, während diese Ereignisse bubbling.

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

Dies 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 If focus is reset back to the losing focus element or TryCancel is successful, no further events are fired.
  3. LostFocus
  4. GotFocus

Die folgende Abbildung zeigt, wie der XYFocus bei einem Wechsel von A nach rechts B4 als Kandidat auswählt. B4 löst dann das GettingFocus-Ereignis aus, bei dem das 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 erfahren Sie, 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 zeigen wir, 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 fokussierbaren 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 UIElements oder die Textstruktur eines TextElements). Der Bereich wird im Aufruf angegeben (wenn das Argument null ist, ist der Bereich das aktuelle Fenster).

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

Hier wird gezeigt, wie Sie angeben, dass die Schaltflächen einer CommandBar ein umbrechendes direktionales Verhalten 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;
        }
    }
}