次の方法で共有


リスト項目内のネストされたUI

入れ子になった UI は、独立してフォーカスを持つことができるコンテナー内に収められた操作可能なコントロールを公開するユーザー インターフェイス (UI) です。

入れ子になった UI を使用して、重要なアクションの実行を高速化するのに役立つ追加のオプションをユーザーに表示できます。 ただし、公開するアクションが多いほど、UI が複雑になります。 この UI パターンを使用する場合は、細心の注意を払う必要があります。 この記事では、特定の UI に最適なアクション コースを決定するのに役立つガイドラインを提供します。

重要な API: ListView クラスGridView クラス

この記事では、 ListView 項目と GridView 項目で入れ子になった UI を作成する方法について説明します。 このセクションでは、入れ子になった他の UI ケースについては説明しませんが、これらの概念は転送可能です。 開始する前に、UI で ListView コントロールまたは GridView コントロールを使用するための一般的なガイダンスを理解しておく必要があります。これは、 リストリスト ビューとグリッド ビュー に関する記事にあります。

この記事では、ここで定義されている用語 リストリスト アイテム入れ子になった UI を使用します。

  • リスト は、リスト ビューまたはグリッド ビューに含まれる項目のコレクションを参照します。
  • リスト アイテム とは、ユーザーがリスト内でアクションを実行できる個々のアイテムを指します。
  • 入れ子になった UI は、リスト アイテム自体に対するアクションの実行とは別に、ユーザーがアクションを実行できるリスト アイテム内の UI 要素を指します。

入れ子になった U I の部分を示すスクリーンショット。

注: ListView と GridView はどちらも ListViewBase クラスから派生しているため、機能は同じですが、データの表示方法は異なります。 この記事では、リストについて説明すると、ListView コントロールと GridView コントロールの両方に情報が適用されます。

プライマリ アクションとセカンダリ アクション

リストを含む UI を作成する場合は、ユーザーがそれらのリスト アイテムから実行する可能性があるアクションを検討します。

  • ユーザーはアイテムをクリックしてアクションを実行できますか?
    • 通常、リスト アイテムをクリックするとアクションが開始されますが、必ずしもそうとは限りません。
  • ユーザーが実行できるアクションは複数ありますか?
    • たとえば、リスト内のメールをタップすると、そのメールが開きます。 ただし、メールの削除など、ユーザーが最初に開かずに実行する必要がある他のアクションが存在する場合があります。 ユーザーがこのアクションに一覧から直接アクセスすると便利です。
  • アクションをユーザーに公開する方法
    • すべての入力の種類を検討してください。 入れ子になった UI の一部の形式は、1 つの入力メソッドでうまく機能しますが、他のメソッドでは機能しない場合があります。

主なアクションは、ユーザーがリスト アイテムを押したときに発生すると予想される操作です。

セカンダリ アクション は、通常、リスト アイテムに関連付けられているアクセラレータです。 これらのアクセラレータは、リスト管理またはリスト アイテムに関連するアクションに使用できます。

セカンダリ アクションのオプション

リスト UI を作成するときは、まず、Windows でサポートされているすべての入力メソッドを考慮する必要があります。 さまざまな種類の入力の詳細については、「 入力の入門」を参照してください。

アプリが Windows でサポートするすべての入力をサポートしていることを確認したら、アプリのセカンダリ アクションがメイン リストのアクセラレータとして公開するのに十分な重要であるかどうかを判断する必要があります。 公開するアクションが多いほど、UI が複雑になることに注意してください。 メイン リスト UI でセカンダリ アクションを公開する必要がありますか。それとも、別の場所に配置できますか?

これらのアクションに常に任意の入力でアクセスできる必要がある場合は、メイン リスト UI で追加のアクションを公開することを検討してください。

メイン リスト UI にセカンダリ アクションを配置する必要がない場合は、他にもいくつかの方法でユーザーに公開できます。 セカンダリ アクションを配置する場所について考慮できるオプションを次に示します。

詳細ページにセカンダリ アクションを配置する

リスト アイテムが押されたときに移動するページにセカンダリ アクションを配置します。 リスト/詳細パターンを使用する場合、多くの場合、詳細ページはセカンダリ アクションを配置するのに適した場所です。

詳細については、 リスト/詳細パターンを参照してください。

コンテキスト メニューにセカンダリ アクションを配置する

ユーザーが右クリックまたは長押しでアクセスできるコンテキスト メニューにセカンダリ アクションを配置します。 これにより、詳細ページを読み込む必要なく、ユーザーが電子メールの削除などのアクションを実行できるという利点があります。 コンテキスト メニューはプライマリ UI ではなくアクセラレータを目的としているため、これらのオプションを詳細ページでも使用できるようにすることをお勧めします。

ゲームパッドまたはリモート コントロールからの入力時にセカンダリ アクションを公開するには、コンテキスト メニューを使用することをお勧めします。

詳細については、「 コンテキスト メニューとポップアップ」を参照してください。

ポインター入力用に最適化するために、ホバー UI にセカンダリ アクションを配置する

マウスやペンなどのポインター入力でアプリが頻繁に使用されることが予想され、セカンダリ アクションをそれらの入力でのみすぐに使用できるようにする場合は、ホバー時にのみセカンダリ アクションを表示できます。 このアクセラレータは、ポインター入力が使用されている場合にのみ表示されるため、他の入力の種類もサポートするために、他のオプションを必ず使用してください。

ホバーで表示するUIの階層表示

詳細については、「 マウス操作」を参照してください。

プライマリ アクションとセカンダリ アクションの UI 配置

メイン リスト UI でセカンダリ アクションを公開する必要があると判断した場合は、次のガイドラインをお勧めします。

プライマリ アクションとセカンダリ アクションを含むリスト アイテムを作成する場合は、プライマリ アクションを左側に配置し、セカンダリ アクションを右側に配置します。 左から右への読み取りカルチャでは、ユーザーはリスト アイテムの左側のアクションをプライマリ アクションとして関連付けます。

これらの例では、項目がより水平方向に流れるリスト UI について説明します (高さよりも広くなります)。 しかし、リスト アイテムが、正方形に近い形状である場合や、幅よりも高さがある場合があります。 通常、これらはグリッドで使用される項目です。 これらの項目の場合、リストが垂直方向にスクロールしない場合は、右側ではなく、リスト項目の下部にセカンダリ アクションを配置できます。

すべての入力を検討する

入れ子になった UI を使用することを決定する場合は、すべての入力の種類でユーザー エクスペリエンスも評価します。 前述のように、入れ子になった UI は一部の入力の種類に適しています。 しかし、他の人にとっては必ずしもうまく機能するとは限りません。 特に、キーボード、コントローラー、リモート入力では、入れ子になった UI 要素にアクセスできない場合があります。 Windows がすべての入力の種類で動作することを確認するには、次のガイダンスに従ってください。

入れ子になった UI の処理

リストアイテムに複数のアクションが入れ子になっている場合は、キーボード、ゲームパッド、リモコン、またはその他の非ポインター入力を使用してナビゲーションを処理するために、このガイダンスをお勧めします。

リスト項目がアクションを実行するネストされたUI

入れ子になった要素を含むリスト UI で、呼び出し、選択 (単一または複数)、ドラッグ アンド ドロップ操作などのアクションがサポートされている場合は、入れ子になった UI 要素間を移動するために、これらの矢印付き手法をお勧めします。

A、B、C、D の文字でラベル付けされた入れ子になった U I 要素を示すスクリーンショット。

ゲームパッド

ゲームパッドからの入力の場合は、次のユーザー エクスペリエンスを提供します。

  • A から、右方向キーは B にフォーカスを置きます。
  • B から、右方向キーは C にフォーカスを置きます。
  • C から、右方向キーは操作なし、または List の右側にフォーカス可能な UI 要素がある場合は、そこにフォーカスを置きます。
  • C から、左方向キーは B にフォーカスを置きます。
  • B から、左方向キーは A にフォーカスを置きます。
  • A から、左方向キーは操作なしか、List の右側にフォーカス可能な UI 要素がある場合は、そこにフォーカスを置きます。
  • AB、または C から、下方向キーは D にフォーカスを置きます。
  • UI 要素からリスト 項目の左側に移動すると、右方向キーによってフォーカスが A に配置されます。
  • UI 要素からリスト 項目の右側に移動すると、左方向キーによってフォーカスが A に設定されます。

キーボード

キーボードからの入力の場合、これはユーザーが取得するエクスペリエンスです。

  • A から、タブ キーは B にフォーカスを置きます。
  • B から、タブ キーは C にフォーカスを置きます。
  • C では、タブ キーによって、フォーカス可能な次の UI 要素がタブ オーダーにフォーカスされます。
  • C キーから Shift+Tab キーでBにフォーカスを移します。
  • B から Shift キーを押しながら Tab キーまたは左方向キーを押すと、フォーカスが A に設定されます。
  • A では、Shift キーを押しながら Tab キーを押して、フォーカス可能な次の UI 要素にフォーカスを置き、逆のタブ オーダーにします。
  • AB、または C から下方向キーを押すと、フォーカスが D に設定されます。
  • UI 要素からリスト アイテムの左側に移動すると、タブ キーによってフォーカスが A に配置されます。
  • UI 要素からリスト アイテムの右側に移動タブ キーを押して、 フォーカスを C に設定します。

この UI を実現するには、リスト で IsItemClickEnabledtrue に設定します。 SelectionMode には任意の値を指定できます。

これを実装するコードについては、この記事の 「例 」セクションを参照してください。

リスト アイテムがアクションを実行しないネストされた UI

リスト ビューは仮想化と最適化されたスクロール動作を提供しますが、リスト アイテムに関連付けられたアクションがないため、リスト ビューを使用できます。 これらの UI では、通常、リスト アイテムを使用して要素をグループ化し、セットとしてスクロールするようにします。

この種の UI は、前の例よりもはるかに複雑になる傾向があり、ユーザーがアクションを実行できる入れ子になった要素が多数存在します。

複雑な入れ子になった U I のスクリーンショット。ユーザーが操作できる入れ子になった要素が多数表示されています。

この UI を実現するには、リストに次のプロパティを設定します。

<ListView SelectionMode="None" IsItemClickEnabled="False" >
    <ListView.ItemContainerStyle>
         <Style TargetType="ListViewItem">
             <Setter Property="IsFocusEngagementEnabled" Value="True"/>
         </Style>
    </ListView.ItemContainerStyle>
</ListView>

リスト 項目がアクションを実行しない場合は、ゲームパッドまたはキーボードを使用してナビゲーションを処理するために、このガイダンスをお勧めします。

ゲームパッド

ゲームパッドからの入力の場合は、次のユーザー エクスペリエンスを提供します。

  • リスト 項目から、下方向キーは次のリスト 項目にフォーカスを置きます。
  • リスト項目から、左/右のキーは操作なし、またはリストの右側にフォーカス可能な UI 要素がある場合は、そこにフォーカスを置きます。
  • リスト アイテムの [A] ボタンをクリックすると、入れ子になった UI にフォーカスが上/下の左/右の優先順位で配置されます。
  • 入れ子になった UI 内で、XY フォーカス ナビゲーション モデルに従います。 フォーカスは、ユーザーが 'B' ボタンを押してリストアイテムにフォーカスを戻すまで、現在のリストアイテム内に含まれる入れ子になった UI の周りを移動することしかできません。

キーボード

キーボードからの入力の場合、これはユーザーが取得するエクスペリエンスです。

  • リスト アイテムから下方向キーを押すと、次のリスト アイテムにフォーカスが移動します。
  • リストアイテムでは、左/右キーを押しても無効です。
  • リスト 項目から、Tab キーを押すと、入れ子になった UI 項目の次のタブ位置にフォーカスが移動します。
  • 入れ子になった UI 項目の 1 つから、タブを押すと、入れ子になった UI 項目がタブ オーダーで走査されます。 入れ子構造のすべての UI 項目の移動が完了すると、ListView の後に続くタブ順の次のコントロールにフォーカスが移動します。
  • Shift + Tab は、タブの動作とは逆方向に動作します。

Example

この例では、 リスト アイテムがアクションを実行する入れ子になった UI を実装する方法を示します

<ListView SelectionMode="None" IsItemClickEnabled="True"
          ChoosingItemContainer="listview1_ChoosingItemContainer"/>
private void OnListViewItemKeyDown(object sender, KeyRoutedEventArgs e)
{
    // Code to handle going in/out of nested UI with gamepad and remote only.
    if (e.Handled == true)
    {
        return;
    }

    var focusedElementAsListViewItem = FocusManager.GetFocusedElement() as ListViewItem;
    if (focusedElementAsListViewItem != null)
    {
        // Focus is on the ListViewItem.
        // Go in with Right arrow.
        Control candidate = null;

        switch (e.OriginalKey)
        {
            case Windows.System.VirtualKey.GamepadDPadRight:
            case Windows.System.VirtualKey.GamepadLeftThumbstickRight:
                var rawPixelsPerViewPixel = DisplayInformation.GetForCurrentView().RawPixelsPerViewPixel;
                GeneralTransform generalTransform = focusedElementAsListViewItem.TransformToVisual(null);
                Point startPoint = generalTransform.TransformPoint(new Point(0, 0));
                Rect hintRect = new Rect(startPoint.X * rawPixelsPerViewPixel, startPoint.Y * rawPixelsPerViewPixel, 1, focusedElementAsListViewItem.ActualHeight * rawPixelsPerViewPixel);
                candidate = FocusManager.FindNextFocusableElement(FocusNavigationDirection.Right, hintRect) as Control;
                break;
        }

        if (candidate != null)
        {
            candidate.Focus(FocusState.Keyboard);
            e.Handled = true;
        }
    }
    else
    {
        // Focus is inside the ListViewItem.
        FocusNavigationDirection direction = FocusNavigationDirection.None;
        switch (e.OriginalKey)
        {
            case Windows.System.VirtualKey.GamepadDPadUp:
            case Windows.System.VirtualKey.GamepadLeftThumbstickUp:
                direction = FocusNavigationDirection.Up;
                break;
            case Windows.System.VirtualKey.GamepadDPadDown:
            case Windows.System.VirtualKey.GamepadLeftThumbstickDown:
                direction = FocusNavigationDirection.Down;
                break;
            case Windows.System.VirtualKey.GamepadDPadLeft:
            case Windows.System.VirtualKey.GamepadLeftThumbstickLeft:
                direction = FocusNavigationDirection.Left;
                break;
            case Windows.System.VirtualKey.GamepadDPadRight:
            case Windows.System.VirtualKey.GamepadLeftThumbstickRight:
                direction = FocusNavigationDirection.Right;
                break;
            default:
                break;
        }

        if (direction != FocusNavigationDirection.None)
        {
            Control candidate = FocusManager.FindNextFocusableElement(direction) as Control;
            if (candidate != null)
            {
                ListViewItem listViewItem = sender as ListViewItem;

                // If the next focusable candidate to the left is outside of ListViewItem,
                // put the focus on ListViewItem.
                if (direction == FocusNavigationDirection.Left &&
                    !listViewItem.IsAncestorOf(candidate))
                {
                    listViewItem.Focus(FocusState.Keyboard);
                }
                else
                {
                    candidate.Focus(FocusState.Keyboard);
                }
            }

            e.Handled = true;
        }
    }
}

private void listview1_ChoosingItemContainer(ListViewBase sender, ChoosingItemContainerEventArgs args)
{
    if (args.ItemContainer == null)
    {
        args.ItemContainer = new ListViewItem();
        args.ItemContainer.KeyDown += OnListViewItemKeyDown;
    }
}
// DependencyObjectExtensions.cs definition.
public static class DependencyObjectExtensions
{
    public static bool IsAncestorOf(this DependencyObject parent, DependencyObject child)
    {
        DependencyObject current = child;
        bool isAncestor = false;

        while (current != null && !isAncestor)
        {
            if (current == parent)
            {
                isAncestor = true;
            }

            current = VisualTreeHelper.GetParent(current);
        }

        return isAncestor;
    }
}