Программное перемещение фокуса

Клавиатура, удаленная и D-панель

Для программного перемещения фокуса в приложении Windows можно использовать метод FocusManager.TryMoveFocus или метод FindNextElement .

TryMoveFocus пытается изменить фокус с элемента с фокусом на следующий фокусируемый элемент в указанном направлении, в то время как FindNextElement извлекает элемент (как DependencyObject), который будет получать фокус на основе указанного направления навигации (только направление навигации, нельзя использовать для эмуляции навигации табуляции).

Примечание.

Рекомендуется использовать метод FindNextElement вместо FindNextFocusableElement, так как FindNextFocusableElement получает uiElement, который возвращает значение NULL, если следующий фокусируемый элемент не является элементом UIElement (например, объектом Гиперссылки).

Поиск кандидата фокуса в области

Вы можете настроить поведение навигации фокуса для TryMoveFocus и FindNextElement, включая поиск следующего кандидата фокуса в определенном дереве пользовательского интерфейса или исключение определенных элементов из рассмотрения.

В этом примере используется игра TicTacToe для демонстрации методов TryMoveFocus и 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);
    }
}

Используйте FindNextElementOptions , чтобы дополнительно настроить определение кандидатов фокуса. Этот объект предоставляет следующие свойства:

  • SearchRoot — область поиска кандидатов на навигацию фокуса дочерним элементам этого объекта DependencyObject. Null указывает, чтобы начать поиск из корня визуального дерева.

Внимание

Если одно или несколько преобразований применяются к потомкам SearchRoot , которые размещают их за пределами направления, эти элементы по-прежнему считаются кандидатами.

  • ExclusionRect — кандидаты навигации фокуса определяются с помощью "вымышленного" ограничивающего прямоугольника, где все перекрывающиеся объекты исключены из фокуса навигации. Этот прямоугольник используется только для вычислений и никогда не добавляется в визуальное дерево.
  • HintRect — кандидаты навигации фокуса определяются с помощью ограничивающего прямоугольника с привязкой к элементам, наиболее вероятным для получения фокуса. Этот прямоугольник используется только для вычислений и никогда не добавляется в визуальное дерево.
  • XYFocusNavigationStrategyOverride — стратегия навигации фокуса, используемая для определения оптимального элемента кандидата для получения фокуса.

На следующем рисунке показаны некоторые из этих понятий.

Если элемент B имеет фокус, FindNextElement определяет I как кандидат фокуса при переходе справа. Это объясняется следующими причинами.

  • Из-за hintRect на A начальная ссылка — A, а не B
  • C не является кандидатом, так как MyPanel был указан как SearchRoot
  • F не является кандидатом, так как исключениеRect перекрывает его

Поведение пользовательской навигации фокуса с помощью подсказок навигации

Поведение пользовательской навигации фокуса с помощью подсказок навигации

Событие NoFocusCandidateFound

Событие UIElement.NoFocusCandidateFound запускается при нажатии клавиш табуляции или стрелки, и в указанном направлении нет кандидата фокуса. Это событие не запускается для TryMoveFocus.

Так как это перенаправленное событие, оно пузырьки из фокусированного элемента вверх по последовательным родительским объектам в корень дерева объектов. Это позволяет обрабатывать событие по мере необходимости.

Здесь показано, как сетка открывает SplitView , когда пользователь пытается переместить фокус слева от наиболее фокусируемого элемента управления (см. статью " Проектирование для Xbox и телевизора").

<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 и LostFocus

События UIElement.GotFocus и UIElement.LostFocus запускаются, когда элемент получает фокус или теряет фокус соответственно. Это событие не запускается для TryMoveFocus.

Так как это перенаправленные события, они пузырятся из фокусированного элемента вверх через последовательные родительские объекты в корень дерева объектов. Это позволяет обрабатывать событие по мере необходимости.

События GettingFocus и LosingFocus

События UIElement.GettingFocus и UIElement.LostFocus запускались перед соответствующими событиями UIElement.GotFocus и UIElement.LostFocus.

Так как это перенаправленные события, они пузырятся из фокусированного элемента вверх через последовательные родительские объекты в корень дерева объектов. Так как это происходит перед изменением фокуса, вы можете перенаправить или отменить изменение фокуса.

GettingFocus и LosingFocus являются синхронными событиями, поэтому фокус не будет перемещен в то время как эти события бурные. Тем не менее, GotFocus и LostFocus являются асинхронными событиями, что означает, что фокус не будет перемещаться снова перед выполнением обработчика.

Если фокус перемещается через вызов Control.Focus, Метод GettingFocus вызывается во время вызова, в то время как GotFocus поднимается после вызова.

Целевой объект навигации фокуса можно изменить во время событий GettingFocus и LosingFocus (перед перемещением фокуса) через свойство GettingFocusEventArgs.NewFocusedElement . Даже если целевой объект изменен, событие по-прежнему пузырится и целевой объект можно изменить снова.

Чтобы избежать проблем повторного ввода данных, исключение возникает при попытке переместить фокус (с помощью TryMoveFocus или Control.Focus) во время выполнения этих событий.

Эти события запускаются независимо от причины перемещения фокуса (включая навигацию по вкладкам, направление навигации и программную навигацию).

Ниже приведен порядок выполнения для событий фокуса:

  1. Проигрыш фокуса , если фокус сбрасывается обратно в элемент проигрыша фокуса или TryCancel успешно, дальнейшие события не запускаются.
  2. ПолучениеFocus Если фокус сбрасывается обратно в элемент проигрыша фокуса или TryCancel успешно, дальнейшие события не запускаются.
  3. LostFocus
  4. GotFocus

На следующем рисунке показано, как при переходе справа от A XYFocus выбирает B4 в качестве кандидата. Затем B4 запускает событие GettingFocus, где ListView имеет возможность переназначить фокус на B3.

Изменение целевого объекта навигации фокуса на событии GettingFocus

Изменение целевого объекта навигации фокуса на событии GettingFocus

Здесь мы покажем, как обрабатывать событие GettingFocus и фокус перенаправления.

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

Здесь показано, как обрабатывать событие LosingFocus для панели команд и задавать фокус при закрытии меню.

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

Поиск первого и последнего фокусируемых элементов

Методы FocusManager.FindFirstFocusableElement и FocusManager.FindLastFocusableElement перемещают фокус на первый или последний фокусируемый элемент в области объекта (дерево элементов uiElement или текстовое дерево TextElement). Область указана в вызове (если аргумент имеет значение NULL, область — текущее окно).

Если в области не удается определить кандидатов фокуса, возвращается значение NULL.

Здесь мы покажем, как указать, что кнопки панели команд имеют поведение направления вокруг оболочки (см . взаимодействие с клавиатурой).

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