#setlinebreak(on);
[[WP TIPS に戻る>wp7/tips]]
*動的にコンテキストメニューを構成し、ViewModel のコマンドをたたく [#r60004ab]
サンプルプロジェクト [[contextmenu_viewmodel.zip>https://skydrive.live.com/redir.aspx?cid=793b87c06d2f0cd5&resid=793B87C06D2F0CD5!1915&parid=793B87C06D2F0CD5!223]]
サンプルプロジェクト [[contextmenu_viewmodel.zip>https://skydrive.live.com/redir.aspx?cid=793b87c06d2f0cd5&resid=793B87C06D2F0CD5!1916&parid=793B87C06D2F0CD5!223]]
WP7 では Silverlight Toolkit 同梱の ContextMenu クラスを使用することで、コンテキストメニューを使用することが出来ます。
たとえば ListBox の項目を長押ししたらメニューを出す XAML は以下のようにします。
#ref(contextmenu-01.jpg,right,around,nolink);
<ListBox x:Name="listbox" ItemsSource="{Binding Items}">
<toolkit:ContextMenuService.ContextMenu>
<toolkit:ContextMenu>
<toolkit:MenuItem Header="Item1"/>
<toolkit:MenuItem Header="Item2"/>
<toolkit:MenuItem Header="Item3"/>
<toolkit:MenuItem Header="Item4"/>
<toolkit:MenuItem Header="Item5"/>
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
</ListBox>
お分かりと思いますが、この方法には ''表示するメニューが固定される ( 選択された項目によって表示するメニューを変えられない)'' という大きな問題があります。
実際には表示したい項目やソフトの状態によっては「削除」だったり、「名前の変更」だったり「最新の情報に更新」だったりするわけなので、この状態では具合が悪いです。
※また ListBox に 100+ の項目を表示して上記の方法でコンテキストメニューを表示すると、MenuItem.CommandParameter に表示しているアイテムを渡すとずれるという問題があります。(100番目の項目を選択したはずなのに、実際には 10番目が渡ってくるなど)
**動的にメニューを構成する [#ec182299]
そこでコンテキストメニューを動的に構成し表示するようにしてみましょう。
ではどうやってメニューを作成するか?そうですコンバータです。普段は bool→Visibility などで活躍するコンバータを、ItemsSource→IEnumerable<MenuItem> 変換に使用します。
***XAML [#g8799536]
まず ContextMenu には ItemsSource プロパティがありますので、ここに MenuItem クラスのコレクションを入れてやればコンテキストメニューが開いたときの項目になります。
XAML 的にはこんな感じになります(コンバータと ListBox.ItemTemplate 部分を抜粋)。
<phone:PhoneApplicationPage.Resources>
<local:ContextMenuConverter x:Key="ContextMenuConverter"/>
<DataTemplate x:Key="StringTemplate">
<Border>
<Grid >
<TextBlock Text="{Binding Index}"/>
<TextBlock Text="{Binding Name}"/>
<toolkit:ContextMenuService.ContextMenu>
<toolkit:ContextMenu ItemsSource="{Binding Converter={StaticResource ContextMenuConverter}}"/>
</toolkit:ContextMenuService.ContextMenu>
</Grid>
</Border>
</DataTemplate>
</phone:PhoneApplicationPage.Resources>
通常ならコンテキストメニューは ListBox 直下として、<ListBox><toolkit:ContextMenuService.ContextMenu>~</ListBox> するのですが、ここでは ListBox に表示される項目のテンプレートの配下にしています。
これは後に解説する Command 実行に関連します。
***コンバータ [#rc95d561]
コンバータはこんな感じです。
value に入ってくるのが ListBox に表示されているアイテム(ここでは DataClass) で、返すのが 表示する MenuItem のリストになります。
※コンテキストメニューは ListBox 直下としない理由がここにあります。ListBox 直下にすると value に DataContext(ViewModel) が渡ってきてしまい、選択された項目が来ないので都合が悪いのです。
public class ContextMenuConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
// 選択されたアイテムを取得する
DataClass c = value as DataClass;
// メニュー項目用リストを準備
List<MenuItem> tmpList = new List<MenuItem>();
// メニュー項目作成
// それぞれの項目に合わせていろいろやる。
MenuItem i = new MenuItem
{
Header = c.Name + " の詳細を開く",
};
tmpList.Add(i);
// 返す
return tmpList;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
これでコンテキストメニューを長押しすると、コンバータの Convert メソッドが呼ばれて MenuItem のリストを返すことで、動的にコンテキストメニュー項目を変更・表示する事が出来ます。
***コンテキストメニューから ViewModel のコマンドをたたく [#y11cf6be]
ここまででコンテキストメニューの動的構成が出来るようになったのですが、このままでは選択できるだけで何も実行することが出来ません。
次にコンテキストメニューから ViewModel のコマンドを実行してみましょう。
まず実装方針は2つあります。
+ViewModel に IValueConverter を実装し、ViewModel にコンバータを兼ねさせる
+ViewModel と コンバータは別のソース(クラス) にし、コンバータに ViewModel を持たせる
まず最初の実装ですが、これは簡単ですね。
サンプルの ViewModel.cs がそのようになっていますので、参照してください。
次のコンバータに ViewModel を持たせる方法ですが、まずコンバータのプロパティに ViewModel を追加して、MenuItem 生成時に Command と CommandParameter を設定してやります。
public class ContextMenuConverter : IValueConverter
{
public ViewModel viewModel { get; set; }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
// 選択されたアイテムを取得する
DataClass c = value as DataClass;
// メニュー項目用リストを準備
List<MenuItem> tmpList = new List<MenuItem>();
// メニュー項目作成
MenuItem i = new MenuItem
{
Header = c.Name + " の詳細を開く",
Command = this.viewModel.HeaderClickCommand,
CommandParameter = value,
};
tmpList.Add(i);
// 返す
return tmpList;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
次に XAML で、Converter 定義で viewModel プロパティに ViewModel を設定します。
''必ず ViewModel の定義の後にコンバータを定義してください。''そうしないとコンバータに設定される ViewModel が null になってしまいます。
<phone:PhoneApplicationPage.Resources>
<local:ViewModel x:Key="ViewModel" d:IsDataSource="True"/>
<local:ContextMenuConverter viewModel="{StaticResource ViewModel}" x:Key="ContextMenuConverter"/>
***サンプルについて [#z578875f]
サンプルでは上記の実装方針両方とも実装してありますが、デフォルトは ViewModel にコンバータを兼用させているものです。
ViewModel とコンバータを分ける方を実行する場合は、
-変更前
<toolkit:ContextMenu ItemsSource="{Binding Converter={StaticResource ViewModel}}"/>
-変更後
<toolkit:ContextMenu ItemsSource="{Binding Converter={StaticResource ContextMenuConverter}}"/>
としてください。