Deep Insider の Tutor コーナー
>>  Deep Insider は本サイトからスピンオフした姉妹サイトです。よろしく! 
Xamarin逆引きTips

Xamarin逆引きTips

Xamarin.FormsでListViewのコンテキストアクションを使用するには?

2015年4月1日

リストの1つをスライド(iOS)もしくは長押し(Android)されたらメニューを表示する「コンテキストアクション」の基本的な使い方を説明する。

古谷 誠進(@furuya02
  • このエントリーをはてなブックマークに追加

 Xamarin.Formsのバージョン1.3以降で追加された機能の1つにListViewコンテキストアクションがある。コンテキストアクションを使用すると、リストの一つをスライド(Androidでは長押し)することでメニューを表示させることができる。

 今回は、このListViewビュー(=コントロール)のコンテキストアクションの利用方法について解説する。

  • *1 なお本Tipsは、Windows上でVisual Studio 2013を使用してXamarin.Forms開発をすることを前提としている(編集部注: Mac上のXamarin Studioでも同様の手順で、本稿の内容が実現できることは確認している)。使用しているXamarin.Formsのバージョンは、プロジェクト作成時に利用されている「1.3.1.6296」である。

1. シナリオ

 最初に、簡単なテキストのみを表示するリストビューの画面を作成する。そして、そのリストビューにコンテキストアクションを追加する方法について解説する。

 また、応用として、コンテキストアクションから、リストの項目を操作する例についても紹介する。

2. Xamarin.Formsプロジェクトを作成する

 メニューバーの[ファイル]-[新規作成]-[プロジェクト]から表示したダイアログで、[テンプレート]-[Visual C#]-[Mobile Apps]-[Blank App (Xamarin.Forms Portable)]を選択し、名前を「ContextActionsSample」として[OK]ボタンを押す。

図1 「Blank App (Xamarin.Forms Portable)」の新規作成

3. リストビューの表示

 最初に、コンテキストアクションを追加するためにリストビューを作成する。

 テキストのみが表示されたリストビューを表示するには、App.csファイルを以下のように修正する。

C#
using System.Linq;
using Xamarin.Forms;

namespace ContextActionsSample {
  public class App : Application {
    public App() {
      MainPage = new MyPage();
    }

    ……省略……
  }

  class MyPage : ContentPage {
    public MyPage() {
      var listView = new ListView {  // <-1
        ItemsSource = Enumerable.Range(0, 50).Select(n => "item-" + n), // <-2
      };
      Padding = new Thickness(0, Device.OnPlatform(20, 0, 0), 0, 0);
      Content = listView;           // <-3
    }
  }

}
「item-0」~「item-50」というテキストが並べたリストビューを表示する画面のコード(App.cs)

 ListViewコントロール(1)を生成し、ItemsSourceプロパティに0から始まって50個の文字列配列を渡している(2)。

 ListViewは、定義したメイン画面(=MyPage)で唯一のコントロールであり、画面全体を占めることになる(3)。

 このコードを実行すると次のような画面になる。

図2 テキスト表示のリストビューを配置した画面(Android) 図2 テキスト表示のリストビューを配置した画面(iOS)
図2 テキスト表示のリストビューを配置した画面(Android/iOS)

4. データテンプレート

 リストビューのコンテキストアクションは、各セルの描画を担任するViewCellオブジェクトのプロパティとして追加されるため、データテンプレートの実装が必須となる。

 先のコードでは、データテンプレートを指定していないため、デフォルトの動作としてカスタムセルのTextCellオブジェクトが動作していた。ここでは、このTextCellオブジェクトに相当するテンプレートをあらためて実装することにする。

 データテンプレートを指定するには、App.csファイルを以下のように修正する。

C#
……省略……

namespace ContextActionsSample {
  ……省略……

  class MyPage : ContentPage {
    public MyPage() {
      var listView = new ListView {
        ItemsSource = Enumerable.Range(0, 50).Select(n => "item-" + n),
        ItemTemplate = new DataTemplate(() => new MyCell(this)), // <-1
      };
      Padding = new Thickness(0, Device.OnPlatform(20, 0, 0), 0, 0);
      Content = listView;
    }
  }

  class MyCell : ViewCell {     // <-2
    public MyCell(MyPage myPage) {
      var label = new Label {   // <-3
        VerticalOptions = LayoutOptions.CenterAndExpand,
      };
      label.SetBinding(Label.TextProperty, new Binding("."));    // <-4

      View = new StackLayout {  // <-5
        Padding = 10,
        Children = { label }
      };
    }
  }
}
データテンプレートを実装したコード(App.cs)

 まずは、ViewCellクラスを継承した、テンプレートクラス(ここではMyCellとした)を定義する(2)。

 MyCellでは、Labelコントロールを1つ生成し(3)、その表示内容をカレントデータにバインドした(4)。

 なお、Labelコントロールは、TextCellをまねて、パディング(Padding)を少し取って表示した(5)。

 最終的に、定義したMyCellクラスのインスタンスは、ListViewItemTemplateプロパティにセットされている(1)。

 現時点では、このコードを実行しても、表示は以前と変わらない。

5. コンテキストアクション

 続いて、このMyCellクラスに、コンテキストアクションを追加していく。

 コンテキストアクションを追加するには、App.csファイルを以下のように修正する。

C#
……省略……

namespace ContextActionsSample {
  ……省略……

  class MyCell : ViewCell {
    public MyCell(MyPage myPage) {
      ……省略……

      var actionDelete = new MenuItem {       // <-1
        Text = "Delete",
        Command = new Command(p => myPage.DisplayAlert("Delete",p.ToString(),"OK")), // <-2
        IsDestructive = true,                 // <-3
      };
      actionDelete.SetBinding(MenuItem.CommandParameterProperty, new Binding("."));  // <-4
      ContextActions.Add(actionDelete);       // <-5

      var actionAdd = new MenuItem {          // <-6
        Text = "Add",
      };
      actionAdd.SetBinding(MenuItem.CommandParameterProperty, new Binding("."));     // <-7
      actionAdd.Clicked += async (s, e) => {  // <-8
        var itemMenu = ((MenuItem) s);
        await myPage.DisplayAlert(itemMenu.Text, (string)itemMenu.CommandParameter, "OK");
      };
      ContextActions.Add(actionAdd);          // <-9

      View = new StackLayout {
        Padding = 10,
        Children = { label }
      };
    }
  }
}
コンテキストアクションを追加したコード(App.cs)

 コンテキストアクションの実体は、メニューアイテムである。ここでは、例として2つのアクションを追加するため、MenuItemクラスのインスタンスを2つ生成している(16)。

 MenuItemオブジェクトのIsDestructiveプロパティをtrueにセットすることで(3)、メニューアイテムの表示が赤色になる(ただし、iOSのみ)。

 コンテキストメニューを選択した際の動作については、2つの実装方法がある。その1つ目は、MenuItemオブジェクトのCommandプロパティにCommandクラスのインスタンスを指定する方法である(2)。そして2つ目は、MenuItemオブジェクトのClickedイベントに追加する方法である(8)。場面に応じて使い分けてほしい。

 最後に、生成したMenuItemオブジェクトは、ViewCellクラス(を継承したMyCellクラスのインスタンス)のContextActionsプロパティに追加することで有効となる(59)。

 なお、アクション発生時に、パラメーターとして表示されているテキストを受け取るため、MenuItemクラスのCommandParameterPropertyフィールドに、カレントデータをバインドしている(4)。

 このコードを実行すると次のような画面になる。

図3 コンテキストアクションを追加した画面(Android)-1 図3 コンテキストアクションを追加した画面(Android)-2 図3 コンテキストアクションを追加した画面(iOS)-1 図3 コンテキストアクションを追加した画面(iOS)-2
図3 コンテキストアクションを追加した画面(Android/iOS)

【コラム】Windows Phoneのコンテキストアクション

 Xamarin.Formsは、Visual Studioで使用する場合、Windows Phoneのプロジェクトも同時に作成可能だが、今回紹介しているコンテキストアクションは、Windows Phoneでは、図4のように表示される。なお、Windows Phoneの場合も、Androidと同じく長押しによりコンテキストアクションが実行される。

図4 Windows Phoneにおけるコンテキストアクション(1) 図4 Windows Phoneにおけるコンテキストアクション(2)
図4 Windows Phoneにおけるコンテキストアクション

6. アイテムの操作

 最後に、応用として、コンテキストアクションに応じて、リストのアイテムを操作する方法を解説する。

 ここまでに作成したサンプルにおける[Add]メニューでリストのアイテムを追加、[Delete]メニューで削除となるように実装するには、App.csファイルを以下のように修正する。

C#
using System.Collections.ObjectModel;
……省略……

namespace ContextActionsSample {
  ……省略……

  class MyPage : ContentPage {

    private ObservableCollection<string> _ar = new ObservableCollection<string>(Enumerable.Range(0, 50).Select(n => "item-" + n)); // <-1

    public MyPage()
    {
      var listView = new ListView{
        //ItemsSource = Enumerable.Range(0, 50).Select(n => "item-" + n),
        ItemsSource = _ar, // <-2
        ItemTemplate = new DataTemplate(() => new MyCell(this)),
      };
      Padding = new Thickness(0, Device.OnPlatform(20, 0, 0), 0, 0);
      Content = listView;
    }

    public async void Action(MenuItem item) {      // <-3
      var text = item.CommandParameter.ToString(); // <-4
      if (item.Text == "Add") {                    // <-5
        _ar.Insert(_ar.IndexOf(text) + 1, text + "-Add");
      } else if (item.Text == "Delete") {            // <-6
        _ar.RemoveAt(_ar.IndexOf(text));
      }
    }

  }

  class MyCell : ViewCell{
    public MyCell(MyPage myPage){
      ……省略……

      var actionDelete = new MenuItem{
        Text = "Delete",
        //Command = new Command(p => myPage.DisplayAlert("Delete",p.ToString(),"OK")),
        IsDestructive = true, 
      };
      actionDelete.SetBinding(MenuItem.CommandParameterProperty, new Binding("."));
      actionDelete.Clicked += (s, a) => myPage.Action((MenuItem)s);  // <-7
      ContextActions.Add(actionDelete);

      var actionAdd = new MenuItem{
        Text = "Add",
      };
      actionAdd.SetBinding(MenuItem.CommandParameterProperty, new Binding("."));
      //actionAdd.Clicked += async (s, e) => {
      //  var itemMenu = ((MenuItem) s);
      //  await myPage.DisplayAlert(itemMenu.Text, (string)itemMenu.CommandParameter, "OK");
      //};
      actionAdd.Clicked+=(s, a) => myPage.Action((MenuItem)s);       // <-8
      ContextActions.Add(actionAdd);


      ……省略……
    }
  }
}
アクションメニューからリストのアイテムを操作するコード(App.cs)

 ItemsSourceプロパティに指定していたリストのデータは、操作が可能になるようクラス変数に変更し、ObservableCollectionクラスのインスタンスに変更した(12)。

 また、MyPageクラスでは、コンテキストアクションに応じて、リストを操作するActionメソッドを新たに定義した(3)。Actionメソッドは、MenuItemオブジェクトを引数に取るが、このMenuItemオブジェクトのTextプロパティの値がアクションへの表示文字列(本稿の例では[Add]や[Delete])と等しいかを確認し(56)、アクション別にCommandParameterプロパティの値がリストの表示文字列(4)と一致しているものを対象・基準として、そのリストビューのデータを操作している。

 一方、MyCellクラスの方では、先の例で使用した、Commandプロパティへの指定やClickedイベントへの処理をいったん削除し、新たに、Antcionメソッドを呼び出すコードに修正した(78)。

 このコードを実行すると次のような画面になる。

図5 コンテキストアクションでリストのアイテム操作した画面(Android) 図5 コンテキストアクションでリストのアイテム操作した画面(iOS)
図5 コンテキストアクションでリストのアイテム操作した画面(Android/iOS)

7. まとめ

 今回は、リストビューにコンテキストメニューを追加する方法について解説した。実行画面を見て分かる通り、Xamarin.Formsのコンテキストアクションは、表示も呼び出し方法もプラットフォームごとに大きく異なるので、使用に関してはよく検討する必要がありそうだ。

【お知らせ】Japan Xamarin User Group

 Japan Xamarin User Group(以下JXUG)というのをご存じだろうか。JXUGでは、Xamarinに関する、最新情報の提供や、情報交換などが行われている。また、JXUG Conferenceと称して、定期的にカンファレンスも行われている。

 そして次のカンファレンスは、dotNetConf 2015 Japan with JXUGと合同で、2015年4月11日(土)の午後に品川(日本マイクロソフトの品川オフィス・セミナールーム)で開催される。今回は、筆者もXamarin.Formsに関する話をさせていただく予定である。Xamarinの生の情報を得るためにも良い機会になると思うので、時間の許す方は、ぜひ参加してみてほしい。

【過去の開催の様子】

※以下では、本稿の前後を合わせて5回分(第42回~第46回)のみ表示しています。
 連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。

Xamarin逆引きTips
42. Xamarin.Formsでビヘイビアーを使用するには?

サブクラス化することなく、UIコントロールに機能を追加できる「ビヘイビアー」の基本的な使い方を説明する。

Xamarin逆引きTips
43. MvvmCrossでコマンドバインディングをするには?

MvvmCrossでは、画面でのイベント発生をViewModelに通知するためにコマンドバインディングを使用する。iOS/Androidにおける、その基本的な実装方法を説明する。

Xamarin逆引きTips
44. 【現在、表示中】≫ Xamarin.FormsでListViewのコンテキストアクションを使用するには?

リストの1つをスライド(iOS)もしくは長押し(Android)されたらメニューを表示する「コンテキストアクション」の基本的な使い方を説明する。

Xamarin逆引きTips
45. Xamarin.Formsでトリガーを使用するには?

イベントやプロパティの変化に応じたコントロールの外観の変更をXAMLだけで実装できるトリガーの基本的な使用方法を解説する。

Xamarin逆引きTips
46. Xamarin.FormsでWebビューを使用するには?

外部のWebページやローカルに配置されたHTMLコンテンツを簡単に表示できるWebViewコントロールをXamarin.Formsで使う方法を説明する。

サイトからのお知らせ

Twitterでつぶやこう!