読者です 読者をやめる 読者になる 読者になる

熊小屋日誌

Windows 10 UWPやXamarin, Python、mbed/NetMF/Arduino/Edison, Azureなどぼちぼちと。たまにPCや勉強会、セミナーなどの話題も

ContextMenuへのバインディング(1of3)

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だけを含む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で定義したコンテキストメニューが表示されて、項目を選択するとイベントハンドラが呼ばれました。

f:id:kumar:20151013114736p:plain

ただ、ViewModelにコントロールであるMenuItemが入り込むため、 ViewModelとViewの分離の観点からこの方法はよくないと考えています。

このため、ContextMenu.ItemsSourceにはPOCOのリストを設定することができないか試してみました。 その過程・結果は次の記事に回します。