データ・コレクションをバインドするには?[Win 8/WP 8]:WinRT/Metro TIPS
Windowsストア・アプリやWindows Phone 8アプリで、データのコレクションをコントロールにバインドする方法を解説する。
powered by Insider.NET
ここまでのTIPSで紹介してきたデータ・バインドの例では、主にstringオブジェクトなど単純なオブジェクトをバインドしていた。また、前回は、文字列のリスト(=C#ではList<string>クラス/VBではList(of String)クラス)をListBoxコントロールに表示する例を挙げたが、詳しくは説明しなかった。そこで本稿では、データのコレクションをコントロールにバインドする方法を解説する。本稿のサンプルは「Windows Store app samples:MetroTips #36(Windows 8版)」と「Windows Store app samples:MetroTips #36(WP 8版)」からダウンロードできる。
なお、掲載しているコードは特記なき場合はWindowsストア・アプリとWindows Phone 8(以降、WP 8)アプリで共通である。
●事前準備
Windows 8(以降、Win 8)向けのWindowsストア・アプリを開発するには、Win 8とVisual Studio 2012(以降、VS 2012)が必要である。これらを準備するには、第1回のTIPSを参考にしてほしい。本稿では64bit版Win 8 ProとVS 2012 Express for Windows 8を使用している。
WP 8向けのアプリを開発するには、SLAT対応CPUを搭載したPC上の64bit版Win 8 Pro以上とWindows Phone SDK 8.0(無償)が必要となる。
●バインドするデータ
これからアプリに表示させるデータとして、2つの文字列をプロパティとして持ったクラスを作る(次のコード)。なお、今回はデータの変更を画面に反映させる必要はないものとして、BindableBaseクラスの継承はしていない。
public class SampleData
{
public string Title { get; set; }
public string Url { get; set; }
}
Public Class SampleData
Public Property Title As String
Public Property Url As String
End Class
次に、上のSampleDataオブジェクトのコレクションを、例えばリスト(=List<SampleData>オブジェクト/List(Of SampleData)オブジェクト)にしてコントロールにバインドすることを考える。コードビハインドからバインドするには、そのような汎用的なコレクションで構わないのだが、デザイン画面でインスタンスを生成してバインドするには汎用的なコレクションは使えない。なぜなら、デザイン画面でインスタンスを生成するときには、単に引数なしのコンストラクタが呼び出されて空のインスタンスが作成されるだけで、その後にインスタンスにデータを追加できないからだ。そのため、汎用的なコレクションでは、デザイン画面でデータを投入できず、どのような表示になるか確認できないのだ。
そこで、独自のコレクション・クラスを作って、その引数なしのコンストラクタでデザイン画面用のデータを生成するようにする(次のコード)。こうしておくと、デザイン画面でデザインを確認できるのだ。なお、デザイン画面で動作しているときだけ画面確認用のデータが生成されるように、DesignModeEnabled(Win 8)プロパティ/IsInDesignTool(WP 8)プロパティを利用している(詳細は「デザイン画面でデータをバインドするには?[Win 8/WP 8]」を参照)。
まずWin 8用のソースを示す。
public class SampleDataCollection : System.Collections.Generic.List<SampleData>
{
public SampleDataCollection()
{
if (Windows.ApplicationModel.DesignMode.DesignModeEnabled)
{
this.Add(new SampleData()
{
Title = "デザイン時Title-1", Url = "デザイン時Url-1",
});
this.Add(new SampleData()
{
Title = "デザイン時Title-2", Url = "デザイン時Url-2",
});
this.Add(new SampleData()
{
Title = "デザイン時Title-3", Url = "デザイン時Url-3",
});
}
}
}
Imports System.Linq
Public Class SampleDataCollection
Inherits System.Collections.Generic.List(Of SampleData)
Public Sub New()
If (Windows.ApplicationModel.DesignMode.DesignModeEnabled) Then
Me.Add(New SampleData())
With Me.Last()
.Title = "デザイン時Title-1"
.Url = "デザイン時Url-1"
End With
Me.Add(New SampleData())
With Me.Last()
.Title = "デザイン時Title-2"
.Url = "デザイン時Url-2"
End With
Me.Add(New SampleData())
With Me.Last()
.Title = "デザイン時Title-3"
.Url = "デザイン時Url-3"
End With
End If
End Sub
End Class
次がWP 8用のコードだ。
public class SampleDataCollection : System.Collections.Generic.List<SampleData>
{
public SampleDataCollection()
{
if (System.ComponentModel.DesignerProperties.IsInDesignTool)
{
this.Add(new SampleData()
{
Title = "デザイン時Title-1", Url = "デザイン時Url-1",
});
this.Add(new SampleData()
{
Title = "デザイン時Title-2", Url = "デザイン時Url-2",
});
this.Add(new SampleData()
{
Title = "デザイン時Title-3", Url = "デザイン時Url-3",
});
}
}
}
Imports System.Linq
Public Class SampleDataCollection
Inherits System.Collections.Generic.List(Of SampleData)
Public Sub New()
If (System.ComponentModel.DesignerProperties.IsInDesignTool) Then
Me.Add(New SampleData())
With Me.Last()
.Title = "デザイン時Title-1"
.Url = "デザイン時Url-1"
End With
Me.Add(New SampleData())
With Me.Last()
.Title = "デザイン時Title-2"
.Url = "デザイン時Url-2"
End With
Me.Add(New SampleData())
With Me.Last()
.Title = "デザイン時Title-3"
.Url = "デザイン時Url-3"
End With
End If
End Sub
End Class
Win 8用とは、デザイン・モードをチェックする方法が異なるだけだ。
- *1List<T>クラスを継承していることの是非は、ここでは問わないこととする。この問題についてはMSDNの「CA1002: ジェネリック リストを公開しません」を参照してほしい。議論の焦点は、公開するコレクションに動作の変更が想定され得るならListクラスではなく汎用的なコレクションにすべき、というものだ。
●データ・コレクションをコントロールにバインドするには?
コントロールのItemsSourceプロパティに、コレクションのオブジェクトをバインドする。
オブジェクトのバインド先となるItemsSourceプロパティはItemsControlクラス(Windows.UI.Xaml.Controls名前空間)が持っている。逆にいえば、ItemsControlクラスを継承しているコントロールにデータ・コレクションをバインドできるのだ。ItemsControlを継承するコントロールとしては、ListViewコントロール、GridViewコントロール、FlipViewコントロール、ListBoxコントロール、ComboBoxコントロールなどがある。
ここでは、ListViewコントロール(WP 8では、その代わりにLongListSelectorコントロール)にバインドしてみよう。XAMLコードは次のようになる。
<ListView x:Name="listView1"
…… 省略 ……
>
</ListView>
<phone:LongListSelector x:Name="listView1"
…… 省略 ……
>
</phone:LongListSelector>
ここではLongListSelectorインスタンスの名前も便宜的に「listView1」としている。
アプリの(デザイン時ではなく)実行時に画面が表示されるときにコードビハインドで、上記のSampleDataCollectionクラスのオブジェクトを生成し、いくつかデータを追加してから、ListViewコントロールにデータ・バインドするコードは、次のようになる(ただし、以下で述べるDataTemplateオブジェクトの作成とItemTemplateプロパティの設定をしないままアプリを実行してもデータは正しく表示されない)。
// SampleDataCollectionクラスのオブジェクトを生成
var sampleCollection = new SampleDataCollection();
// いくつかデータを追加
sampleCollection.Add(new SampleData()
{
Title = "Metroスタイル・アプリの開発者が知るべき3つのこと",
Url = "http://www.atmarkit.co.jp/fdotnet/……",
});
sampleCollection.Add(new SampleData()
{
Title = "デザイン・ガイドラインに従って画面を作成するには?[Win 8]",
Url = "http://www.atmarkit.co.jp/ait/articles/1208/23/news131.html",
});
sampleCollection.Add(new SampleData()
{
Title = "メニューの代わりにアプリ・バーを使うには?[Win 8]",
Url = "http://www.atmarkit.co.jp/ait/articles/1208/30/news149.html",
});
// ListViewコントロールにデータ・バインド
this.listView1.ItemsSource = sampleCollection;
' SampleDataCollectionクラスのオブジェクトを生成
Dim sampleCollection = New SampleDataCollection()
' いくつかデータを追加
sampleCollection.Add(New SampleData())
With sampleCollection.Last()
.Title = "Metroスタイル・アプリの開発者が知るべき3つのこと"
.Url = "http://www.atmarkit.co.jp/fdotnet/……"
End With
sampleCollection.Add(New SampleData())
With sampleCollection.Last()
.Title = "デザイン・ガイドラインに従って画面を作成するには?[Win 8]"
.Url = "http://www.atmarkit.co.jp/ait/articles/1208/23/news131.html"
End With
sampleCollection.Add(New SampleData())
With sampleCollection.Last()
.Title = "メニューの代わりにアプリ・バーを使うには?[Win 8]"
.Url = "http://www.atmarkit.co.jp/ait/articles/1208/30/news149.html"
End With
' ListViewコントロールにデータ・バインド
Me.listView1.ItemsSource = sampleCollection
コードを記述する場所は、Win 8ではLoadStateメソッド(LayoutAwarePageクラスを継承した場合)またはOnNavigatedToメソッド(継承しない場合)、WP 8ではコンストラクタの末尾である。
●何がどこにバインドされたのか?
上のコードを実行した結果として、どんなオブジェクトがListViewコントロールのどこにバインドされるかというと、次の図のようになる。
SampleDataCollectionのオブジェクトをListViewコントロールにバインドするときのイメージ(Win 8)
※コントロールの名前などは異なるが、WP 8でも同様である。
(1)SampleDataCollectionオブジェクトをListViewコントロールのItemsSourceプロパティにセット。
(2)ListViewコントロールはListViewItemオブジェクトを必要なだけ生成し*2、ItemsSourceプロパティにセットされたコレクションの各アイテムをListViewItemオブジェクトのデータ・コンテキスト割り当てる。
(3)以上で、SampleDataCollectionオブジェクトの各アイテムが、個々のListViewItemオブジェクトのデータ・コンテキストにセットされた。
SampleDataCollectionオブジェクトをListViewコントロールのItemsSourceプロパティにセットすると、ListViewコントロールはListViewItemオブジェクトを生成し、コレクションの各アイテムを各ListViewItemオブジェクトのデータ・コンテキストにセットするのである。
注意点は、コントロールのDataContextプロパティにコレクションをセットしても、ItemsSourceプロパティに自動的には設定されないことだ。DataContextプロパティにセットされているオブジェクトを、XAMLコードでさらにItemsSourceプロパティにバインドすることは可能である。
- *2ListViewコントロールが生成するListViewItemオブジェクトの個数は、ItemsSourceプロパティにセットされたコレクションの要素数と等しいとは限らない。UIの仮想化が有効になっているときは、表示するために必要な数しかListViewItemオブジェクトは生成されない。
●コントロールにバインドされたデータを表示するには?
上の図で、個々のListViewItemオブジェクトのデータ・コンテキストにセットされたSampleDataオブジェクトを表示するには、テンプレートを使う。
具体的には、XAMLコードでDataTemplateオブジェクトを定義し、それをListViewコントロールのItemTemplateプロパティにセットすればよい。例えば、SampleDataクラスのデータを表示するDataTemplateオブジェクトを、次のコードのように定義する。DataTemplateオブジェクトのデータ・コンテキストには、SampleDataクラスの個々のオブジェクトが設定されるので、テンプレート内部ではSampleDataクラスのTitleプロパティやUrlプロパティにバインドできる。
<Page.Resources>
…… 省略 ……
<DataTemplate x:Key="DataTemplate1">
<Grid Margin="6" Background="DarkCyan">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Background="BlueViolet" Margin="10,5,5,5" >
<TextBlock Text="{Binding Title}" FontSize="30" TextWrapping="Wrap" />
</Border>
<Border Grid.Column="1" Background="DarkBlue" Margin="0,5,10,5">
<TextBlock Text="{Binding Url}" FontSize="18" TextTrimming="WordEllipsis" VerticalAlignment="Center" />
</Border>
</Grid>
</DataTemplate>
</Page.Resources>
※ WP 8では、「Page.Resources」タグを「phone:PhoneApplicationPage.Resources」タグに変える。グリッドの分割幅なども適宜調整してほしい。
そして、次のコードのようにして、上のDataTemplateオブジェクトをListViewコントロールのItemTemplateプロパティにセットする。
<ListView x:Name="listView1"
ItemTemplate="{StaticResource DataTemplate1}"
…… 省略 ……
>
</ListView>
<phone:LongListSelector
x:Name="listView1"
ItemTemplate="{StaticResource DataTemplate1}"
…… 省略 ……
>
</phone:LongListSelector>
これで実行してみると、コードビハインドで生成したデータが画面に表示されるはずだ。
なお、Win 8ではこのままだと、アイテムごとに表示される幅が違ってしまって見苦しくなる場合がある*3。しかし、DataTemplateの定義にいろいろと手を入れてみても、うまくいかない。アイテムごとの表示幅などはDataTemplateの外側で、すなわち、コントロールのItemContainerStyleプロパティやItemsPanelTemplateなどで設定しなければならない。ItemContainerStyleプロパティで設定する例を示すと、次のコードのようになる。
<ListView x:Name="listView1"
ItemTemplate="{StaticResource DataTemplate1}"
Width="1000" … 省略 …
>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Width" Value="980" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
</ListView>
ListViewコントロール自体の幅を「1000」とし、個々のListViewItemの幅を「980」に設定した。
- *3WP 8では、テンプレートのGridコントロールで幅を指定すればよいようだ。
●デザイン画面でバインドするには?
XAMLコードだけでデータ・ソースのインスタンスを生成し、それをListViewコントロールのItemsSourceプロパティに設定すればよい。
「デザイン画面でデータをバインドするには?[Win 8/WP 8]」で解説したように、XAMLコードでSampleDataCollectionオブジェクトをページのリソースに定義し、それをListViewコントロールのItemsSourceプロパティにセットすればよいのだ。次のコードのようになる。
まず、SampleDataCollectionのオブジェクトをページのリソースに定義するXAMLコードは、次のようになる。
<Page.Resources>
<local:SampleDataCollection x:Key="SampleDataCollection" />
…… 省略 ……
</Page.Resources>
※ WP 8では、「Page.Resources」タグが「phone:PhoneApplicationPage.Resources」タグになる。また、ページの開始タグに「xmlns:local」名前空間の定義を追加する。
次は、コントロールのItemsSourceプロパティにSampleDataCollectionのオブジェクトをセットするコードだ。
<ListView x:Name="listView1" ItemsSource="{StaticResource SampleDataCollection}"
…… 省略 ……
>
…… 省略 ……
</ListView>
<phone:LongListSelector
x:Name="listView1" ItemsSource="{StaticResource SampleDataCollection}"
…… 省略 ……
>
</phone:LongListSelector>
以上で完成だ。
VS 2012のデザイン画面は次の画像のようになる。上で示したXAMLコードに、さらに見やすくするための色分けなどを追加してある。
そして、実行した画面が次の画像だ。表示されているデータは、デザイン時と実行時で切り替わっている。
●まとめ
コレクションをコントロールにバインドするには、コレクションのオブジェクトをコントロールのItemsSourceプロパティにセットする。その表示はDataTemplateオブジェクトで定義して、データの表示に使用するコントロールのItemTemplateプロパティに設定する。
コレクションをバインドする方法について、詳しくは次のドキュメントを参照してほしい。
Copyright© Digital Advantage Corp. All Rights Reserved.