Dela via


Programmatisk fokusnavigering

Tangentbord, fjärr- och D-pad

Om du vill flytta fokus programmatiskt i ditt Windows-program kan du använda metoden FocusManager.TryMoveFocus eller metoden FindNextElement .

TryMoveFocus försöker ändra fokus från elementet med fokus till nästa fokuserbara element i den angivna riktningen, medan FindNextElement hämtar elementet (som ett DependencyObject) som kommer att få fokus baserat på den angivna navigeringsriktningen (endast riktningsnavigering, kan inte användas för att emulera tabbnavigering).

Anmärkning

Vi rekommenderar att du använder metoden FindNextElement i stället för FindNextFocusableElement eftersom FindNextFocusableElement hämtar ett UIElement, som returnerar null om nästa fokuserbara element inte är ett UIElement (till exempel ett hyperlänkobjekt).

Hitta en fokuskandidat inom ett omfång

Du kan anpassa fokusnavigeringsbeteendet för både TryMoveFocus och FindNextElement, inklusive sökning efter nästa fokuskandidat i ett specifikt gränssnittsträd eller exkludera specifika element från övervägande.

I det här exemplet används ett TicTacToe-spel för att demonstrera metoderna TryMoveFocus och 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);
    }
}

Använd FindNextElementOptions för att ytterligare anpassa hur fokuskandidater identifieras. Det här objektet innehåller följande egenskaper:

  • SearchRoot – Begränsa sökomfånget för fokusnavigeringskandidater till barnobjekt till denna DependencyObject. Null anger att sökningen ska startas från roten i det visuella trädet.

Viktigt!

Om en eller flera transformationer tillämpas på de underordnade till SearchRoot som placerar dem utanför det riktade området betraktas dessa element fortfarande som kandidater.

  • ExclusionRect – Fokusnavigeringskandidater identifieras med hjälp av en "fiktiv" avgränsningsrektangel där alla överlappande objekt undantas från navigeringsfokus. Den här rektangeln används endast för beräkningar och läggs aldrig till i det visuella trädet.
  • HintRect – Fokusnavigeringskandidater identifieras med hjälp av en "fiktiv" rektangel som identifierar de element som mest sannolikt kommer att få fokus. Den här rektangeln används endast för beräkningar och läggs aldrig till i det visuella trädet.
  • XYFocusNavigationStrategyOverride – fokusnavigeringsstrategin som används för att identifiera det bästa kandidatelementet för att få fokus.

Följande bild illustrerar några av dessa begrepp.

När element B har fokus identifierar FindNextElement mig som fokuskandidat när du navigerar till höger. Orsakerna till detta är:

  • På grund av HintRect på A är startreferensen A, inte B
  • C är inte en kandidat eftersom MyPanel har angetts som SearchRoot
  • F är inte en kandidat eftersom ExclusionRect överlappar den

Beteende för anpassad fokusnavigering med hjälp av navigeringstips

Beteende för anpassad fokusnavigering med hjälp av navigeringstips

NoFocusCandidateFound-händelse

Händelsen UIElement.NoFocusCandidateFound utlöses när fliken eller piltangenterna trycks ned och det inte finns någon fokuskandidat i den angivna riktningen. Den här händelsen utlöses inte för TryMoveFocus.

Eftersom det här är en dirigerad händelse bubblar den från det fokuserade elementet uppåt genom efterföljande överordnade objekt till objektträdets rot. På så sätt kan du hantera händelsen när det är lämpligt.

Här visar vi hur ett rutnät öppnar en SplitView när användaren försöker flytta fokus till vänster om den vänster mest fokuserbara kontrollen (se Designa för Xbox och 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;
    }
}

Händelser för GotFocus och LostFocus

Händelserna UIElement.GotFocus och UIElement.LostFocus utlöses när ett element fokuserar eller förlorar fokus. Den här händelsen utlöses inte för TryMoveFocus.

Eftersom det här är routade händelser bubblar de från det fokuserade elementet uppåt genom efterföljande överordnade objekt till objektträdets rot. På så sätt kan du hantera händelsen när det är lämpligt.

GettingFocus- och LosingFocus-händelser

Händelserna UIElement.GettingFocus och UIElement.LosingFocus utlöses före respektive UIElement.GotFocus - och UIElement.LostFocus-händelser .

Eftersom det här är routade händelser bubblar de från det fokuserade elementet uppåt genom efterföljande överordnade objekt till objektträdets rot. Eftersom detta händer innan en fokusändring sker kan du omdirigera eller avbryta fokusändringen.

GettingFocus och LosingFocus är synkrona händelser så fokus flyttas inte medan dessa händelser bubblar. GotFocus och LostFocus är dock asynkrona händelser, vilket innebär att det inte finns någon garanti för att fokus inte flyttas igen innan hanteraren körs.

Om fokus flyttas genom ett anrop till Control.Focus aktiveras GettingFocus under anropet, medan GotFocus aktiveras efter anropet.

Fokusnavigeringsmålet kan ändras under händelserna GettingFocus och LosingFocus (innan fokus flyttas) via egenskapen GettingFocusEventArgs.NewFocusedElement . Även om målet ändras bubblar händelsen fortfarande och målet kan ändras igen.

För att undvika återträdbarhetsproblem utlöses ett undantag om du försöker flytta fokus genom att använda TryMoveFocus eller Control.Focus när dessa händelser bubblar.

Dessa händelser utlöses oavsett orsaken till att fokus flyttas (inklusive fliknavigering, riktningsnavigering och programmeringsnavigering).

Här är körningsordningen för fokushändelserna:

  1. LosingFocus Om fokus återställs till det förlorande fokuselementet eller om TryCancel lyckas utlöses inga ytterligare händelser.
  2. GettingFocus Om fokus återställs till det förlorande fokuselementet eller om TryCancel lyckas utlöses inga ytterligare händelser.
  3. LostFocus
  4. GotFocus

Följande bild visar hur XYFocus väljer B4 som kandidat när du flyttar till höger från A. B4 utlöser sedan händelsen GettingFocus där ListView har möjlighet att omtilldela fokus till B3.

Ändra fokusnavigeringsmål för Händelsen GettingFocus

Ändra fokusnavigeringsmål för Händelsen GettingFocus

Här visar vi hur du hanterar händelsen GettingFocus och omdirigerar fokus.

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

Här visar vi hur du hanterar händelsen LosingFocus för en kommandorad och anger fokus när menyn stängs.

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

Hitta det första och sista fokuserbara elementet

Metoderna FocusManager.FindFirstFocusableElement och FocusManager.FindLastFocusableElement flyttar fokus till det första eller sista fokuserbara elementet inom omfånget för ett objekt (elementträdet i ett UIElement eller textträdet i ett TextElement). Omfånget anges i anropet (om argumentet är null är omfånget det aktuella fönstret).

Om inga fokuskandidater kan identifieras i omfånget returneras null.

Här visar vi hur du anger att knapparna i en kommandorad har ett omslutande riktningsbeteende (se Tangentbordsinteraktioner).

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