ダイナミックにビューを更新する ~ダイナミックテンプレートによる柔軟な UI ~
サンプルプロジェクト DynamicTemplate.zip
WP7 アプリを作ってて ListBox を多用しますが、画面の方向によって表示を変えたい!という事ありませんか?
例えばこの画像のように、縦画面では通常のリスト表示、横画面では見やすいようにアイコン表示。いかにもありがちですよね。
ListBox を ListView っぽく「アイコン表示」・「詳細表示」を切り替えたいこともあります。
画面の回転などの状態に合わせて表示方法を変えれば良いのですが、どうするのが一番簡単でしょうか。
そうです、縦画面用・横画面用のテンプレートを作成しておいて、Orientation プロパティの変化に合わせてテンプレートを変えてしまえばいいのです。
テンプレートを変える仕組みには、コンバータを使用します。
方法
まずは Blend 上でテンプレートを作成します。
- あまり使われることがない ContentControl を配置します。
- ContentControl のテンプレートを "PortlaitTemplate" という名前で作成します。
- テンプレートに ListBox を配置し、データバインディングして表示できるようにしておきます。
- ListBox 項目のデータテンプレートを "PortlaitItemTemplate" という名前などで作成します。
- ContentControl に戻って、テンプレートをコピーして作成し "LandscapeTemplate" という名前で作成します。
横画面用の ListBox を作成します( WrapPanel で表示するなど)。 - ListBox 項目のデータテンプレートを "LandscapeItemTemplate" という名前などで作成します。
- 次に Visual Studio に戻って、以下のクラスをプロジェクトに追加します。
public class OrientationToTemplateConverter : IValueConverter { // ListBox.Visibility にバインドされているコンバータ public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { PageOrientation ori = (PageOrientation)value; if (ori == PageOrientation.None || ori == PageOrientation.Portrait || ori == PageOrientation.PortraitDown || ori == PageOrientation.PortraitUp) { object template = Application.Current.Resources["PortlaitTemplate"]; return template; } else { object template = Application.Current.Resources["LandscapeTemplate"]; return template; } } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } }
- また Blend に戻って、ContentControl.Template のバインディングを変更します。
バインディングを PhoneApplicationPage.Orientation にして、コンバータに上記の OrientationToTemplateConverter を指定します。 - これで完成!と言いたいところですが、もう一つ。
MainPage.xaml に作成した DataTemplate, ControlTemplate を切り取って、App.xaml に貼り付けます。
これで完成です。ビルドしてみてください。
画面の回転に合わせてテンプレートが入れ替わり、動的にビューが変更されています。
この技は至る所で使えます。
ViewModel の一つのプロパティを変更するだけで、ビューの制御を行うことが出来ます。
例えば ListBox を ListView っぽく「アイコン表示」・「詳細表示」なども、テンプレートを用意しておいてコンバータで状態を検出して、それに対応したテンプレートを返すだけです。
メモリの節約にも使用することが出来ます。
たとえば ListBox に表示する項目がない場合、コンバータで null を返してしまえば ListBox のインスタンスは生成されません。
手順
まだ Blend の操作に慣れていない方もいらっしゃると思いますので、すべての手順を書いておきます。
※ここまで書いた後に、手順動画にすれば良かったと後悔。
まず今回は ListBox の表示を変えてみたいと思います。
- Portlait(縦画面) の時は普通に ListBox で表示
- Landscape(横画面) の時は、WrapPanel を使用した ListBox で表示
- 画面の回転に合わせて表示を変える
とします。
- まずプロジェクトを作成しテンプレート変換コンバータクラスを追加します。
public class OrientationToTemplateConverter : IValueConverter { // ListBox.Visibility にバインドされているコンバータ public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { PageOrientation ori = (PageOrientation)value; if (ori == PageOrientation.None || ori == PageOrientation.Portrait || ori == PageOrientation.PortraitDown || ori == PageOrientation.PortraitUp) { object template = Application.Current.Resources["PortlaitTemplate"]; return template; } else { object template = Application.Current.Resources["LandscapeTemplate"]; return template; } } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } }
- Silverlight Toolkit をプロジェクトに追加します。追加方法は以下のページを参照してください。
Silverlight Toolkitがいつの間にかNuGetになってたので。
- ContentControl をアセットの「コントロール」「すべて」から選択し、ContentPanel 上に同じ大きさで配置します。
- ContentControl の「テンプレート編集」「空のアイテムの作成」を選択し「PortlaitTemplate」でドキュメント上に作成します。
- テンプレートには Grid と同じ大きさの ListBox を配置します。
- ListBox に表示するサンプルデータを作成します。
- とりあえずはデフォルトで。
- 作成したサンプルデータの "Collection" を ListBox にドラッグ&ドロップして項目を表示します。
- ListBox の「生成されたアイテムの編集」「コピーして編集」を選択します。
- PortlaitItemTemplate として作成します。
- とりあえず StackPanel の背景を PhoneAccentColor にし、上下左右の Margin を 5 にしました。
- 上図のように ListBox の幅で StackPanel が表示されていません。ListBox.HorizontalContentAlignment = Stretch にしても幅一杯に表示されないので(バグと言われている)、XAML で ListBox を以下のように編集します。
<ListBox~> <ListBox.ItemContainerStyle> <Style TargetType="ListBoxItem"> <Setter Property="HorizontalContentAlignment" Value="Stretch" /> </Style> </ListBox.ItemContainerStyle> </ListBox>
- これで ListBox 幅一杯に広がりました。ここまでで縦画面のテンプレート作成は終わりです。次は横画面用のテンプレートに取りかかります。
- ListBox の「テンプレート編集」「コピーして編集」を選択します。
- LandscapeTemplate として作成します。
- Portlait と同じテンプレートがコピーされます。
- LandscapeTemplate テンプレート(横画面) では WrapPanel で表示させますので、 XAML で以下のように追記します。
xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit" ~中略~ <ListBox~> <ListBox.ItemsPanel> <ItemsPanelTemplate> <toolkit:WrapPanel/> </ItemsPanelTemplate> </ListBox.ItemsPanel> </ListBox>
- これで WrapPanel で表示されるようになりました。
- Landscape で表示するアイテムのテンプレートを編集します。
ListBox の「生成されたアイテムの編集」「コピーして編集」を選択します。 - LandscapeItemTemplate として作成します。
- 140x140 のサイズにするとちょうど良さそうです。これで Portlait / Landscape 双方のテンプレートが完了しました。次に進みます。
- 次は画面が回転したらテンプレートが変わるように設定します。
ContentControl の Template の「詳細オプション」を選択します。 - 「データバインド」を選択します。
- 「要素プロパティ」から PhoneApplicationPage を選択し、表示を「すべてのプロパティ」にし、プロパティから「Orientation」を選択します。
「値コンバータ」の「...」ボタンを押します。 - OrientationToTemplateConverter を選択します。
- はい完成!と言いたいところですが、何も表示されなくなってしまいました。
というのもコンバータの中では、アプリケーションリソース内のテンプレートを参照していますが、今のところテンプレートはページ上にあります。
コンバータがテンプレートを参照する際に取得できないので、null のテンプレートが設定され何も表示されなくなってしまいました。
では、MainPage.xaml にある ControlTemplate, DataTemplate を切り取って、App.xaml の、Application.Resources にペースとしてください。 - これで見えるようになりました。
- Blend 上で方向を変えてみても正しく追従しています。
ではエミュレータや実機で動作させてみてください!