プログラムによるフォーカス ナビゲーションProgrammatic focus navigation

キーボード、リモート、および方向パッド

UWP アプリケーションでフォーカスをプログラムによって移動するには、FocusManager.TryMoveFocus メソッドまたは FindNextElement メソッドのどちらかを使用します。To move focus programmatically in your UWP application, you can use either the FocusManager.TryMoveFocus method or the FindNextElement method.

TryMoveFocus は、フォーカスを持つ要素から、指定された方向にあるフォーカス可能な次の要素にフォーカスを移動します。FindNextElement は、指定されたナビゲーションの方向に基づいてフォーカスを受け取る要素 (DependencyObject) を取得します (方向ナビゲーションのみ、タブ ナビゲーションのエミュレートには使用できません)。TryMoveFocus attempts to change focus from the element with focus to the next focusable element in the specified direction, while FindNextElement retrieves the element (as a DependencyObject) that will receive focus based on the specified navigation direction (directional navigation only, cannot be used to emulate tab navigation).

注意

FindNextFocusableElement ではなく、FindNextElement メソッドの使用をお勧めします。FindNextFocusableElement は UIElement を取得し、この UIElement はフォーカス可能な次の要素が UIElement ではない場合 (Hyperlink オブジェクトなどの場合) に null を返すためです。We recommend using the FindNextElement method instead of FindNextFocusableElement because FindNextFocusableElement retrieves a UIElement, which returns null if the next focusable element is not a UIElement (such as a Hyperlink object).

スコープ内でフォーカス候補を見つけるFind a focus candidate within a scope

TryMoveFocusFindNextElement のどちらについても、フォーカス ナビゲーションの動作をカスタマイズできます。たとえば、特定の UI ツリー内にある次のフォーカス候補を検索したり、特定の要素を対象から除外したりすることができます。You can customize the focus navigation behavior for both TryMoveFocus and FindNextElement, including searching for the next focus candidate within a specific UI tree or excluding specific elements from consideration.

次の例では、三目並べゲームを使用して、TryMoveFocus メソッドと FindNextElement メソッドの使用例を示します。This example uses a TicTacToe game to demonstrate the TryMoveFocus and FindNextElement methods.

<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 を使用して、フォーカス候補の識別方法をさらにカスタマイズします。Use FindNextElementOptions to further customize how focus candidates are identified. このオブジェクトは、次のプロパティを提供します。This object provides the following properties:

  • SearchRoot - フォーカス ナビゲーションの候補を検索するスコープを、この DependencyObject の子に設定します。SearchRoot - Scope the search for focus navigation candidates to the children of this DependencyObject. Null は、ビジュアル ツリーのルートから検索を開始することを示します。Null indicates to start the search from the root of the visual tree.

重要

SearchRoot の子孫に 1 つ以上の変換が適用され、子孫が方向領域の外側に配置される場合でも、これらの要素 (子孫) が候補と見なされます。If one or more transforms are applied to the descendants of SearchRoot that place them outside of the directional area, these elements are still considered candidates.

  • ExclusionRect - フォーカス ナビゲーションの候補は、"架空の" 四角形領域を使用して識別されます。この四角形領域では、領域に重なるすべてのオブジェクトがフォーカスのナビゲーションから除外されます。ExclusionRect - Focus navigation candidates are identified using a "fictitious" bounding rectangle where all overlapping objects are excluded from navigation focus. この四角形領域は、計算にのみ使用され、ビジュアル ツリーには追加されません。This rectangle is used only for calculations and is never added to the visual tree.
  • HintRect - フォーカス ナビゲーションの候補は、"架空の" 四角形領域を使用して識別されます。この四角形領域では、フォーカスを受け取る可能性が最も高い要素が特定されます。HintRect - Focus navigation candidates are identified using a "fictitious" bounding rectangle that identifies the elements most likely to receive focus. この四角形領域は、計算にのみ使用され、ビジュアル ツリーには追加されません。This rectangle is used only for calculations and is never added to the visual tree.
  • XYFocusNavigationStrategyOverride - フォーカスを受け取る最も適切な候補となる要素を識別するために使用されるナビゲーション方法です。XYFocusNavigationStrategyOverride - The focus navigation strategy used to identify the best candidate element to receive focus.

次の図は、これらの概念の一部を示しています。The following image illustrates some of these concepts.

要素 B にフォーカスがある場合、右に移動すると、FindNextElement によって I がフォーカス候補として識別されます。When element B has focus, FindNextElement identifies I as the focus candidate when navigating to the right. その理由は以下のとおりです。The reasons for this are:

  • A に対する HintRect によって、参照の開始が A となり、B ではないためBecause of the HintRect on A, the starting reference is A, not B
  • MyPanel が SearchRoot として指定されているため、C は候補にはならないC is not a candidate because MyPanel has been specified as the SearchRoot
  • ExclusionRect が F に重なっているため、F は候補にはならないF is not a candidate because the ExclusionRect overlaps it

ナビゲーション ヒントを使用したカスタム フォーカス ナビゲーションの動作

ナビゲーション ヒントを使用してカスタムのフォーカスのナビゲーション動作Custom focus navigation behavior using navigation hints

NoFocusCandidateFound イベントNoFocusCandidateFound event

UIElement.NoFocusCandidateFound イベントは、ユーザーが Tab キーまたは方向キーを押したが、指定された方向にフォーカス候補がない場合に発生します。The UIElement.NoFocusCandidateFound event is fired when the tab or arrow keys are pressed and there is no focus candidate in the specified direction. このイベントは、TryMoveFocus に対しては発生しません。This event is not fired for TryMoveFocus.

これはルーティング イベントであるため、フォーカスのある要素から、連続する親オブジェクトを通ってオブジェクト ツリーのルートまでバブル アップします。Because this is a routed event, it bubbles from the focused element up through successive parent objects to the root of the object tree. これにより、適切な場合はいつでもイベントを処理することができます。This lets you handle the event wherever appropriate.

ユーザーがフォーカス可能な一番左のコントロールの左側にフォーカスを移動しようとしたときに、Grid がどのようにして SplitView を開くかを次に示します (「Xbox およびテレビ向け設計」をご覧ください)。Here, we show how a Grid opens a SplitView when the user attempts to move focus to the left of the left-most focusable control (see Designing for Xbox and 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;
    }
}

GotFocus イベントと LostFocus events イベントGotFocus and LostFocus events

UIElement.GotFocus イベントと UIElement.LostFocus イベントは、それぞれ要素がフォーカスを取得したとき、およびフォーカスを失ったときに発生します。The UIElement.GotFocus and UIElement.LostFocus events are fired when an element gets focus or loses focus, respectively. このイベントは、TryMoveFocus に対しては発生しません。This event is not fired for TryMoveFocus.

これらのイベントはルーティング イベントであるため、フォーカスのある要素から、連続する親オブジェクトを通ってオブジェクト ツリーのルートまでバブル アップします。Because these are routed events, they bubble from the focused element up through successive parent objects to the root of the object tree. これにより、適切な場合はいつでもイベントを処理することができます。This lets you handle the event wherever appropriate.

GettingFocus イベントと LosingFocus イベントGettingFocus and LosingFocus events

UIElement.GettingFocus イベントと UIElement.LosingFocus イベントは、それぞれに対応する UIElement.GotFocusUIElement.LostFocus の前に発生します。The UIElement.GettingFocus and UIElement.LosingFocus events fire before the respective UIElement.GotFocus and UIElement.LostFocus events.

これらのイベントはルーティング イベントであるため、フォーカスのある要素から、連続する親オブジェクトを通ってオブジェクト ツリーのルートまでバブル アップします。Because these are routed events, they bubble from the focused element up through successive parent objects to the root of the object tree. これはフォーカスの移動前に行われるため、フォーカスの移動をリダイレクトしたり、キャンセルしたりすることができます。As this happens before a focus change takes place, you can redirect or cancel the focus change.

GettingFocusLosingFocus は同期イベントであるため、これらのイベントのバブル中はフォーカスが移動しません。GettingFocus and LosingFocus are synchronous events so focus won’t be moved while these events are bubbling. ただし、GotFocusLostFocus は非同期イベントであるため、ハンドラーが実行される前にフォーカスがもう一度移動しないことは保証されません。However, GotFocus and LostFocus are asynchronous events, which means there is no guarantee that focus won’t move again before the handler is executed.

Control.Focus の呼び出しを経由してフォーカスが移動した場合、その呼び出し時に GettingFocus が発生しますが、GotFocus は、その呼び出しの後に発生します。If focus moves through a call to Control.Focus, GettingFocus is raised during the call, while GotFocus is raised after the call.

GettingFocus イベントや LosingFocus イベントが発生しているとき (フォーカスが移動する前) に、GettingFocusEventArgs.NewFocusedElement プロパティを使用することで、フォーカス ナビゲーションのターゲットを変更できます。The focus navigation target can be changed during the GettingFocus and LosingFocus events (before focus moves) through the GettingFocusEventArgs.NewFocusedElement property. ターゲットが変更されている場合でも、イベントはバブルし、ターゲットをもう一度変更できます。Even if the target is changed, the event still bubbles and the target can be changed again.

再入の問題を回避するために、これらのイベントのバブル中にフォーカスを移動しようとした場合 (TryMoveFocus または Control.Focus を使用)、例外がスローされます。To avoid reentrancy issues, an exception is thrown if you try to move focus (using TryMoveFocus or Control.Focus) while these events are bubbling.

これらのイベントは、フォーカスの移動理由 (たとえば、タブ ナビゲーション、方向ナビゲーション、プログラムによるナビゲーションなど) に関係なく発生します。These events are fired regardless of the reason for the focus moving (including tab navigation, directional navigation, and programmatic navigation).

フォーカス イベントの実行順序を以下に示します。Here is the order of execution for the focus events:

  1. LosingFocus: フォーカスを失った要素にフォーカスがリセットされるか、TryCancel が成功した場合、それ以上イベントは発生しません。LosingFocus If focus is reset back to the losing focus element or TryCancel is successful, no further events are fired.
  2. GettingFocus: フォーカスを失った要素にフォーカスがリセットされるか、TryCancel が成功した場合、それ以上イベントは発生しません。GettingFocus If focus is reset back to the losing focus element or TryCancel is successful, no further events are fired.
  3. LostFocusLostFocus
  4. GotFocusGotFocus

次の図は、A から右に移動するとき、XYFocus では B4 がどのようにして候補として選択されるかを示しています。The following image shows how, when moving to the right from A, the XYFocus chooses B4 as a candidate. 候補として選択された後、B4 によって GettingFocus イベントが発生し、ListView が B3 にフォーカスを再割り当てすることができます。B4 then fires the GettingFocus event where the ListView has the opportunity to reassign focus to B3.

GettingFocus イベントでフォーカス ナビゲーションのターゲットを変更する

GettingFocus イベントでフォーカスのナビゲーション ターゲットを変更します。Changing focus navigation target on GettingFocus event

GettingFocus イベントを処理し、フォーカスをリダイレクトする方法を次に示します。Here, we show how to handle the GettingFocus event and redirect focus.

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

CommandBarLosingFocus イベントを処理し、メニューが閉じたときにフォーカスを設定する方法を次に示します。Here, we show how to handle the LosingFocus event for a CommandBar and set focus when the menu is closed.

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

フォーカス可能な最初と最後の要素を見つけるFind the first and last focusable element

FocusManager.FindFirstFocusableElement メソッドと FocusManager.FindLastFocusableElement メソッドでは、オブジェクトのスコープ内にあるフォーカス可能な最初または最後の要素にフォーカスが移動されます (UIElement の要素ツリーや TextElement のテキスト ツリー)。The FocusManager.FindFirstFocusableElement and FocusManager.FindLastFocusableElement methods move focus to the first or last focusable element within the scope of an object (the element tree of a UIElement or the text tree of a TextElement). スコープは呼び出しで指定されます (引数が null の場合、スコープは現在のウィンドウになります)。The scope is specified in the call (if the argument is null, the scope is the current window).

スコープ内でフォーカス候補を識別できない場合、null が返されます。If no focus candidates can be identified in the scope, null is returned.

CommandBar のボタンが折り返し方向動作をするように指定する方法を次に示します (「キーボード操作」をご覧ください)。Here, we show how to specify that the buttons of a CommandBar have a wrap-around directional behavior (see Keyboard Interactions).

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