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

Клавиатура, пульт управления и крестовина

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

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

Примечание

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

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

Можно настроить поведение перемещения фокуса для обоих методов 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 не является кандидатом, так как ExclusionRect перекрывает его.

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

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

Событие NoFocusCandidateFound

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

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

Здесь показано, как элемент Grid открывает 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.LosingFocus возникают перед соответствующими событиями UIElement.GotFocus и UIElement.LostFocus.

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

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

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

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

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

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

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

  1. LosingFocus — при установке фокуса обратно на элемент, теряющий фокус, или если TryCancel было выполнено успешно; другие события не возникают.
  2. GettingFocus — при установке фокуса обратно на элемент, теряющий фокус, или если 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 и установить фокус, когда меню закрыто.

<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 имеют поведение направления с обтеканием (см. раздел Взаимодействие с помощью клавиатуры).

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