Xamarin.Forms の MasterDetail を実装してみよう

## 【更新】Visual Studio 2017 の場合

- `MasterDetailPage` とその他諸々をソリューションに追加してくれるテンプレート **Forms MasterDetail Page Xaml** が VS2017 で追加されました。
- 説明は以上です。ありがとうございました :smile:
- 【注意】VS2017 のプロジェクト テンプレート **クロスプラットフォーム アプリ (Xamarin.Forms またはネイティブ)** で **マスター/詳細** を選択しても `MasterDetailPage` クラスは使用されません。 ([Bug 53141](https://bugzilla.xamarin.com/show_bug.cgi?id=53141))
 
---

## 方針

- Xamarin 公式ページになるべく従って
- C# コードではなく xaml の方で
- Droid と iOS
- 私が引っかかったところを補足していきます
- 自分で作るクラスがどれか分かりにくいので ``My`` を付けていきます

---

## 公式

[developer.xamarin.com/guides/xamarin-forms/user-interface/navigation/master-detail-page/](https://developer.xamarin.com/guides/xamarin-forms/user-interface/navigation/master-detail-page/)

---

## 環境

- Visual Studio 2015 update 3
- Xamarin 4.2.x.x

---

## プロジェクト

- **Blank Xaml App (Xamarin.Forms Portable)** `MyApp` を作成
- **Windows** と **WinPhone** プロジェクトを削除（ゴメンナサイ）

---

## 最初のビルド

*Welcome to Xamarin Forms!*

---

## MyMasterDetailPage.xaml

**Forms Xaml Page** `MyMasterDetailPage` を追加

---

```xml
<?xml version="1.0" encoding="utf-8" ?>
<MasterDetailPage xmlns="http://xamarin.com/schemas/2014/forms"
                  xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                  xmlns:local="clr-namespace:MyApp;assembly=MyApp"
                  x:Class="MyApp.MyMasterDetailPage">
  <MasterDetailPage.Master>
    <local:MyMasterPage />
  </MasterDetailPage.Master>
  <MasterDetailPage.Detail>
    <NavigationPage>
      <x:Arguments>
        <local:MainPage />
      </x:Arguments>
    </NavigationPage>
  </MasterDetailPage.Detail>
</MasterDetailPage>
```

---

- .cs で派生親を `ContentPage` から `MasterDetailPage` に変更するのを忘れないこと
- **MasterDetailPage** を作るテンプレートがあってもいいのに
- デザインの情報がほとんど無いのに xaml で作るのって回りくどくないですか...
- Detail 役は `MainPage` （*Welcome to Xamarin Forms!* のページ）で済ませました
- Master 役はこのあと作ります

---

## MyMasterPage.xaml

- **Forms Xaml Page** `MyMasterPage` を追加

---

```xml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MyApp.MyMasterPage"
             Padding="0,40,0,0"
             Icon="hanburger.png"
             Title="メニュー">
  <StackLayout VerticalOptions="FillAndExpand">
    <ListView x:Name="listView" VerticalOptions="FillAndExpand" SeparatorVisibility="None">
      <ListView.ItemTemplate>
        <DataTemplate>
          <ImageCell Text="{Binding Title}" ImageSource="{Binding IconSource}" />
        </DataTemplate>
      </ListView.ItemTemplate>
    </ListView>
  </StackLayout>
</ContentPage>
```

---

- Master 役
- 派生親は `ContentPage` のままで OK
- `hamnurger.png` を **Sample Code** から落としてきて Droid と iOS の両方に追加する
- `Padding="0,40,0,0"` をサボると1番上のアイテムが画面上部の OS の操作と干渉する
- `ContentPage.Content` は省略できると思います

---

### MyMasterPage.xaml.cs

---

```c#
using System.Collections.Generic;
using Xamarin.Forms;

namespace MyApp
{
  public partial class MyMasterPage : ContentPage
  {
    public MyMasterPage()
    {
      InitializeComponent();

      var myMasterPageItems = new List<MyMasterPageItem>();
      myMasterPageItems.Add(new MyMasterPageItem
      {
        Title = "ようこそ",
        IconSource = "icon.png",
        TargetType = typeof(MainPage)
      });
      listView.ItemsSource = myMasterPageItems;
    }
  }
}

```

---

- リストの中身を準備するところ
- 画像は Droid にある icon.png でゴマカす（iOS 側にも icon.png を追加する）
- `x:Name="listView"` と書けば .cs で `listView` が使えるようになるのはうれしい
- `MyMasterPageItem` はこのあと作る

---

## MyMasterPageItem.cs

- **クラス** `MyMasterPageItem` を追加
- **Sample Code** の **MasterPageItem.cs** からメンバーの定義をコピペ

---

```c#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyApp
{
  public class MyMasterPageItem
  {
    public string Title { get; set; }
    public string IconSource { get; set; }
    public Type TargetType { get; set; }
  }
}
```

---

- ListView のアイテム
- public にするのを忘れない
- 自分で作らないといけないクラスなのに説明が無いのはちょっと不親切では...
- `Type` 型とかスラスラ出てきません...

---

## App.xaml.cs

- 最初に開く画面を変える
- これを変えないと *Welcome to Xamarin Forms!* のまま

---

```c#
    public App()
    {
      InitializeComponent();

      //MainPage = new MyApp.MainPage();
      MainPage = new MyMasterDetailPage();
    }
```

---

## 一旦ビルド

- ここで一旦ビルドが通る
- 実行すると Master は出てくるけど、タップしても何も起きない状態

---

## タップの実装
### MyMasterPage.xaml.cs

- Master の ListView を公開

---

```c#
  public partial class MyMasterPage : ContentPage
  {
    public ListView ListView { get { return listView; } }
    ...
```

---

### MyMasterDetailPage.xaml

- Master に名前を付ける

---

```xml
<MasterDetailPage ...>
  <MasterDetailPage.Master>
    <local:MyMasterPage x:Name="masterPage" />
  </MasterDetailPage.Master>
  ...
</MasterDetailPage>
```

---

### MyMasterDetailPage.xaml.cs

- MasterDetailPage にイベント処理を記述

---

```c#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Xamarin.Forms;

namespace MyApp
{
  public partial class MyMasterDetailPage : MasterDetailPage
  {
    public MyMasterDetailPage()
    {
      InitializeComponent();
      masterPage.ListView.ItemSelected += OnItemSelected;
    }

    void OnItemSelected(object sender, SelectedItemChangedEventArgs e)
    {
      var item = e.SelectedItem as MyMasterPageItem;
      if (item != null)
      {
        Detail = new NavigationPage((Page)Activator.CreateInstance(item.TargetType));
        masterPage.ListView.SelectedItem = null;
        IsPresented = false;
      }
    }
  }
}

```

---

- MasterDetailPage から Master の中に手を突っ込むのは気持ち悪くないですか...

---

## 最後のビルド

- 動くぞ :o
- ついでに ListView の書き方も分かった
- 公式の最後の `MasterBehavior` をいじる話はオマケ

---