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

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

Windows アプリケーションでフォーカスをプログラムによって移動するには、FocusManager.TryMoveFocus メソッドまたは FindNextElement メソッドのどちらかを使用します。

TryMoveFocus は、フォーカスを持つ要素から、指定された方向にあるフォーカス可能な次の要素にフォーカスを移動します。FindNextElement は、指定されたナビゲーションの方向に基づいてフォーカスを受け取る要素 (DependencyObject) を取得します (方向ナビゲーションのみ、タブ ナビゲーションのエミュレートには使用できません)。

注意

FindNextFocusableElement ではなく、FindNextElement メソッドの使用をお勧めします。FindNextFocusableElement は UIElement を取得し、この UIElement はフォーカス可能な次の要素が UIElement ではない場合 (Hyperlink オブジェクトなどの場合) に null を返すためです。

スコープ内でフォーカス候補を見つける

TryMoveFocusFindNextElement のどちらについても、フォーカス ナビゲーションの動作をカスタマイズできます。たとえば、特定の UI ツリー内にある次のフォーカス候補を検索したり、特定の要素を対象から除外したりすることができます。

次の例では、三目並べゲームを使用して、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 の子孫に 1 つ以上の変換が適用され、子孫が方向領域の外側に配置される場合でも、これらの要素 (子孫) が候補と見なされます。

  • ExclusionRect - フォーカス ナビゲーションの候補は、"架空の" 四角形領域を使用して識別されます。この四角形領域では、領域に重なるすべてのオブジェクトがフォーカスのナビゲーションから除外されます。 この四角形領域は、計算にのみ使用され、ビジュアル ツリーには追加されません。
  • HintRect - フォーカス ナビゲーションの候補は、"架空の" 四角形領域を使用して識別されます。この四角形領域では、フォーカスを受け取る可能性が最も高い要素が特定されます。 この四角形領域は、計算にのみ使用され、ビジュアル ツリーには追加されません。
  • XYFocusNavigationStrategyOverride - フォーカスを受け取る最も適切な候補となる要素を識別するために使用されるナビゲーション方法です。

次の図は、これらの概念の一部を示しています。

要素 B にフォーカスがある場合、右に移動すると、FindNextElement によって I がフォーカス候補として識別されます。 これには次の理由があります。

  • A に対する HintRect によって、参照の開始が A となり、B ではないため
  • MyPanel が SearchRoot として指定されているため、C は候補にはならない
  • ExclusionRect が F に重なっているため、F は候補にはならない

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

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

NoFocusCandidateFound イベント

UIElement.NoFocusCandidateFound イベントは、ユーザーが Tab キーまたは方向キーを押したが、指定された方向にフォーカス候補がない場合に発生します。 このイベントは、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 events イベント

UIElement.GotFocus イベントと UIElement.LostFocus イベントは、それぞれ要素がフォーカスを取得したとき、およびフォーカスを失ったときに発生します。 このイベントは、TryMoveFocus に対しては発生しません。

これらのイベントはルーティング イベントであるため、フォーカスのある要素から、連続する親オブジェクトを通ってオブジェクト ツリーのルートまでバブル アップします。 これにより、適切な場合はいつでもイベントを処理することができます。

GettingFocus イベントと LosingFocus イベント

UIElement.GettingFocus イベントと UIElement.LosingFocus イベントは、それぞれに対応する UIElement.GotFocusUIElement.LostFocus の前に発生します。

これらのイベントはルーティング イベントであるため、フォーカスのある要素から、連続する親オブジェクトを通ってオブジェクト ツリーのルートまでバブル アップします。 これはフォーカスの移動前に行われるため、フォーカスの移動をリダイレクトしたり、キャンセルしたりすることができます。

GettingFocusLosingFocus は同期イベントであるため、これらのイベントのバブル中はフォーカスが移動しません。 ただし、GotFocusLostFocus は非同期イベントであるため、ハンドラーが実行される前にフォーカスがもう一度移動しないことは保証されません。

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

CommandBarLosingFocus イベントを処理し、メニューが閉じたときにフォーカスを設定する方法を次に示します。

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