ようやくWPFもわかってきたのですが、なかなか情報が少ない状況です。.NET 3.0がリリースしてしばらく経ったと思うのですがあまり流行っていないような気もします。さんざん苦労したので調べてわかったことなどを書いてみようと思いました。
別にWPFに限ったことではないのですが、新しいプラットフォームでアプリをつくるときに必ずといって悩むことがあります。
特にUI系のサンプルプログラムはUIコントロールの説明を重視するために静的なデータを表示することがほとんどです。コードの中に表示する文字列や画像のパスが入っていることも珍しくありません。
ところが実際にアプリを作るときは、実行時には表示するデータはまだわからず、何からのアクションに伴いデータを取得して表示するということがよくあります。しかし、表示する項目が動的に変わるようなサンプルはまだ少ないように思います。
WPFを使ったときに複数のアイテムを表示する方法にかなり悩んだので、今回は、YouTube Data APIで検索した結果をいくつかのコントロールで表示するサンプルプログラムを書いてみることにします。
1. データのカプセル化(モデルの作成)
何かしらのデータを表示する際に単にその名前だけを表示することは少なく、複数のメタデータを並べて表示することが多いと思います。たとえばRSS Readerの記事の項目で言えば、タイトルだけじゃなく、作者や、公開日時も表示しますし、YouTubeなどの動画系の項目なら名前だけじゃなく、サムネイルやタグなんかを表示すると思います。
ますはこういったデータの固まりをモデルとしてクラスを用意します。今回はGoogle Data API SDKを使うので、Google.GData.Client.AtomEntryをモデルとして使います。(名前のとおりAtomEntryは基本的なモデルクラスでYouTube用のVideoデータのプロパティ、たとえば動画のサムネイルなどはとれないようです)
2. UI用Adapterクラスの作成
モデルとUI用のクラスを別に分ける必要がないのですが、クラスの再利用を考えるとデータの管理する箇所がUIのフレームワークに依存するのを避けるために別ける方が良いと思います。(そもそも今回はモデル用のGoogle.GData.Client.AtomEntryは変更できないのだが)
具体的にはSystem.ComponentModelにあるINotifyPropertyChangedを継承したUI用のクラスを作ります。名前の通りプロパティの変更をUIコントロールに通知することができ、あとはXAMLでプロパティをBindするだけになるので非常に楽ちんです。
using System; using System.Collections.Generic; using System.ComponentModel; using System.Text; using Google.GData.Client; using Google.GData.Extensions; namespace WPFYouTube { public class UIVideoItem : INotifyPropertyChanged { public UIVideoItem(AtomEntry entry) { Title = entry.Title.Text; } public string Title { get; set; } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string info) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(info)); } } }
ますは名前だけ返すクラスにしておきます。
3. Collectionの用意
リストにはUI用のクラスの配列を渡します。ここのやり方を知るのに時間がかかりました。配列の内容の変更を通知してくれるObservableCollectionを使ってXAMLでBindをします。
ちなみにこれから説明する例が素直なやり方なのかはよくわかっていません。
Windowのクラス側にてObservableCollectionのテンプレートを使いUIVideoItemの配列を定義します。
private ObservableCollection<UIVideoItem> videoItems = new ObservableCollection<UIVideoItem>();
XAMLの方でCollectionViewSourceを定義して、SourceにWindowの”VideoItems”というリソースを動的リソースとして指定します。(ObservableCollection<UIVideoItem>のXAMLでの書き方がわからなかったのでこうしてます)
<Window.Resources> <CollectionViewSource Source="{DynamicResource VideoItems}" x:Key="VideoItemCollection"/> </Window.Resources>
動的リソースの”VideoItems”は、Windowクラスのプロパティではありません。従ってpublicのメンバでVideoItemsを定義しても意味がありません。Resourcesメンバに指定する必要があります。
public Window1() { this.Resources["VideoItems"] = this.videoItems; InitializeComponent(); }
上記によって、動的リソースの”VideoItems”にObservableCollection<UIVideoItem>を指定します。
最後にXAMLの方に戻って、CollectionViewSourceをListBoxのItemsSourceにBindします。これでC#側でmediaItemsを変更するだけでリストの内容が変えることができます。
<ListBox ItemsSource="{Binding Source={StaticResource VideoItemCollection}}"/>
実行してみましょう。(XPクラシックモードです)
なんじゃこれは?ですが、正しい挙動です。ListBoxは項目のToStringの結果を表示するのでこうなります。
4. ItemTemplateの用意
UIVideoItemに下記のようなToStringを実装すればリストボックスにタイトルが並ぶようになります。
public override string ToString() { return Title; }
しかし、これだけならCollectionViewSource にstringの配列を渡すだけでもできてしまうので、モデルやUIクラスを作った意味があまりありません。そこでリストの項目に複数のプロパティをそれぞれ別のコントロールを使って表示するようにします。とりあえずAuthorプロパティをUIVideoItemに用意します。
public UIVideoItem(AtomEntry entry) { Title = entry.Title.Text; Author = (entry.Authors != null && entry.Authors.Count > 0) ? entry.Authors[0].Name : ""; } public string Title { get; set; } public string Author { get; set; }
ListBoxはデフォルトでは項目のToStringを単にテキストラベルで表示するだけですが、表示方法をItemTemplateで指定できます。
<ListBox ItemsSource="{Binding Source={StaticResource VideoItemCollection}}"> <ItemsControl.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding Path=Title}"/> <TextBlock Text="{Binding Path=Author}"/> </StackPanel> </DataTemplate> </ItemsControl.ItemTemplate> </ListBox>
これを実行すると、TitleとAuthorがStackPanelで水平に並べられて表示されます。
水平に並べたためあまり効果は見えませんが、TItleとAutorが別々のTextBlockになっているのでTitleだけ太字にしたり、Authorだけ色を変えるなどスタイルの変更が柔軟に対応できます。
なお項目の表示方法を共通化したい場合などは、リソースとしてあらかじめ宣言しておくことができます。
<Window.Resources> <CollectionViewSource Source="{DynamicResource VideoItems}" x:Key="VideoItemCollection"/> <DataTemplate x:Key="VideoOnItemsControl"> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding Path=Title}"/> <TextBlock Text="{Binding Path=Author}"/> </StackPanel> </DataTemplate> </Window.Resources>
上記のように用意しておくと、ListBoxのところは
<ListBox ItemsSource="{Binding Source={StaticResource VideoItemCollection}}" ItemTemplate="{StaticResource VideoOnItemsControl}"/>
で済むようになります。
5. コンテナのTemplateの変更
表示方法を変更できるのは項目だけではありません。項目を入れている箱、コンテナも変更することができます。通常リストボックスは垂直方向に項目が並びますがこれを水平方向に変えることがWPFでは簡単にできます。
<ListBox ItemsSource="{Binding Source={StaticResource VideoItemCollection}}" ItemTemplate="{StaticResource VideoOnItemsControl}"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <StackPanel Orientation="Horizontal"/> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> </ListBox>
と書くと、、、
のようになります。
だいぶ長くなってしまいました。今日はここまでにします。次回はItemsControlとListViewについて書いてみたいと思います。