これは、XAML Advent Calendar 2013 の 1 日目のエントリーです。
WPF (または Windows ストア アプリ) におけるコレクション コントロール (ItemsControl) の外観をカスタマイズするための基礎として、4 つのプロパティの使いどころを紹介します。
前置き
WPF でアプリ開発をしていると、そのデザイン性の高さを生かし、WPF っぽい (?) UI のアプリを開発したくなりませんか? なりますよね! 既定のデザインで開発しても、データ バインディング等々の恩恵を多分に受けられるので十分素晴らしいのですが、「その UI だったら WindowsForms で作れるじゃん」なんて思ってしまったり。
なので、私が WPF アプリを開発するときは、最近の Visual Studio や Zune、GitHub for Windows などに見られるモダンなウィンドウ (正式名称教えてください…) で作っていますし (こんな感じの)、実は仕事でもこういったフルカスタムな UI の WPF アプリを開発していました。
このモダンなウィンドウを作る試みは、当ブログの過去のエントリーにもあります。
フルカスタムな UI は、WPF Themes 等から既成のテーマを持ってくるか、Style や Template を使って外観を作りこむ (あるいはその両方) ことになります。外観を作りこむとき、個人的に感じた “頻出かつ難易度が高い UI 部品” が、コレクション コントロールでした。ComboBox や ListBox をはじめとするコレクション コントロールは、外観のカスタマイズに関して高い柔軟性がある一方、Style や Template を指定するプロパティも多く、思い通りの外観を作成するのは大変です (実際、仕事でのチーム メンバーの中にもつまずいている人が多かった印象)。
そこで今回は、(今更感はありますが) コレクション コントロールの外観をカスタマイズする上での基礎をおさらいしましょう。サンプルはすべて WPF でのコードになりますが、Windows ストア アプリ開発でも基本的な考え方は同じ、なはず。
コレクション コントロールの要素
ここで言うコレクション コントロールは、複数の項目の提示をサポートする ItemsControl クラスやその派生クラス (ListBox など) のことを指します。ItemsControl クラスは、用途に応じて 4 つのプロパティを設定し、外観をカスタマイズすることができます。
- Template property
- ItemsPanel property
- ItemContainerStyle property
- ItemTemplate property
Blend for Visual Studio (または Expression Blend) を使用する場合は、コントロールを選択した状態で、デザイナー上部の [{コントロール名}] のメニューから、それぞれのプロパティを編集することができます。
Template property
ControlTemplate を指定します。
主に Border コントロール等で全体の枠線や背景色の設定を行うことが多いです。また、コントロール内のどの部分にコレクション本体を配置するかも、ここで決定します (ItemsPresenter)。以下のコード例では、太さ 1px の黒い枠線に対し 10px の余白を開け、そこにコレクション本体を配置しています。
1 2 3 4 5 6 7 8 9 |
BorderBrush="Black" Background="Azure"> |
ItemsPanel
ItemsPanelTemplate を指定します。
Panel の派生クラスを使用し、コレクション項目をどのようにレイアウトするかを決定します。StackPanel や WrapPanel で垂直または水平方向に並べることが多いでしょう。以下のコードで 3 つの例を示します。
1 2 3 4 5 6 7 |
ItemsPanelTemplate 内には、必ず Panel から派生する要素が含まれていなければならないため、例えば以下のようなケースではエラーが発生してしまいます。ご注意。
また、ItemsPanel を指定せず、前述の ControlTemplate でコレクション項目の並べ方を指定する方法もあります。ControlTemplate 内に ItemsPresenter を配置する代わりに、Panel の派生クラスを指定し、IsItemsHost プロパティに True を設定します。以下の 2 つのコードは、同じ結果が得られます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
BorderBrush="Black" Background="Azure"> |
1 2 3 4 5 6 7 8 9 10 11 12 |
BorderBrush="Black" Background="Azure"> IsItemsHost="True" /> |
ちなみに ItemsControl とその派生クラスは、その用途に応じて既定の ItemsPanelTemplate を持っています。例えば、ListBox の場合は VirtualizingStackPanel、MenuItem の場合は WrapPanel、StatusBar の場合は DockPanel、など。
ItemTemplate
ここからは、項目ごとの外観のカスタマイズになります。
項目の外観のカスタマイズは、ItemContainerStyle で設定するコンテナー要素と、ItemTemplate で設定するデータ オブジェクトの 2 つが対象となります。
先に、簡単な ItemTemplate から説明しましょう。
ItemTemplate プロパティには DataTemplate を指定し、各項目のデータ オブジェクトをどのように表示するかを決定します。
以下のような SampleData 型のコレクションを画面に表示することを考えます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public class SampleData { public int Number { get; set; } public string Name { get; set; } } public class MainWindowViewModel { public List public MainWindowViewModel() { this.Samples = new List { new SampleData { Number = 1, Name = "赤城" }, new SampleData { Number = 2, Name = "加賀" }, new SampleData { Number = 3, Name = "蒼龍" }, new SampleData { Number = 4, Name = "飛龍" }, new SampleData { Number = 5, Name = "翔鶴" }, new SampleData { Number = 6, Name = "瑞鶴" }, }; } } |
ItemsControl の ItemsSource プロパティに Samples をバインドします (1 行目)(MainWindow.DataContext に MainWindowViewModel オブジェクトがバインドしてある前提)。そして、SampleData オブジェクトを表示するための ItemTemplate プロパティに、以下のような DataTemplate を設定してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
このように、コレクションの各項目のデータ オブジェクトをどのように表示するかを決定するのが ItemTemplate です。この例では、データ オブジェクトは SampleData クラスであり、DataTemplate 内の TextBlock に SampleData クラスの各プロパティをバインドして表示させています。
ちなみに、この ItemTemplate を指定しなかった場合、表示されるのは「各オブジェクトの文字列表現」です。上記の例の場合、SampleData オブジェクトを .ToString() した文字列が並びます。また、複数の DataTemplate を定義し、コレクション内のデータに応じて DataTemplate を切り替えたい、などの場合は ItemTemplateSelector プロパティを使用して実現できます。
ItemContainerStyle
項目ごとの外観のカスタマイズ、その 2。
コレクション コントロール (ItemsControl) は、データ オブジェクトを格納する「コンテナー要素」を生成し、そのコンテナー要素を並べて表示します。生成されるコンテナー要素は ItemsControl の派生クラスごとに異なり、例えば ListBox が生成するコンテナー要素は ListBoxItem、ComboBox が生成するコンテナー要素は ComboBoxItem です。
ItemContainerStyle は、そのコンテナー要素をカスタマイズするためのプロパティです。項目にマウスが乗ったときに色を変える、項目が選択されたときに色を変える、といった動作はコンテナー要素が実現しているもので、これらの動作を変更したい場合は ItemContainerStyle を設定しカスタマイズします。
ListBox と、そのコンテナー要素 ListBoxItem をカスタマイズする ItemContainerStyle の例を示します。ListBoxItem は ContentControl の派生型です。コンテナー要素の Template 内に ContentPresenter を配置 (8 行目) して、データ オブジェクト (= ItemTemplate で設定したやつ) を表示できるようにしましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
13 ~ 20 行目の Trigger により、コンテナー要素が選択されたとき (IsSelected が true)、コンテナー要素にマウスが乗ったとき (IsMouseOver が true)、それぞれ要素の背景色が変更されます。
前述の ItemTemplate は、あくまでデータ オブジェクトをどのように表示するかを定義するのに留めるべきで、コレクション内の項目としてどう表示するか (たとえば、項目ごとに区切り線を入れるのか、項目が選択されたときにどういう外観に変わるのか、コレクションの n 番目はどういう外観に変わるのか、など) は、こちらの ItemContainerStyle で設定しましょう。
なお、ItemTemplate 同様、複数の Style を定義しコレクション内のデータに応じて ItemContainerStyle を切り替えたい場合は、ItemContainerStyleSelector プロパティを使用して実現できます。
まとめ
ItemsControl の外観を作りこむための基本として、4 つのプロパティの使いどころを紹介しました。
- Template property
コレクション コントロール本体の外観を決定。 - ItemsPanel property
Panel の派生型を指定し、項目をどのようにレイアウトするかを決定。 - ItemContainerStyle property
コンテナー要素の外観を決定。マウスオーバー、項目の選択などで外観を変化させる場合に使用。 - ItemTemplate property
データ オブジェクト (ItemsSource にバインドされた各データ) の外観を決定。
例によって Silverlight 製のおもちゃを用意しようと思っていたのですが、ItemsControl の外観に関するネタはまだあるため、今月中に公開予定の第二回にご期待ください。
外観のカスタマイズを含め ItemsControl を自由に使いこなせるようになると、UI 作成の幅が広がって捗ります (小並感)。最近 Twitter でチラチラとツイートしている自作の艦これブラウザーですが、メイン画面だけでもそこそこ ItemsControl を使っていたり。
(赤枠部分が ItemsControl またはその派生クラスで実装したもの。クリックで拡大)。
ソースは GitHub で公開していますので、興味のある方は是非。
XAML Advent Calendar 2013、明日は @kaorun さんです。よろしくお願いします!
Pingback: WPFだからできること | 泥庭