コレクションとリストのコンテキスト コマンドの実行
多くのアプリに、リスト、グリッド、ツリーの形で、ユーザーが操作できるコンテンツのコレクションが含まれています。 たとえば、ユーザーは、項目の削除、名前の変更、フラグ付け、更新ができる可能性があります。 この記事では、どのような種類の入力でも、最善のエクスペリエンスが得られるように、そのような操作をコンテキスト コマンドを使って実装する方法を説明します。
重要な API: ICommand インターフェイス、UIElement.ContextFlyout プロパティ、INotifyPropertyChanged インターフェイス
あらゆる種類の入力に対応するコマンドを作成する
ユーザーはさまざまなデバイスや入力方法を使って Windows アプリを操作できるため、アプリでは入力方法に依存しないコンテキスト メニューと、各種入力方法専用のアクセラレータの両方でコマンドを公開する必要があります。 両方を含めることで、入力方法やデバイスの種類に関わらず、コンテンツに対してコマンドをすばやく呼び出すことができます。
次の表に、いくつかの典型的なコレクションのコマンドと、これらのコマンドを公開する方法を示します。
Command | 入力方法を問わない | マウス アクセラレータ | キーボード アクセラレータ | タッチ アクセラレータ |
---|---|---|---|---|
アイテムの削除 | コンテキスト メニュー | ホバー ボタン | Del キー | スワイプして削除 |
項目にフラグを設定 | コンテキスト メニュー | ホバー ボタン | Ctrl + Shift + G | スワイプしてフラグを設定 |
データの更新 | コンテキスト メニュー | 該当なし | F5 キー | 引っ張って更新 |
項目をお気に入りに追加 | コンテキスト メニュー | ホバー ボタン | F、Ctrl + S | スワイプしてお気に入りに追加 |
通常は、特定の項目に対するすべてのコマンドをその項目のコンテキスト メニューから利用できるようにします。 コンテキスト メニューには、入力の種類にかかわらず、ユーザーがアクセスでき、ユーザーが実行できるコンテキスト コマンドの全部を含めてください。
頻繁にアクセスするコマンドについては、入力アクセラレータを使うことを検討してください。 入力アクセラレータを使用すると、ユーザーは、入力デバイスに応じてすばやく操作を実行できます。 次のような入力アクセラレータがあります。
- スワイプして操作 (タッチ アクセラレータ)
- 引っ張ってデータを更新 (タッチ アクセラレータ)
- キーボード ショートカット (キーボード アクセラレータ)
- アクセス キー (キーボード アクセラレータ)
- マウスとペンのホバー ボタン (ポインター アクセラレータ)
Note
ユーザーは、どの種類のデバイスからでも、すべてのコマンドにアクセスできる必要があります。 たとえば、アプリのコマンドがホバー ボタン ポインター アクセラレータでしか公開されない場合、タッチ ユーザーはコマンドにアクセスできません。 少なくとも、すべてのコマンドにアクセスできるコンテキスト メニューを使用します。
例: PodcastObject データ モデル
推奨されるコマンド実行のデモとして、この記事では、ポッドキャスト アプリ用のポッドキャスト リストを作成します。 コード例では、ユーザーがリストから特定のポッドキャストを「お気に入りに追加」できるようにする方法を示しています。
以下に、この記事で作業するポッドキャスト オブジェクトの定義を示します。
public class PodcastObject : INotifyPropertyChanged
{
// The title of the podcast
public String Title { get; set; }
// The podcast's description
public String Description { get; set; }
// Describes if the user has set this podcast as a favorite
public bool IsFavorite
{
get
{
return _isFavorite;
}
set
{
_isFavorite = value;
OnPropertyChanged("IsFavorite");
}
}
private bool _isFavorite = false;
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(String property)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
}
ユーザーが IsFavorite プロパティの設定を切り替えたときにプロパティの変更に対応できるように、PodcastObject が INotifyPropertyChanged を実装していることに注目してください。
ICommand インターフェイスを使用したコマンドの定義
ICommand インターフェイスを使うと、複数の入力の種類に利用できるコマンドを定義できます。 たとえば、Delete キーが押されたときと、コンテキスト メニューで [削除] が右クリックされたときの 2 種類のイベント ハンドラーで同じ削除コマンドのコードを記述するのではなく、ICommand として削除ロジックを 1 度実装したら、各種入力方法でこの削除ロジックを利用可能にできます。
「お気に入りに追加」の操作を表す ICommand を定義する必要があります。 ポッドキャストをお気に入りに追加するには、コマンドの Execute メソッドを使用します。 特定のポッドキャストがコマンドのパラメーターを介して実行メソッドに渡されます。これは、CommandParameter プロパティを使用してバインドできます。
public class FavoriteCommand: ICommand
{
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
// Perform the logic to "favorite" an item.
(parameter as PodcastObject).IsFavorite = true;
}
}
複数のコレクションや要素で同じコマンドを使用するには、コマンドをページやアプリのリソースとして保存します。
<Application.Resources>
<local:FavoriteCommand x:Key="favoriteCommand" />
</Application.Resources>
コマンドを実行するには、コマンドの Execute メソッドを呼び出します。
// Favorite the item using the defined command
var favoriteCommand = Application.Current.Resources["favoriteCommand"] as ICommand;
favoriteCommand.Execute(PodcastObject);
さまざまな入力に応答する UserControl の作成
項目のリストがあり、各項目が複数の入力方法に応答する場合、項目の UserControl を定義し、これを使用して項目のコンテキスト メニューとイベント ハンドラーを定義することで、コードを簡潔にできます。
Visual Studio で UserControl を作成するステップは次のとおりです。
- ソリューション エクスプローラーで、プロジェクトを右クリックします。 コンテキスト メニューが表示されます。
- [追加] > [新しい項目] の順に選びます。
[新しい項目の追加] ダイアログが表示されます。 - 項目の一覧から [UserControl] を選択します。 任意の名前を付けて、[追加] をクリックします。 Visual Studio によってスタブ UserControl が生成されます。
この記事のポッドキャストの例では、各ポッドキャストはリストにまとめられて表示され、さまざまな方法でポッドキャストを「お気に入りに追加」できるようになります。 ユーザーは次の操作によって、ポッドキャストを「お気に入りに追加」することができます。
- コンテキスト メニューの呼び出し
- キーボード ショートカットの実行
- ホバー ボタンの表示
- スワイプ ジェスチャの実行
これらの動作をカプセル化して、FavoriteCommand を使用できるように、リスト内のポッドキャストを表す「PodcastUserControl」という名前の新しい UserControl を作りましょう。
PodcastUserControl は PodcastObject のフィールドを TextBlocks として表示し、さまざまなユーザーの操作に応答します。 この記事では、この PodcastUserControl を参照し、拡張していきます。
PodcastUserControl.xaml
<UserControl
x:Class="ContextCommanding.PodcastUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
IsTabStop="True" UseSystemFocusVisuals="True"
>
<Grid Margin="12,0,12,0">
<StackPanel>
<TextBlock Text="{x:Bind PodcastObject.Title, Mode=OneWay}" Style="{StaticResource TitleTextBlockStyle}" />
<TextBlock Text="{x:Bind PodcastObject.Description, Mode=OneWay}" Style="{StaticResource SubtitleTextBlockStyle}" />
<TextBlock Text="{x:Bind PodcastObject.IsFavorite, Mode=OneWay}" Style="{StaticResource SubtitleTextBlockStyle}"/>
</StackPanel>
</Grid>
</UserControl>
PodcastUserControl.xaml.cs
public sealed partial class PodcastUserControl : UserControl
{
public static readonly DependencyProperty PodcastObjectProperty =
DependencyProperty.Register(
"PodcastObject",
typeof(PodcastObject),
typeof(PodcastUserControl),
new PropertyMetadata(null));
public PodcastObject PodcastObject
{
get { return (PodcastObject)GetValue(PodcastObjectProperty); }
set { SetValue(PodcastObjectProperty, value); }
}
public PodcastUserControl()
{
this.InitializeComponent();
// TODO: We will add event handlers here.
}
}
この PodcastUserControl では、PodcastObject への参照を DependencyProperty として維持しています。 これにより、PodcastObjects を PodcastUserControl にバインドできるようになります。
いくつか PodcastObjects を生成したら、PodcastObjects を ListView にバインドして、ポッドキャストのリストを作成できます。 PodcastUserControl オブジェクトは、PodcastObjects の視覚エフェクトを記述します。したがって、ListView の ItemTemplate を使用して設定します。
MainPage.xaml
<ListView x:Name="ListOfPodcasts"
ItemsSource="{x:Bind podcasts}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:PodcastObject">
<local:PodcastUserControl PodcastObject="{x:Bind Mode=OneWay}" />
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<!-- The PodcastUserControl will entirely fill the ListView item and handle tabbing within itself. -->
<Style TargetType="ListViewItem" BasedOn="{StaticResource ListViewItemRevealStyle}">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Padding" Value="0"/>
<Setter Property="IsTabStop" Value="False"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
コンテキスト メニューの作成
コンテキスト メニューは、ユーザーの要求に応じて、コマンドやオプションの一覧を表示します。 コンテキスト メニューは、アタッチされた要素に関連するコンテキスト コマンドを提供します。また、通常、その項目固有のセカンダリ操作のために予約されています。
ユーザーは、以下の「コンテキスト アクション」を使用してコンテキスト メニューを呼び出すことができます。
入力 | コンテキスト アクション |
---|---|
マウス | 右クリック |
[キーボード] | Shift + F10、メニュー ボタン |
タッチ | 項目を長押し |
ペン | バレル ボタンを押す、項目を長押し |
Gamepad | メニュー ボタン |
ユーザーはさまざまな種類の入力方法でコンテキスト メニューを開く可能性があるため、リストの項目に対して実行できるコンテキスト コマンドの全部をコンテキスト メニューに含めてください。
ContextFlyout
UIElement クラスによって定義される ContextFlyout プロパティを利用すると、すべての入力の種類で使用できるコンテキスト メニューを簡単に作成できます。 コンテキスト メニューを表すポップアップは MenuFlyout または CommandBarFlyout を使って提供します。上記で定義した「コンテキスト操作」をユーザーが実行すると、項目に対応する MenuFlyout または CommandBarFlyout が表示されます。
メニューとコンテキスト メニューのシナリオを識別し、メニュー ポップアップとコマンド バーのポップアップを使用する場合のガイダンスについては、「メニューとコンテキスト メニュー」を参照してください。
この例では、MenuFlyout を使用して、PodcastUserControl に ContextFlyout を追加します。 ContextFlyout として指定された MenuFlyout には、ポッドキャストをお気に入りに追加するための項目が 1 つだけ含まれています。 この MenuFlyoutItem では上記で定義した favoriteCommand を使い、CommandParameter が PodcastObject にバインドされていることに注意してください。
PodcastUserControl.xaml
<UserControl>
<UserControl.ContextFlyout>
<MenuFlyout>
<MenuFlyoutItem Text="Favorite" Command="{StaticResource favoriteCommand}" CommandParameter="{x:Bind PodcastObject, Mode=OneWay}" />
</MenuFlyout>
</UserControl.ContextFlyout>
<Grid Margin="12,0,12,0">
<!-- ... -->
</Grid>
</UserControl>
また、ContextRequested イベントを使って、コンテキスト操作に応答することもできます。 ContextRequested イベントは、ContextFlyout が指定されている場合は発生しません。
入力アクセラレータの作成
コレクション内の各項目のコンテキスト コマンドをすべて含むコンテキスト メニューを用意することをお勧めしますが、よく実行される特定のコマンドをユーザーがすばやく実行できるようにすることも一案です。 たとえば、メール アプリであれば、応答、アーカイブ、フォルダーへ移動、フラグの設定、削除などのセカンダリ コマンドをコンテキスト メニューに表示しますが、最もよく使われるコマンドは削除とフラグの設定です。 最もよく使用されるコマンドを特定したら、入力ベースのアクセラレータを使用して、これらのコマンドをユーザーが実行しやすくできます。
ポッドキャスト アプリでは、頻繁に実行されるコマンドは「お気に入りに追加」コマンドです。
キーボード アクセラレータ
ショートカットと直接キーの処理
コンテンツの種類に応じて、操作を実行する特定のキーの組み合わせを明らかにします。 たとえば、メール アプリでは、選択されたメールの削除に Del キーが使用される可能性があります。 ポッドキャスト アプリでは、Ctrl + S や F キーによって、後で視聴するためにポッドキャストをお気に入りに追加する可能性があります。 Del キーで削除するなど、よく知られた一般的なキーボード ショートカットがあるコマンドもあれば、アプリまたはドメイン固有のショートカットがあるコマンドもあります。 できればよく知られているショートカットを使用してください。または、ヒントでリマインダー テキストを表示してショートカット コマンドをユーザーに伝えることを検討してください。
KeyDown イベントを使用することで、アプリはユーザーがキーを押したときに応答できます。 通常、ユーザーは、押したキーを放すときではなく、キーを最初に押したときにアプリが応答するものと考えます。
次の例では、KeyDown ハンドラーを PodcastUserControl に追加して、ユーザーが Ctrl + S または F キーを押したときにポッドキャストをお気に入りに追加する方法を示しています。このコードでは、前と同じコマンドを使用しています。
PodcastUserControl.xaml.cs
// Respond to the F and Ctrl+S keys to favorite the focused item.
protected override void OnKeyDown(KeyRoutedEventArgs e)
{
var ctrlState = CoreWindow.GetForCurrentThread().GetKeyState(VirtualKey.Control);
var isCtrlPressed = (ctrlState & CoreVirtualKeyStates.Down) == CoreVirtualKeyStates.Down || (ctrlState & CoreVirtualKeyStates.Locked) == CoreVirtualKeyStates.Locked;
if (e.Key == Windows.System.VirtualKey.F || (e.Key == Windows.System.VirtualKey.S && isCtrlPressed))
{
// Favorite the item using the defined command
var favoriteCommand = Application.Current.Resources["favoriteCommand"] as ICommand;
favoriteCommand.Execute(PodcastObject);
}
}
マウス アクセラレータ
右クリックのコンテキスト メニューはユーザーにとっておなじみですが、マウスのクリック 1 回で、よく使用されるコマンドを実行できるようにしても便利です。 このエクスペリエンスを実現するには、専用のボタンをコレクション項目のキャンバスに含めます。 ユーザーがマウスを使用してすばやく操作できるようにすると同時に、不要な表示をできる限りなくすには、特定のリスト項目内にポインターが置かれたときに、専用のボタンのみが表示されるようにすることができます。
次の例では、PodcastUserControl 内で直接定義したボタンによって、お気に入りに追加コマンドを提示しています。 なお、この例のボタンでも、以前と同じ FavoriteCommand コマンドを使用しています。 このボタンの表示と非表示を切り替えるには、VisualStateManager を使用して、ボタンの領域内にポインターが置かれたときと、領域からポインターが外れたときに、表示の状態を切り替えることができます。
PodcastUserControl.xaml
<UserControl>
<UserControl.ContextFlyout>
<!-- ... -->
</UserControl.ContextFlyout>
<Grid Margin="12,0,12,0">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="HoveringStates">
<VisualState x:Name="HoverButtonsShown">
<VisualState.Setters>
<Setter Target="hoverArea.Visibility" Value="Visible" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="HoverButtonsHidden" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel>
<TextBlock Text="{x:Bind PodcastObject.Title, Mode=OneWay}" Style="{StaticResource TitleTextBlockStyle}" />
<TextBlock Text="{x:Bind PodcastObject.Description, Mode=OneWay}" Style="{StaticResource SubtitleTextBlockStyle}" />
<TextBlock Text="{x:Bind PodcastObject.IsFavorite, Mode=OneWay}" Style="{StaticResource SubtitleTextBlockStyle}"/>
</StackPanel>
<Grid Grid.Column="1" x:Name="hoverArea" Visibility="Collapsed" VerticalAlignment="Stretch">
<AppBarButton Icon="OutlineStar" Label="Favorite" Command="{StaticResource favoriteCommand}" CommandParameter="{x:Bind PodcastObject, Mode=OneWay}" IsTabStop="False" VerticalAlignment="Stretch" />
</Grid>
</Grid>
</UserControl>
ホバー ボタンは、マウス ポインターが項目に重なったら表示し、項目から外れたら非表示にします。 マウス イベントに応答するには、PodcastUserControl で PointerEntered イベントと PointerExited イベントを使用します。
PodcastUserControl.xaml.cs
protected override void OnPointerEntered(PointerRoutedEventArgs e)
{
base.OnPointerEntered(e);
// Only show hover buttons when the user is using mouse or pen.
if (e.Pointer.PointerDeviceType == Windows.Devices.Input.PointerDeviceType.Mouse || e.Pointer.PointerDeviceType == Windows.Devices.Input.PointerDeviceType.Pen)
{
VisualStateManager.GoToState(this, "HoverButtonsShown", true);
}
}
protected override void OnPointerExited(PointerRoutedEventArgs e)
{
base.OnPointerExited(e);
VisualStateManager.GoToState(this, "HoverButtonsHidden", true);
}
ホバー状態で表示されたボタンは、ポインター入力でしかアクセスできません。 このボタンはポインター入力でしか操作できないため、ボタンのアイコンを囲む余白を最小限にするか完全になくして、ポインター入力向けに最適化することもできます。 これを行うには、ペンとマウスで操作できるように、ボタンのフットプリントを必ず 20 x 20 ピクセル以上にしてください。
タッチ アクセラレータ
読み取る
スワイプによるコマンド実行は、タッチ デバイスを操作しているユーザーが、よく使用されるセカンダリ操作をタッチを使用して実行できるようにするタッチ アクセラレータです。 スワイプはタッチ ユーザーが、スワイプして削除やスワイプして呼び出すなどの一般的な操作を使用して、コンテンツをすばやく自然に操作することを可能にします。 詳細については、スワイプによるコマンドの実行についての記事を参照してください。
スワイプをコレクションに統合するには、コマンドをホストする SwipeItems と、項目をラップしてスワイプ操作を可能にする SwipeControl という 2 つのコンポーネントが必要です。
SwipeItems は、PodcastUserControl のリソースとして定義できます。 次の例では、SwipeItems に、項目をお気に入りに追加するコマンドが含まれています。
<UserControl.Resources>
<SymbolIconSource x:Key="FavoriteIcon" Symbol="Favorite"/>
<SwipeItems x:Key="RevealOtherCommands" Mode="Reveal">
<SwipeItem IconSource="{StaticResource FavoriteIcon}" Text="Favorite" Background="Yellow" Invoked="SwipeItem_Invoked"/>
</SwipeItems>
</UserControl.Resources>
SwipeControl は、項目をラップし、ユーザーがスワイプ ジェスチャを使用して操作できるようにします。 SwipeControl には、RightItems として SwipeItems への参照が含まれていることに注意してください。 ユーザーが右から左へスワイプすると、[お気に入りに追加] が表示されます。
<SwipeControl x:Name="swipeContainer" RightItems="{StaticResource RevealOtherCommands}">
<!-- The visual state groups moved from the Grid to the SwipeControl, since the SwipeControl wraps the Grid. -->
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="HoveringStates">
<VisualState x:Name="HoverButtonsShown">
<VisualState.Setters>
<Setter Target="hoverArea.Visibility" Value="Visible" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="HoverButtonsHidden" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid Margin="12,0,12,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel>
<TextBlock Text="{x:Bind PodcastObject.Title, Mode=OneWay}" Style="{StaticResource TitleTextBlockStyle}" />
<TextBlock Text="{x:Bind PodcastObject.Description, Mode=OneWay}" Style="{StaticResource SubtitleTextBlockStyle}" />
<TextBlock Text="{x:Bind PodcastObject.IsFavorite, Mode=OneWay}" Style="{StaticResource SubtitleTextBlockStyle}"/>
</StackPanel>
<Grid Grid.Column="1" x:Name="hoverArea" Visibility="Collapsed" VerticalAlignment="Stretch">
<AppBarButton Icon="OutlineStar" Command="{StaticResource favoriteCommand}" CommandParameter="{x:Bind PodcastObject, Mode=OneWay}" IsTabStop="False" LabelPosition="Collapsed" VerticalAlignment="Stretch" />
</Grid>
</Grid>
</SwipeControl>
ユーザーがスワイプしてお気に入りに追加コマンドを呼び出すと、Invoked メソッドが呼び出されます。
private void SwipeItem_Invoked(SwipeItem sender, SwipeItemInvokedEventArgs args)
{
// Favorite the item using the defined command
var favoriteCommand = Application.Current.Resources["favoriteCommand"] as ICommand;
favoriteCommand.Execute(PodcastObject);
}
引っ張って更新
[引っ張って更新] を使用すると、タッチ操作でデータのコレクションを引き下げることで、より多くのデータを取得できます。 詳細については、引っ張って更新についての記事を参照してください。
ペン アクセラレータ
ペン入力は、精度の高いポインター入力を実現します。 ユーザーは、ペン ベースのアクセラレータを使用して、コンテキスト メニューを開くなどの一般的な操作を実行できます。 コンテキスト メニューを開くには、バレル ボタンを押して画面をタップするか、コンテンツを長押しします。 また、マウスと同様に、ペンを使用してコンテンツにポインターを重ねて、ヒントを表示するなど、UI についての理解を深めたり、セカンダリのホバー操作を表示したりすることもできます。
ペン入力用にアプリを最適化するには、ペン操作とスタイラス操作についての記事を参照してください。
推奨事項
- どの種類の Windows デバイスでも、ユーザーがすべてのコマンドにアクセスできるようにしてください。
- コレクション項目に対して使用できるすべてのコマンドにアクセスできるように、コンテキスト メニューを含めます。
- 頻繁に使われるコマンドについては、入力アクセラレータを提供します。
- コマンドを実装するには、ICommand インターフェイス を使用します。
関連トピック
Windows developer