しっぽを追いかけて

ぐるぐるしながら考えています

Unity と猫の話題が中心   掲載内容は個人の私見であり、所属組織の見解ではありません

Xamarin でデータの型によってリスト内の表示を切り替えたい(1)

ItemsControl を利用して今度はリスト内にバインドされたデータの型によって、各行の表示を切り替えてみたいと思います

今回のソリューション構成はこちら

f:id:matatabi_ux:20150201213957p:plain

ViewModel を 2つと、TypeTemplateSelector という DataTemplateSelector を追加しました

TypeTemplateSelector は次のような感じです

/// <summary>
/// データソースの型によってテンプレートを選択する DataTemplateSelector
/// </summary>
public class TypeTemplateSelector : DataTemplateSelector
{
    /// <summary>
    /// デフォルトのテンプレート
    /// </summary>
    public DataTemplate DefaultTemplate { get; set; }

    /// <summary>
    /// 文字列アイテム用のテンプレート
    /// </summary>
    public DataTemplate TextItemTemplate { get; set; }

    /// <summary>
    /// 画像アイテム用のテンプレート
    /// </summary>
    public DataTemplate ImageItemTemplate { get; set; }

    /// <summary>
    /// テンプレートを選択する
    /// </summary>
    /// <param name="item">アイテムのデータソース</param>
    /// <param name="container">アイテムのコンテナ</param>
    /// <param name="index">アイテムのインデックス</param>
    /// <returns>選択されたテンプレート</returns>
    public override DataTemplate SelectTemplate(object item, BindableObject container, int? index = null)
    {
        var template = this.DefaultTemplate;

        // データの型ごとにテンプレートを変える
        switch (item.GetType().Name)
        {
            case @"TextItemViewModel":
                return this.TextItemTemplate ?? this.DefaultTemplate;

            case @"ImageItemViewModel":
                return this.ImageItemTemplate ?? this.DefaultTemplate;
        }

        return this.DefaultTemplate;
    }
}

渡された item の型によって選択する DataTemplate を切り替えています

そして ViewModel

/// <summary>
/// トップ画面の ViewModel
/// </summary>
public class TopPageViewModel : BindableBase
{
    /// <summary>
    /// アイテム
    /// </summary>
    private ObservableCollection<object> items;

    /// <summary>
    /// アイテム
    /// </summary>
    public ObservableCollection<object> Items
    {
        get { return this.items; }
        set { this.SetProperty<ObservableCollection<object>>(ref this.items, value); }
    }

    /// <summary>
    /// コンストラクタ
    /// </summary>
    public TopPageViewModel()
    {
        this.items = new ObservableCollection<object>()
        {
            new TextItemViewModel() { Title = "ラグドール", },
            new ImageItemViewModel()
            {
                Title = "ノルウェー・ジャン・フォレストキャット",
                Source = @"http://upload.wikimedia.org/wikipedia/commons/8/87/Norwegische_Waldkatze.jpg",
            },
            new TextItemViewModel() { Title = "メインクーン", },
            new ImageItemViewModel()
            {
                Title = "アメリカン・ショートヘア",
                Source = "http://upload.wikimedia.org/wikipedia/ja/5/50/Americanshorthair.JPG",
            },
            "スコティッシュフォールド",
            new ImageItemViewModel()
            {
                Title = "マンチカン",
                Source = @"http://upload.wikimedia.org/wikipedia/commons/6/6e/Longhairedmunchkin.jpg",
            },
       };
    }
}

Items の型を object のコレクションにして好き放題 ViewModel (実は string 直接もあったり)を突っ込んじゃってます

共通のデータがある場合は Inteface なり継承なりをジェネリックの型にしてば制約をつければいいです

最後に XAML

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:b="clr-namespace:XamarinControl.Behaviors;assembly=XamarinControl"
             xmlns:c="clr-namespace:XamarinControl.Controls;assembly=XamarinControl"
             xmlns:s="clr-namespace:XamarinControl.Selectors;assembly=XamarinControl"
             xmlns:vm="clr-namespace:XamarinControl.ViewModels;assembly=XamarinControl"
             x:Class="XamarinControl.Views.TopPage">
  
  <ContentPage.BindingContext>
    <vm:TopPageViewModel/>
  </ContentPage.BindingContext>
  
  <c:ItemsControl ItemsSource ="{Binding Items}">
    <c:ItemsControl.ItemTemplateSelector>
      <s:TypeTemplateSelector>
        
        <!-- TextItemViewModel の場合 -->
        <s:TypeTemplateSelector.TextItemTemplate>
          <DataTemplate>
            <Grid Padding="20,10">
              <Label Text="{Binding Title}"/>
            </Grid>
          </DataTemplate>
        </s:TypeTemplateSelector.TextItemTemplate>

        <!-- ImageItemViewModel の場合 -->
        <s:TypeTemplateSelector.ImageItemTemplate>
          <DataTemplate>
            <Grid Padding="20,10" HeightRequest="80">
              <Grid.ColumnDefinitions>
                <ColumnDefinition Width="80"/>
                <ColumnDefinition Width="*"/>
              </Grid.ColumnDefinitions>
              <Image Grid.Column="0"
                     Source="{Binding Source}"
                     Aspect="AspectFit"
                     HorizontalOptions="Start"/>
              <Label Grid.Column="1"
                     Text="{Binding Title}"/>
            </Grid>
          </DataTemplate>
        </s:TypeTemplateSelector.ImageItemTemplate>

        <!-- その他の場合 -->
        <s:TypeTemplateSelector.DefaultTemplate>
          <DataTemplate>
            <Grid Padding="20,10">
              <Label Text="{Binding}" TextColor="Red"/>
            </Grid>
          </DataTemplate>
        </s:TypeTemplateSelector.DefaultTemplate>
      
      </s:TypeTemplateSelector>
    </c:ItemsControl.ItemTemplateSelector>
  </c:ItemsControl>
</ContentPage>

ItemTemplateSelector に各データごとに DataTemplate を定義しています

さてこれで実行すると・・・

f:id:matatabi_ux:20150201214639p:plain

1つのリスト内でバラバラな表示ができてますね・・・まさにポリモーフィズム

標準の ListView だとこれができないかったり