Sdílet prostřednictvím


Programovatelná navigace zaostření

Klávesnice, vzdálená klávesnice a D-pad

K programovému přesunutí fokusu v aplikaci pro Windows můžete použít metodu FocusManager.TryMoveFocus nebo Metodu FindNextElement .

TryMoveFocus se pokusí změnit zaměření z prvku, který má zaměření, na další zaostřitelný prvek ve zvoleném směru, zatímco FindNextElement načte prvek (jako DependencyObject), který bude přijímat zaměření na základě zvolené navigační směrování (jen směrová navigace, nelze použít k emulaci navigace pomocí tabulátoru).

Poznámka:

Doporučujeme místo FindNextFocusableElement použít metodu FindNextFocusableElement, protože FindNextFocusableElement načte UIElement, který vrátí hodnotu null, pokud další fokusovatelný prvek není UIElement (například Hypertextový objekt).

Vyhledání kandidáta zaostření v rámci rozsahu

Můžete přizpůsobit chování navigace fokusu pro TryMoveFocus i FindNextElement, včetně hledání dalšího kandidáta fokusu v rámci konkrétního stromu uživatelského rozhraní nebo vyloučení konkrétních prvků z úvahy.

Tento příklad používá hru TicTacToe k demonstraci metod TryMoveFocus a 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);
    }
}

Pomocí FindNextElementOptions můžete dále přizpůsobit, jak se identifikují kandidáti pro zaměření. Tento objekt poskytuje následující vlastnosti:

  • SearchRoot – Vymezení rozsahu hledání pro kandidáty navigace v rámci fokusu na potomky tohoto DependencyObject. Null označuje zahájit hledání od kořene vizuálního stromu.

Důležité

Pokud jsou na potomky SearchRoot aplikovány transformace, které je umístí mimo směrovou oblast, jsou tyto prvky stále považovány za kandidáty.

  • ExclusionRect – Kandidáti na navigační fokus se identifikují pomocí "fiktivního" ohraničujícího obdélníku, kde jsou všechny překrývající se objekty vyloučeny z navigačního fokusu. Tento obdélník se používá pouze pro výpočty a nikdy se nepřidá do vizuálního stromu.
  • HintRect – Kandidáti pro navigaci fokusu jsou identifikováni pomocí "fiktivního" ohraničujícího obdélníku, který identifikuje prvky, které s největší pravděpodobností dostanou fokus. Tento obdélník se používá pouze pro výpočty a nikdy se nepřidá do vizuálního stromu.
  • XYFocusNavigationStrategyOverride – strategie navigace fokusu používaná k identifikaci nejlepšího kandidáta prvku pro získání fokusu.

Následující obrázek znázorňuje některé z těchto konceptů.

Když prvek B má fokus, FindNextElement identifikuje I jako kandidáta fokusu při navigaci doprava. Důvody jsou:

  • Vzhledem k HintRectu na A je počáteční odkaz A, ne B.
  • C není kandidát, protože MyPanel byl zadán jako SearchRoot
  • F není kandidát, protože ho překrývá ExclusionRect.

Vlastní chování navigace zaostření pomocí navigačních vodítek

Vlastní chování navigace podle fokusu pomocí navigačních vodítek

NoFocusCandidateFound – událost

Událost UIElement.NoFocusCandidateFound se vyvolá, když jsou stisknuty klávesy tabulátoru nebo šipek a v určeném směru není žádný dostupný prvek pro zaměření. Tato událost se neaktivuje pro TryMoveFocus.

Vzhledem k tomu, že se jedná o směrovanou událost, bublinuje z prioritního prvku až po následné nadřazené objekty do kořene stromu objektů. To vám umožní zpracovat událost všude, kde je to vhodné.

Tady si ukážeme, jak Mřížka otevře SplitView , když se uživatel pokusí přesunout fokus nalevo od levého ovládacího prvku s možností fokusu (viz Návrh pro Xbox a 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;
    }
}

Události GotFocus a LostFocus

Události UIElement.GotFocus a UIElement.LostFocus se aktivují, když se prvek zaměří nebo ztratí fokus. Tato událost se neaktivuje pro TryMoveFocus.

Vzhledem k tomu, že se jedná o směrované události, tyto události bublinově vystoupají z prvku s fokusem nahoru přes následné nadřazené objekty do kořene stromu objektů. To vám umožní zpracovat událost všude, kde je to vhodné.

GettingFocus a LosingFocus – události

Události UIElement.GettingFocus a UIElement.LosingFocus se aktivují před příslušnými událostmi UIElement.GotFocus a UIElement.LostFocus.

Vzhledem k tomu, že se jedná o směrované události, šíří se od fokusovaného prvku nahoru přes následné nadřazené objekty do kořene stromu objektů. Protože se to děje před tím, než ke změně fokusu dojde, můžete změnu fokusu přesměrovat nebo zrušit.

GettingFocus a LosingFocus jsou synchronní události, takže fokus se nepřesune, zatímco tyto události probíhají. GotFocus a LostFocus jsou asynchronní události, což znamená, že neexistuje žádná záruka, že se fokus znovu nepřesune před spuštěním obslužné rutiny.

Pokud fokus prochází voláním Control.Focus, GettingFocus je vyvolán během volání, zatímco GotFocus je vyvolán po volání.

Cíl navigace fokusu lze změnit během událostí GettingFocus a LosingFocus (před přesunutím fokusu) prostřednictvím vlastnosti GettingFocusEventArgs.NewFocusedElement. I když se cíl změní, událost stále bubliny a cíl se dá změnit znovu.

Pokud se chcete vyhnout problémům s opětovným vytvářením, vyvolá se výjimka, pokud se pokusíte přesunout fokus (pomocí TryMoveFocus nebo Control.Focus), zatímco tyto události bublují.

Tyto události se aktivují bez ohledu na důvod přesouvání fokusu (včetně navigace pomocí tabulátoru, navigační navigace a programové navigace).

Zde je pořadí provádění událostí zaostření:

  1. ZtrátaFokusu Pokud se fokus obnoví zpět na prvek, který ztratil fokus, nebo TryCancel je úspěšný, žádné další události se neaktivují.
  2. GettingFocus Pokud se fokus obnoví zpět na prvek, který ztrácí fokus, nebo pokud je TryCancel úspěšný, žádné další události se neaktivují.
  3. LostFocus
  4. GotFocus

Následující obrázek ukazuje, jak při pohybu doprava od A si XYFocus zvolí B4 jako kandidáta. B4 pak aktivuje událost GettingFocus, kde ListView má příležitost znovu přiřadit fokus na B3.

Změna cíle navigace fokusu při události GettingFocus

Změna navigačního cíle při události GettingFocus

Tady si ukážeme, jak zpracovat událost GettingFocus a přesměrovat 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;
        }
    }
}

Tady si ukážeme, jak řešit událost LosingFocus pro CommandBar a nastavit fokus při zavření 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;
        }
    }
}

Vyhledání prvního a posledního fokusovatelného prvku

Metody FocusManager.FindFirstFocusableElement a FocusManager.FindLastFocusableElement přesunou fokus na první nebo poslední fokusovatelný prvek v rámci oboru objektu (strom elementu UIElement nebo textového stromu TextElement). Obor je zadán ve volání (pokud je argument null, obor je aktuální okno).

Pokud v oboru nelze identifikovat žádné kandidáty fokusu, vrací se hodnota null.

Ukážeme si zde, jak určit, že tlačítka panelu příkazů mají obalující směrové chování (viz Interakce klávesnice).

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