Programozott fókusznavigáció

Billentyűzet, távvezérlő és D-pad

Ha programozott módon szeretné áthelyezni a fókuszt a Windows-alkalmazásban, használhatja a FocusManager.TryMoveFocusmetódust vagy a FindNextElement metódust .

A TryMoveFocus megpróbálja módosítani a fókuszt a fókuszt tartalmazó elemről a következő fókuszra a megadott irányban, míg a FindNextElement lekéri azt az elemet ( dependencyObject néven), amely a megadott navigációs irány alapján kapja meg a fókuszt (csak az iránynavigáció nem használható a tabulátornavigáció emulálására).

Megjegyzés:

A FindNextFocusableElement helyett a FindNextElement metódust javasoljuk, mert a FindNextFocusableElement egy UIElement értéket kér le, amely null értéket ad vissza, ha a következő fókuszba helyezhető elem nem UIElement (például hiperhivatkozás-objektum).

Fókuszjelölt keresése hatókörön belül

Testre szabhatja a fókusznavigációs viselkedést a TryMoveFocus és a FindNextElement esetében is, beleértve a következő fókuszjelölt keresését egy adott felhasználói felület fáján belül, vagy kizárhat bizonyos elemeket.

Ez a példa egy TicTacToe-játékot használ a TryMoveFocus és a FindNextElement metódusok bemutatásához.

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

A FindNextElementOptions használatával testre szabhatja a fókuszjelöltek azonosításának módját. Ez az objektum a következő tulajdonságokat biztosítja:

  • SearchRoot – A fókusznavigációs jelöltek keresését korlátozza a DependencyObject gyermekeire. A null érték azt jelzi, hogy a keresést a vizualizációfa gyökeréből indítja el.

Fontos

Ha egy vagy több átalakítást alkalmaz a SearchRoot leszármazottaira, amelyek az irányterületen kívül helyezik el őket, ezek az elemek továbbra is jelölteknek minősülnek.

  • ExclusionRect – A fókusznavigációs jelöltek egy "fiktív" határolókeret használatával vannak azonosítva, ahol az összes átfedésben lévő objektum ki van zárva a navigációs fókuszból. Ez a téglalap csak számításokhoz használható, és soha nem lesz hozzáadva a vizualizációfához.
  • HintRect – A fókusznavigációs jelöltek egy "fiktív" határolókeret használatával vannak azonosítva, amely azonosítja azokat az elemeket, amelyek a legnagyobb valószínűséggel kapják meg a fókuszt. Ez a téglalap csak számításokhoz használható, és soha nem lesz hozzáadva a vizualizációfához.
  • XYFocusNavigationStrategyOverride – A fókusznavigációs stratégia, amellyel azonosíthatja a legjobban alkalmas elemet a fókusz fogadásához.

Az alábbi ábrán néhány fogalom látható.

Ha a B elem fókuszban van, a FindNextElement "I"-t azonosítja mint fókuszjelöltet jobbra navigáláskor. Ennek okai a következők:

Egyéni fókusz navigációs viselkedése navigációs tippek használatával

Egyéni fókusz navigációs viselkedése navigációs tippek használatával

NoFocusCandidateFound esemény

A UIElement.NoFocusCandidateFound esemény akkor aktiválódik, ha a lap vagy a nyílbillentyűk lenyomva vannak, és nincs fókuszjelölt a megadott irányban. Ez az esemény nem aktiválódik a TryMoveFocus esetében.

Mivel ez egy irányított esemény, az fókuszált elemtől felfelé áramlik a szülőobjektumokon keresztül az objektumfa gyökeréhez. Ez lehetővé teszi, hogy az eseményt ott kezelje, ahol szükséges.

Itt bemutatjuk, hogyan nyílik meg a Grid a SplitView, amikor a felhasználó megpróbálja a fókuszt a bal szélső fókuszálható vezérlő bal oldalára mozgatni (lásd: Tervezés Xboxhoz és TV-hez).

<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 és LostFocus események

A UIElement.GotFocus és a UIElement.LostFocus események akkor aktiválódnak, amikor egy elem fókuszba kerül, vagy elveszíti a fókuszt. Ez az esemény nem aktiválódik a TryMoveFocus esetében.

Mivel ezek továbbított események, a fókuszált elemtől indulva buborékszerűen haladnak felfelé az egymást követő szülőobjektumokon át az objektumfa gyökeréhez. Ez lehetővé teszi, hogy az eseményt ott kezelje, ahol szükséges.

GettingFocus és LosingFocus események

A UIElement.GettingFocus és az UIElement.LosingFocus események a megfelelő UIElement.GotFocus és UIElement.LostFocus események előtt aktiválódnak.

Mivel ezek továbbított események, a fókuszált elemtől indulva buborékszerűen haladnak felfelé az egymást követő szülőobjektumokon át az objektumfa gyökeréhez. Mivel ez a fókuszváltás előtt történik, átirányíthatja vagy megszakíthatja a fókuszmódosítást.

A FókuszMegkapás és a FókuszElvesztés szinkron események, így a fókusz nem lesz áthelyezve, amíg ezek az események buborékolnak. A GotFocus és a LostFocus azonban aszinkron események, ami azt jelenti, hogy nincs garancia arra, hogy a fókusz nem mozdul el újra a kezelő végrehajtása előtt.

Ha a fókusz a Control.Focus hívásán keresztül történik, a GettingFocus a hívás során van kiváltva, míg a GotFocus a hívás után lesz kiváltva.

A fókusznavigációs cél a GettingFocus és a LosingFocus események során (a fókusz áthelyezése előtt) módosítható a GettingFocusEventArgs.NewFocusedElement tulajdonságon keresztül. Még ha a cél is módosul, az esemény továbbra is buborékol, és a cél újból módosítható.

A reentranciai problémák elkerülése érdekében kivétel kerül dobásra, ha a fókuszt (TryMoveFocus vagy Control.Focus használatával) próbálja áthelyezni, amikor ezek az események felfutóban vannak.

Ezek az események a fókusz áthelyezésének okától függetlenül aktiválódnak (beleértve a tabulátoros navigációt, az irányított navigációt és a programozott navigációt).

A fókuszesemények végrehajtási sorrendje a következő:

  1. LosingFocus Ha a fókusz visszaáll a vesztes fókuszelemre, vagy a TryCancel sikeres, a rendszer nem aktivál további eseményeket.
  2. GettingFocus Ha a fókusz visszaáll a vesztes fókuszelemre, vagy a TryCancel sikeres, a rendszer nem aktivál további eseményeket.
  3. LostFocus
  4. GotFocus

Az alábbi képen látható, hogy az XYFocus hogyan választja ki a B4-et jelöltként, amikor az A-tól jobbra halad. A B4 ezután aktiválja a GettingFocus eseményt, ahol a ListView-nak lehetősége van a fókusz ismételt hozzárendelésére a B3-ra.

Fókusznavigációs cél módosítása a GettingFocus eseményen

Fókusznavigációs cél módosítása a GettingFocus eseményen

Itt bemutatjuk, hogyan kezelheti a GettingFocus eseményt, és hogyan irányíthatja át a fókuszt.

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

Itt bemutatjuk, hogyan kezelheti egy parancssávLosingFocus eseményét, és hogyan állíthatja be a fókuszt a menü bezárásakor.

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

Az első és az utolsó fókuszba helyezhető elem megkeresése

A FocusManager.FindFirstFocusableElement és a FocusManager.FindLastFocusableElement metódusok áthelyezik a fókuszt az objektum hatókörén belül az első vagy utolsó fókuszba helyezhető elemre (egy UIElement elemfája vagy egy TextElement szövegfája). A hatókör a hívásban van megadva (ha az argumentum null, akkor a hatókör az aktuális ablak).

Ha a hatókörben nem azonosíthatók fókuszjelöltek, a rendszer null értéket ad vissza.

Itt bemutatjuk, hogyan adhatja meg, hogy a parancssáv gombjai körbefuttatásos irányban viselkedjenek (lásd : Billentyűzetműveletek).

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