酢ろぐ!

カレーが嫌いなスマートフォンアプリプログラマのブログ。

Windows PhoneでContextMenuServiceを使ってコンテキストメニューで選択したアイテムを取得する

Windows Phone Advent Calendar "ひとり" 2011」第5日目です。

iOSの場合リストに並んでいる特定のアイテムを削除したい時にどうするでしょうか。左から右へスワイプすると削除ボタンが表示されます。削除ボタンをタップすることで2段階のプロセスを経てアイテムの削除を行います。

Windows Phoneで、同様にリストに並んでいる特定のアイテムを削除したい場合は、まずListBoxのアイテムをホールドし、コンテキストメニューを表示し、メニュー一覧に表示される削除を選択するのが一般的な方法です。

本日は、コンテキストメニューを表示させる方法をご紹介します。その後にコンテキストメニューの「削除」を選択した時に、どのアイテムに対して行われたのかを知りたい場合があります。実際にどのアイテムが表示されたのかを調べる方法もご紹介したいと思います。

-(2011/12/5 13:30追記)id:tmytがContextMenuは中華フォント問題が残ると教えてくれたのでリンクを貼っておきます。 ContextMenuの中華フォント問題 - tmytのらくがき 日本語向きアプリを作っているのであれば要チェックです!


**プロジェクトの作成

新しいプロジェクトを作成します。テンプレートから「Windows Phone データバインド アプリケーション」を選択します。プロジェクトの名前は「ContextServiceTest」とします。

作成したプロジェクトでContextMenuServiceクラスを使用するために、Silverlight for Windows Phone Toolkitのアセンブリへの参照追加します。

ソリューションエクスプローラーからContextServiceTestプロジェクトの参照設定を右クリックして、参照の追加を選択します。

ダイアログが表示されますので、その中からMicrosoft.Phone.Controls.Toolkitを選択してOKボタンをクリックします。以上でContextMenuServiceを使う準備は終わりです。

***XAML

MainPage.xamlに追記を行います。まずXAMLファイルのルート要素内でxmlns宣言を追加します。

|xml| <phone:PhoneApplicationPage x:Class="ContextServiceTest.MainPage" 〜(中略)〜 xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit" ||<

「xmlns:toolkit="clr-namespace:Namespace;assembly=Assembly"」のフォーマットでxmlns宣言を追加します。XML名前空間をアセンブリまたはアセンブリ内のCLR名前空間に割り当てることが出来ます。アセンブリと CLR 名前空間を明示的に宣言する必要があります。

プレフィックスを割り当てることで、アプリケーションからSilverlight for Windows Phone Toolkitのコントロールや型を参照することが出来るようになります。

ContextMenuServiceの場合で例に取ると以下のように要素を宣言することが出来ます。

|xml| <toolkit:ContextMenuService.ContextMenu> ||<

これは、toolkit名前空間(Microsoft.Phone.Controls.ToolkitアセンブリのMicrosoft.Phone.Controls名前空間)のContextMenuServiceクラスのContextMenuプロパティを指します。

さて次にContextMenuをListBoxのItemTemplateに追加してみましょう。ListBoxのアイテムは、StackPanelに含まれるTextBlockが2つで構成されています。StackPanelをホールドした場合にコンテキストメニューを表示するように定義します。

|xml| <ListBox.ItemTemplate>

    <!-- コンテキストメニューの定義 -->
    <toolkit:ContextMenuService.ContextMenu>
      <toolkit:ContextMenu IsZoomEnabled="True">
        <toolkit:MenuItem Header="Delete" Click="MenuItem_Click" />
      </toolkit:ContextMenu>
    </toolkit:ContextMenuService.ContextMenu>

    <TextBlock Text="{Binding LineOne}" TextWrapping="Wrap" 
      Style="{StaticResource PhoneTextExtraLargeStyle}"/>
    <TextBlock Text="{Binding LineTwo}" TextWrapping="Wrap" 
      Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
  </StackPanel>
</DataTemplate>

</ListBox.ItemTemplate> ||<

ホールドしてコンテキストメニューを表示させてみました。

不要だと思いますが、MainPage.xamlの全体のXAMLを掲載しておきます。

|xml| <phone:PhoneApplicationPage x:Class="ContextServiceTest.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:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit" mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768" d:DataContext="{d:DesignData SampleData/MainViewModelSampleData.xaml}" FontFamily="{StaticResource PhoneFontFamilyNormal}" FontSize="{StaticResource PhoneFontSizeNormal}" Foreground="{StaticResource PhoneForegroundBrush}" SupportedOrientations="Portrait" Orientation="Portrait" shell:SystemTray.IsVisible="True">

<Grid x:Name="LayoutRoot" Background="Transparent">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>

    <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
        <TextBlock x:Name="ApplicationTitle" Text="SOFTBUILD" Style="{StaticResource PhoneTextNormalStyle}"/>
        <TextBlock x:Name="PageTitle" Text="ContextServiceTest" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
    </StackPanel>

    <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
        <ListBox x:Name="MainListBox" Margin="0,0,-12,0" ItemsSource="{Binding Items}" SelectionChanged="MainListBox_SelectionChanged">
            <ListBox.ItemTemplate>
                <DataTemplate>
                  <StackPanel Margin="0,0,0,17" Width="432" Height="78">

                        <!-- コンテキストメニューの定義 -->
                        <toolkit:ContextMenuService.ContextMenu>
                            <toolkit:ContextMenu IsZoomEnabled="True">
                                <toolkit:MenuItem Header="Delete" 
                                    Click="MenuItem_Click" />
                            </toolkit:ContextMenu>
                        </toolkit:ContextMenuService.ContextMenu>

                        <TextBlock Text="{Binding LineOne}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
                      <TextBlock Text="{Binding LineTwo}" TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
                  </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Grid>

</phone:PhoneApplicationPage> ||<

以上でXAML側の対応は終わりです。

***C#

コード側です。コンテキストメニューに表示されるアイテムをタップするとMenuItem_Clickメソッドが呼ばれます。

イベントの発生元インスタンスがMenuItemクラスであるかどうかをチェックし、変数elementにキャストを行っています。elementのDataContextプロパティからバインディングされているアイテムが参照できるので、ItemViewModel型にキャストしています。

ItemViewModelの部分は、ListBox.ItemsSourceにバインディングされている型を指定してください。Windows Phone データバインド アプリケーションはMainPageのListBoxのアイテムにはItemViewModelでバインディングされています。

|cs| private void MenuItem_Click(object sender, RoutedEventArgs e) { var element = (sender as MenuItem); if (element == null) return;

// NOTE: ItemViewModelの部分はListBox.ItemsSourceに
// バインディングされている型を指定します
var item = element.DataContext as ItemViewModel;
if (item == null) return;

// TODO: 分離ストレージやLinq to SQLで削除の処理を行う

} ||<

以上で、ContextMenuServiceを使ってコンテキストメニューで選択したアイテムを取得する方法は終わりです。

**選択項目以外をズームアウトさせない方法

ContextMenuのIsZoomEnabledプロパティをTrueに設定している場合、任意のアイテムをホールドすると選択されたアイテム以外がズームアウトします

IsZoomEnabledをFalseに設定していると、そのままの状態でコンテキストメニューが表示されます。

|xml| <toolkit:ContextMenu IsZoomEnabled="False"> ||<

ランタイム3をホールドしてコンテキストメニューを表示してみました。

先ほどのスクリーンショットと違い選択項目以外がズームアウトしていないのが分かります。