【WPF】ItemsControlの基本的な使い方

# はじめに
例えば

```c#
class Store
{
    public string Name { get; set; }
    public string Prefecture { get; set; }
    public int FavoriteCount { get; set; }
}
```

このようなクラスの **List** を画面上に表示し、一覧画面を作成したいというようなケースは多々あるかと思います。
言語によってはView上で `for` を使用しListをループすることで実現可能だったりしますが
残念ながらWPFではそのような方法では実現できません。

そこで登場するのが[ItemControlクラス](https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.controls.itemscontrol?redirectedfrom=MSDN&view=netcore-3.1)です。
ItemsControlクラスはコレクションを並べて表示することができ、上記のようなことを実現するにはもってこいのクラスです。
また派生クラスには[ListBoxクラス](https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.controls.listbox?view=netcore-3.1)や[ComboBoxクラス](https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.controls.combobox?view=netcore-3.1)などがあり、UIのカスタマイズなどを行うことができるようになっています。
非常に柔軟性が高く、お世話になる機会も多いだろうと思い、ItemsControlクラス（とその派生クラス）の基本的な使い方を整理していきます。

## ソースコードについて
今回はMVVMでいうところのViewModelは以下を使用します。

```c#:MainViewModel.cs
namespace ItemsControl.ViewModels
{
    class MainViewModel
    {
        public MainViewModel()
        {
            this.Mall = Enumerable.Range(1 20).Select(x => new Store()
            {
                Name = "お店" + x,
                Prefecture = "東京都",
                FavoriteCount = x * 10,

            }).ToList();
        }

        public List<Store> Mall { get; set; }
    }
}
```

またModelとして前述のStoreクラスを使用しています。

# ItemsControlでカスタマイズできること
ItemsControlでは4つのプロパティが用意されており、それらを設定することでカスタマイズしています。

|プロパティ名|概要|設定で使用するコントロール|
|---|---|---|
|Template|ItemsControlそのものを指定します。|ControlTemplate|
|ItemsPanel|要素の並べ方について指定します。|ItemsPanaleTemplate|
|ItemTemplate|要素を表示する際のテンプレートを指定します。|DataTemplate|
|ItemContainierStyle|要素に適応するStyleを指定します。|Style|

それぞれの持ち場を図にするとこんな感じです。
![ItemsControl.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582190/2897c96a-be4f-790b-9314-81d6dd99951c.jpeg)

ではそれぞれ見ていきます。

## Template
`ControlTemplate` を指定することで、コントロール全体の設定を行うことが多いです。

```xml:MailView.xaml
<ListBox ItemsSource="{Binding Mall}">
    <ListBox.Template>
        <ControlTemplate TargetType="{x:Type ListBox}">
            <Border BorderThickness="5" BorderBrush="Red" Background="LightGray">
                <ItemsPresenter Margin="5" />
            </Border>
        </ControlTemplate>
    </ListBox.Template>
</ListBox>
```

表示するとこんな感じ。
![2020-04-26_14h23_42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582190/6e5ed1e5-61f2-b1d7-70bc-58297faf53cc.png)

見るとわかる通り、表示されているのはListに格納されているオブジェクトの名前になっています。
これはそのオブジェクト（今回の場合は `Store` クラス）に `toString()` が実装されていないためです。
なので `toString()` を実装してあげればこのままでもデータを表示することは可能です。

```c#:MainViewModel.cs
namespace ItemsControl.ViewModels
{
    class MainViewModel
    {
        public MainViewModel()
        {
            this.Mall = Enumerable.Range(1 20).Select(x => new Store()
            {
                Name = "お店" + x,
                Prefecture = "東京都",
                FavoriteCount = x * 10,

            }).ToList();
        }

        public List<Store> Mall { get; set; }

        public override string ToString()
        {
            return this.Name;
        }
    }
}
```

![2020-04-26_14h25_44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582190/da70a08b-b3ef-5883-e903-271173fa945e.png)

## ItemsPanel
`ItemsPanelTemplate` でコレクションをどう並べるかを指定します。
指定できるのは[Panelクラス](https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.controls.panel?view=netcore-3.1)の派生クラスである以下の3つです。
なお、デフォルトで `StackPanel` が指定されているので、何も指定されていない場合は要素が縦に並びます。

|クラス名|配置|
|---|---|
|StacPanel|縦に並ぶ|
|WrapPanel|横に並ぶ|
|Grid|指定はできるが子要素を並べる機能がないためすべて重なる|

```xml:MailView.xaml
<ListBox ItemsSource="{Binding Mall}">
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel Orientation="Horizontal" />
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
</ListBox>
```

表示するとこんな感じ。
![2020-04-26_14h27_07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582190/f05296ca-ae85-9e37-e2a9-0ba96aed354b.png)


ちなみにGridで表示すると以下のようにすべての要素が重なります。
これ、どういうときに使うんだろうか...
![2020-04-26_14h28_37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582190/a66348cf-a497-28d9-7b42-89a187a56ee8.png)

## ItemTemplate
`DataTemplate` でコレクションの項目をどのように表示するかを指定します。
要は、各要素単位での表示設定です。

```xml:MailView.xaml
<ListBox ItemsSource="{Binding Mall}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel>
                <TextBlock>
                    <TextBlock.Text>
                        <MultiBinding StringFormat="【{0}】{1}">
                            <Binding Path="Prefecture" />
                            <Binding Path="Name" />
                        </MultiBinding>
                    </TextBlock.Text>
                </TextBlock>
                <TextBlock Text="{Binding FavoriteCount, StringFormat=お気に入り：{0}}" />
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>
```

表示するとこんな感じ。
![2020-04-26_14h30_54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582190/0e54f2d5-9bb8-b5e0-9f37-7a4db16ff18b.png)



## ItemContainerStyle
`Style` を指定します。
`ItemTemplate` と同じく要素ごとの表示方法を指定するプロパティです。
要は、WebページでいうところのCSSです。

```xml:MailView.xaml
<ListBox ItemsSource="{Binding Mall}">
    <ListBox.ItemContainerStyle>
            <Style TargetType="ListBoxItem">
                <Setter Property="OverridesDefaultStyle" Value="True" />
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="{x:Type ContentControl}">
                            <Border Background="{TemplateBinding Background}">
                                <ContentPresenter />
                            </Border>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            <Style.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                     <Setter Property="Background" Value="LightBlue"/>
                </Trigger>
                <Trigger Property="IsSelected" Value="True">
                     <Setter Property="Background" Value="LightGreen" />
                </Trigger>
            </Style.Triggers>
        </Style>
    </ListBox.ItemContainerStyle>
</ListBox>
```

表示するとこんな感じ。
![2020-04-26_14h45_26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582190/1c7eb4c5-82d2-5204-7d87-e7c2c4b90e72.png)

`Trigger` では、特定の動作に連動させてStyleを変更することができます。
上記コードだと

- マウスオーバーで要素の背景をLightBlueに
- 選択した要素の背景をLightGreenに

という2点を対応しています。

# 最終的にできたもの

ここまで整理してきた4つのプロパティをすべて合わせたものがこちらです。
※見栄えを整えるために `ItemContainerStyle` に一部Styleを追加しています

```xml:MailView.xaml
<ListBox ItemsSource="{Binding Mall}">
        <ListBox.Template>
        <ControlTemplate TargetType="{x:Type ListBox}">
            <Border BorderThickness="5" BorderBrush="Red" Background="LightGray">
                <ItemsPresenter Margin="5" />
            </Border>
        </ControlTemplate>
    </ListBox.Template>
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel Orientation="Horizontal" />
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
    <ListBox.ItemTemplate>
            <DataTemplate>
                <StackPanel>
                    <TextBlock>
                        <TextBlock.Text>
                            <MultiBinding StringFormat="【{0}】{1}">
                                <Binding Path="Prefecture" />
                                <Binding Path="Name" />
                            </MultiBinding>
                        </TextBlock.Text>
                    </TextBlock>
                    <TextBlock Text="{Binding FavoriteCount, StringFormat=お気に入り：{0}}" />
                </StackPanel>
            </DataTemplate>
    </ListBox.ItemTemplate>
    <ListBox.ItemContainerStyle>
            <Style TargetType="ListBoxItem">
                <Setter Property="OverridesDefaultStyle" Value="True" />
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="{x:Type ContentControl}">
                            <Border Background="{TemplateBinding Background}">
                                <ContentPresenter />
                            </Border>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
                <Setter Property="Margin" Value="10" />    <!-- 追加したStykle その1 -->
                <Setter Property="Width" Value="100" />    <!-- 追加したStykle その2 -->
                <Setter Property="Height" Value="50" />    <!-- 追加したStykle その3 -->
                <Style.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                     <Setter Property="Background" Value="LightBlue"/>
                </Trigger>
                <Trigger Property="IsSelected" Value="True">
                     <Setter Property="Background" Value="LightGreen" />
                </Trigger>
            </Style.Triggers>
        </Style>
    </ListBox.ItemContainerStyle>
</ListBox>
```
![2020-04-26_14h54_29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582190/7d8f0911-a967-dcb3-ffc9-b1bcd44a6092.png)


# まとめ
ItemsControlでコントロールの外見を変更する方法を整理しました。
変更のためには4つのパラメーターを変更していきます。
一覧系の画面や選択肢を列挙など、使用用途は幅広くお世話になることも多いかと思います。

なお、今回はListBoxを使用しましたが、基本的な考え方はComboBoxも同じなので流用できるはずです。

# 参考
[WPF 実践](http://kisuke0303.sakura.ne.jp/blog/wordpress/wp-content/uploads/2016/08/4843696230c1698ad8ff7d086b998344.pdf)
[ItemsControl 攻略 ～ 外観のカスタマイズ](http://grabacr.net/archives/1240)