Windows Phone ToolkitのContextMenuのサンプルコードでは
#!XML
<toolkit:ContextMenuService.ContextMenu>
<toolkit:ContextMenu>
<toolkit:MenuItem Header="Item1"/>
<toolkit:MenuItem Header="Item2"/>
<toolkit:MenuItem Header="Item3"/>
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
というように、MenuItemで明示的にメニュー項目を定義している場合をよく見ます。
今回、以下の条件を満たすためにContextMenuのItemsSourceを使う方法を試していましたが、 すっかりはまっていたので、その過程をまとめてみます。
- ListBoxの各要素をTap&Holdすることでコンテキストメニューを表示させる
- 複数の画面から利用されるため、ListBoxと付随するコントロールをUserControlとしてまとめる
- コンテキストメニューを画面によって変更する必要がある
以下では、話を単純にするために、ListBoxだけを含むWindowsPhoneページで説明していきます。
1st Step) 単純にMenuItemのリストをItemsSourceにバインド
まず初めに考えたのがContextMenuを構成するMenuItemをViewModelで定義して、 そのリストをContextMenu.ItemsSourceに設定するもの。コードは以下のようになります。
■XAML
#!XML
<phone:PhoneApplicationPage
x:Class="WP7ContextMenuWithMenuItem.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
shell:SystemTray.IsVisible="True">
<Grid x:Name="LayoutRoot" Background="Transparent">
<ListBox Grid.Row="0" x:Name="ListBox" ItemsSource="{Binding ListItems}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Margin="0" TextWrapping="Wrap" Text="{Binding Text}" Style="{StaticResource PhoneTextLargeStyle}">
<toolkit:ContextMenuService.ContextMenu>
<toolkit:ContextMenu ItemsSource="{Binding MenuItems}" IsZoomEnabled="False"/>
</toolkit:ContextMenuService.ContextMenu>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</phone:PhoneApplicationPage>
■コードビハインド&ViewModel
#!C#
public partial class MainPage : PhoneApplicationPage
{
public MainPage()
{
InitializeComponent();
LayoutRoot.DataContext = new ViewModel();
}
}
public class ViewModel
{
public ObservableCollection<ListItem> ListItems { get; private set; }
public ViewModel()
{
this.ListItems = new ObservableCollection<ListItem>(){
new ListItem() { Text="Item 1", MenuItems=this.CreateMenuDefs(), },
new ListItem() { Text="Item 2", MenuItems=this.CreateMenuDefs(), },
new ListItem() { Text="Item 3", MenuItems=this.CreateMenuDefs(), },
};
}
private ObservableCollection<MenuItem> CreateMenuDefs()
{
ObservableCollection<MenuItem> menuDefs
= new ObservableCollection<MenuItem>() {
new MenuItem() { Header="Menu 1" },
new MenuItem() { Header="Menu 2" },
};
foreach (var item in menuDefs)
{
// イベントハンドラをここで設定する
item.Click += new RoutedEventHandler(MenuItem_Click_VM);
}
return menuDefs;
}
private void MenuItem_Click_VM(object sender, RoutedEventArgs e)
{
MenuItem menuItem = sender as MenuItem;
return;
}
}
public class ListItem
{
public string Text { get; set; }
public ObservableCollection<MenuItem> MenuItems { get; set; }
}
補足
ItemsSourceにバインドしたMenuItemsですが、ItemTemplateのなかで定義しているため、 ListBoxのItemsSourceにバインドしたリストの要素、つまりListItemクラスに定義する必要があります。
実行結果
ViewModelで定義したコンテキストメニューが表示されて、項目を選択するとイベントハンドラが呼ばれました。
ただ、ViewModelにコントロールであるMenuItemが入り込むため、 ViewModelとViewの分離の観点からこの方法はよくないと考えています。
このため、ContextMenu.ItemsSourceにはPOCOのリストを設定することができないか試してみました。 その過程・結果は次の記事に回します。