#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}}"/>

としてください。