Navigasi fokus terprogram

Keyboard, jarak jauh, dan D-pad

Untuk memindahkan fokus secara terprogram di aplikasi Windows, Anda dapat menggunakan metode FocusManager.TryMoveFocus atau metode FindNextElement .

TryMoveFocus mencoba mengubah fokus dari elemen dengan fokus ke elemen yang dapat difokuskan berikutnya ke arah yang ditentukan, sementara FindNextElement mengambil elemen (sebagai DependencyObject) yang akan menerima fokus berdasarkan arah navigasi yang ditentukan (navigasi arah saja, tidak dapat digunakan untuk meniru navigasi tab).

Catatan

Sebaiknya gunakan metode FindNextElement alih-alih FindNextFocusableElement karena FindNextFocusableElement mengambil UIElement, yang mengembalikan null jika elemen yang dapat difokuskan berikutnya bukan UIElement (seperti objek Hyperlink).

Menemukan kandidat fokus dalam cakupan

Anda dapat menyesuaikan perilaku navigasi fokus untuk TryMoveFocus dan FindNextElement, termasuk mencari kandidat fokus berikutnya dalam pohon UI tertentu atau mengecualikan elemen tertentu dari pertimbangan.

Contoh ini menggunakan game TicTacToe untuk menunjukkan metode TryMoveFocus dan 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);
    }
}

Gunakan FindNextElementOptions untuk menyesuaikan lebih lanjut bagaimana kandidat fokus diidentifikasi. Objek ini menyediakan properti berikut:

  • SearchRoot - Cakupan pencarian kandidat navigasi fokus ke anak-anak DependencyObject ini. Null menunjukkan untuk memulai pencarian dari akar pohon visual.

Penting

Jika satu atau beberapa transformasi diterapkan ke keturunan SearchRoot yang menempatkannya di luar area arah, elemen-elemen ini masih dianggap sebagai kandidat.

  • ExclusionRect - Kandidat navigasi fokus diidentifikasi menggunakan persegi panjang pembatas "fiktif" di mana semua objek yang tumpang tindih dikecualikan dari fokus navigasi. Persegi panjang ini hanya digunakan untuk perhitungan dan tidak pernah ditambahkan ke pohon visual.
  • HintRect - Kandidat navigasi fokus diidentifikasi menggunakan persegi panjang pembatas "fiktif" yang mengidentifikasi elemen yang kemungkinan besar menerima fokus. Persegi panjang ini hanya digunakan untuk perhitungan dan tidak pernah ditambahkan ke pohon visual.
  • XYFocusNavigationStrategyOverride - Strategi navigasi fokus yang digunakan untuk mengidentifikasi elemen kandidat terbaik untuk menerima fokus.

Gambar berikut mengilustrasikan beberapa konsep ini.

Ketika elemen B memiliki fokus, FindNextElement mengidentifikasi saya sebagai kandidat fokus saat menavigasi ke kanan. Alasan untuk ini adalah:

  • Karena HintRect pada A, referensi awalnya adalah A, bukan B
  • C bukan kandidat karena MyPanel telah ditentukan sebagai SearchRoot
  • F bukan kandidat karena ExclusionRect tumpang tindih

Perilaku navigasi fokus kustom menggunakan petunjuk navigasi

Perilaku navigasi fokus kustom menggunakan petunjuk navigasi

Peristiwa NoFocusCandidateFound

Peristiwa UIElement.NoFocusCandidateFound diaktifkan saat tombol tab atau panah ditekan dan tidak ada kandidat fokus dalam arah yang ditentukan. Kejadian ini tidak diaktifkan untuk TryMoveFocus.

Karena ini adalah peristiwa yang dirutekan, itu gelembung dari elemen yang berfokus melalui objek induk berturut-turut ke akar pohon objek. Ini memungkinkan Anda menangani peristiwa di mana pun yang sesuai.

Di sini, kami menunjukkan bagaimana Grid membuka SplitView saat pengguna mencoba memindahkan fokus ke kiri kontrol paling fokus (lihat Merancang untuk Xbox dan 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;
    }
}

Peristiwa GotFocus dan LostFocus

Peristiwa UIElement.GotFocus dan UIElement.LostFocus dipicu ketika elemen mendapatkan fokus atau kehilangan fokus. Kejadian ini tidak diaktifkan untuk TryMoveFocus.

Karena ini adalah peristiwa yang dirutekan, mereka gelembung dari elemen yang berfokus melalui objek induk berturut-turut ke akar pohon objek. Ini memungkinkan Anda menangani peristiwa di mana pun yang sesuai.

Peristiwa GettingFocus dan LosingFocus

Peristiwa UIElement.GettingFocus dan UIElement.LosingFocus terjadi sebelum peristiwa UIElement.GotFocus dan UIElement.LostFocus masing-masing.

Karena ini adalah peristiwa yang dirutekan, mereka gelembung dari elemen yang berfokus melalui objek induk berturut-turut ke akar pohon objek. Karena ini terjadi sebelum perubahan fokus terjadi, Anda dapat mengalihkan atau membatalkan perubahan fokus.

GettingFocus dan LosingFocus adalah peristiwa sinkron sehingga fokus tidak akan dipindahkan saat peristiwa ini menggelembung. Namun, GotFocus dan LostFocus adalah peristiwa asinkron, yang berarti tidak ada jaminan bahwa fokus tidak akan bergerak lagi sebelum handler dijalankan.

Jika fokus berpindah melalui panggilan ke Control.Focus, GettingFocus dinaikkan selama panggilan, sementara GotFocus dinaikkan setelah panggilan.

Target navigasi fokus dapat diubah selama peristiwa GettingFocus dan LosingFocus (sebelum fokus bergerak) melalui properti GettingFocusEventArgs.NewFocusedElement . Bahkan jika target diubah, peristiwa masih gelembung dan target dapat diubah lagi.

Untuk menghindari masalah reentrancy, pengecualian dilemparkan jika Anda mencoba memindahkan fokus (menggunakan TryMoveFocus atau Control.Focus) saat peristiwa ini menggelembung.

Peristiwa ini diaktifkan terlepas dari alasan pemindahan fokus (termasuk navigasi tab, navigasi arah, dan navigasi terprogram).

Berikut adalah urutan eksekusi untuk peristiwa fokus:

  1. KehilanganFocus Jika fokus diatur ulang kembali ke elemen fokus yang hilang atau TryCancel berhasil, tidak ada peristiwa lebih lanjut yang diaktifkan.
  2. MendapatkanFocus Jika fokus diatur ulang kembali ke elemen fokus yang hilang atau TryCancel berhasil, tidak ada peristiwa lebih lanjut yang diaktifkan.
  3. LostFocus
  4. GotFocus

Gambar berikut menunjukkan caranya, saat berpindah ke kanan dari A, XYFocus memilih B4 sebagai kandidat. B4 kemudian mengaktifkan peristiwa GettingFocus di mana ListView memiliki kesempatan untuk menetapkan kembali fokus ke B3.

Mengubah target navigasi fokus pada peristiwa GettingFocus

Mengubah target navigasi fokus pada peristiwa GettingFocus

Di sini, kami menunjukkan cara menangani peristiwa GettingFocus dan mengalihkan 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;
        }
    }
}

Di sini, kami menunjukkan cara menangani peristiwa LosingFocus untuk CommandBar dan mengatur fokus saat menu ditutup.

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

Menemukan elemen pertama dan terakhir yang dapat difokuskan

Metode FocusManager.FindFirstFocusableElement dan FocusManager.FindLastFocusableElement memindahkan fokus ke elemen pertama atau terakhir yang dapat difokuskan dalam cakupan objek (pohon elemen UIElement atau pohon teks TextElement). Cakupan ditentukan dalam panggilan (jika argumen null, cakupannya adalah jendela saat ini).

Jika tidak ada kandidat fokus yang dapat diidentifikasi dalam cakupan, null dikembalikan.

Di sini, kami menunjukkan cara menentukan bahwa tombol CommandBar memiliki perilaku arah wrap-around (lihat Interaksi Keyboard).

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