【WPF】ControlTemplateやStyleを別ファイルに定義し、Viewから利用する方法

# 概要
xamlのコントロールの`ControlTemplate`や`Style`を別のxamlファイルに定義し、それを色々なxamlファイルから利用する方法をまとめました。
別ファイルにまとめておくことで、テンプレートやスタイルが再利用できやすくなったり、各コントロールの装飾の定義自体で、画面を定義しているxamlが煩雑になりすぎることも防ぐことができます。

# 方法
`ControlTemplate`や`Style`の定義をまとめたxamlファイル (`ResourceDictionary`) を作成しておきます。そして、画面を作成している別のxamlファイル (`View`) から、`ResourceDictionary`内の`MergedDictionaries`を使用して別のxamlファイルにまとめたリソースを利用します。
`MergedDictionaries`はリソースの共有や再利用を容易にするための機能です。
### ResourceDictionaryについて
`ResourceDictionary`は、XAMLで色々な箇所で使用することができる再利用可能なリソースを定義するするための機能です。
各リソースには、名前 (Key) を設定して、他から参照できるようにしておくことができます。

以下のように、画面を作成しているViewの`<ResourceDictionary>`部分にリソースを定義して同じxamlファイルから使用することが可能です。

```xml
<Window x:Class="Hoge.SampleView1
             Title="サンプル画面1" Width="300">
    <Window.Resources>
        <ResourceDictionary>
            <!-- リソースの定義 -->
            <ControlTemplate x:Key="SampleView1Button" TargetType="{x:Type ToggleButton}">
                <Border
                    x:Name="sampleBorder"
                    CornerRadius="20">
                       <TextBlock
                        x:Name="sampleTextBlock"
                        Text="{TemplateBinding Content}" />
                </Border>
                <ControlTemplate.Triggers>
                    <!-- リソースの処理 ※省略 -->
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </ResourceDictionary>
     </Window.Resources>
     <Grid>
        <ToggleButton
            Width="150"
            Height="30"
            Content="{Binding ButtonName}"
            IsChecked="{Binding IsChecked}"
            Template="{StaticResource SampleView1Button}" /> <!-- リソースを使用 -->
     </Grid>
</Window>
```

ただ他のxamlファイルからも上記と同じリソース (ここでは`SampleView1Button`) を使用したいという場合があります。そのような場合に別のxamlファイルの`ResourceDictionary`にリソースを定義します。

ここでは`SampleDictionary.xaml`というファイル名の`ResourceDictionary`を定義しています。

```xml
<ResourceDictionary xmlns="http://hogehoge"
                    xmlns:x="http://fugafuga">
 <ControlTemplate x:Key="SampleView1Button" TargetType="{x:Type ToggleButton}">
    <Border
        x:Name="sampleBorder"
        CornerRadius="20">
           <TextBlock
            x:Name="sampleTextBlock"
            Text="{TemplateBinding Content}" />
    </Border>
    <ControlTemplate.Triggers>
        <!-- リソースの処理 ※省略 -->
    </ControlTemplate.Triggers>
 </ControlTemplate>
```
画面を作成するview側では、`ResourceDictionary.MergedDictionaries`を使用して、`SampleDictionary.xaml`のリソースを使用できるようにし、`ToggleButton`のテンプレートに`SampleDictionary.xaml`で定義したテンプレートを使用しています。
```xml
<Window x:Class="Hoge.SampleView1
             Title="サンプル画面1" Width="300">
    <Window.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="../SampleDictionary.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
     </Window.Resources>
        </ResourceDictionary>
     </Window.Resources>
     <Grid>
        <ToggleButton
            Width="150"
            Height="30"
            Content="{Binding ButtonName}"
            IsChecked="{Binding IsChecked}"
            Template="{StaticResource SampleView1Button}" /> <!-- リソースを使用 -->
     </Grid>
</Window>
```

# サンプルコード
サンプル画面1 (`SampleView1`) とサンプル画面2 (`SampleView2`) を作成しました。
リソースを定義するファイルは、`StyleDictionary.xaml`というファイルを作成しました。そのファイルに`ControlTemplate`や`Style`を定義して、各画面のxamlからそれを利用しています。

### サンプル画面1のView
`ToggleButton`のテンプレートに`StyleDictionary.xaml`で定義したテンプレートを使用しています。
`StyleDictionary`には、`ToggleButton`の押下時、未押下時のボタンの見た目を定義しています。

```xml
<Window x:Class="Hoge.SampleView1
            Title="サンプル画面1" Width="300">
    <Window.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="../StyleDictionary.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
     </Window.Resources>
     <Grid>
        <ToggleButton
            Width="150"
            Height="30"
            Content="{Binding ButtonName}"
            IsChecked="{Binding IsChecked}"
            Template="{StaticResource SampleView1Button}" />
    </Grid>
</Window>
```

### サンプル画面2のView
`ListView`の`ListViewItem`の`Style`を定義する箇所 (`ItemContainerStyle`) に、`StyleDictionary.xaml`で定義したテンプレートを使用しています。
`StyleDictionary`には、`ListView`の奇数行や偶数行、選択行の背景色を設定する`Style`を定義しています。

```xml
<Window x:Class="Hoge.SampleView2"
             Title="サンプル画面2" Width="500">
    <Window.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="../StyleDictionary.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Window.Resources>
    <Grid>
        <ListView
   			SelectionMode="Single"
			AlternationCount="2"
            KeyboardNavigation.TabNavigation="Continue"
            Padding="7"
			ItemContainerStyle="{StaticResource SampleListViewItemStyle}"
			ItemsSource="{Binding Items}">
            <ListView.View>
                <GridView>
                    <GridView.Columns>
                    <!-- 以下省略 -->

```

### `ControlTemplate`や`Style`を定義したxamlファイル (StyleDictionary.xaml)

```xml
<ResourceDictionary xmlns="http://hogehoge"
                    xmlns:x="http://fugafuga">
    <!--  サンプル画面1のボタンのテンプレート  -->
    <ControlTemplate x:Key="SampleView1Button" TargetType="{x:Type ToggleButton}">
        <Border
			x:Name="sampleBorder"
			CornerRadius="20">
            <TextBlock
				x:Name="sampleTextBlock"
				HorizontalAlignment="Center"
				VerticalAlignment="Center"
				FontSize="10"
				FontWeight="Bold"
				Text="{TemplateBinding Content}" />
        </Border>
        <ControlTemplate.Triggers>
            <!-- ボタン未押下時 -->
            <Trigger Property="IsChecked" Value="False">
                <Setter TargetName="sampleBorder" Property="Background" Value="LightGray" />
                <Setter TargetName="sampleBorder" Property="BorderBrush" Value="Transparent" />
                <Setter TargetName="sampleTextBlock" Property="Foreground" Value="White" />
            </Trigger>
            <!-- ボタン押下時 -->
            <Trigger Property="IsChecked" Value="True">
                <Setter TargetName="sampleBorder" Property="Background" Value="DodgerBlue" />
                <Setter TargetName="sampleBorder" Property="BorderBrush" Value="Transparent" />
                <Setter TargetName="sampleTextBlock" Property="Foreground" Value="White" />
            </Trigger>
        </ControlTemplate.Triggers>
    </ControlTemplate>

    <!--  サンプル画面2のListViewItemのStyle  -->
    <Style x:Key="SampleListViewItemStyle" TargetType="{x:Type ListViewItem}">
        <Setter Property="IsTabStop" Value="False"/>
        <Style.Triggers>
            <!-- 奇数行の背景色 -->
            <Trigger Property="ItemsControl.AlternationIndex" Value="0">
                <Setter Property="Background" Value="AntiqueWhite"/>
            </Trigger>
            <!-- 偶数行の背景色 -->
            <Trigger Property="ItemsControl.AlternationIndex" Value="1">
                <Setter Property="Background" Value="NavajoWhite"/>
            </Trigger>
            <!-- 選択行の背景色 -->
            <Trigger Property="IsSelected" Value="True">
                <Setter Property="Background" Value="LightBlue"/>
            </Trigger>
        </Style.Triggers>
    </Style>
</ResourceDictionary>
```

:::note
`TemplateBinding`について
`ControlTemplate`内で使用可能で、テンプレート内にあるプロパティを、テンプレートが適用されているコントロールのプロパティとリンクするものです。
上記の場合は`Sample1View`の`ToggleButton`の`Content`プロパティとリンクしています。
:::

# おわりに
この方法は実業務でもよく利用していたので、まとめておきました。
画面のコントロールに少々複雑な`ControlTemplate`や`Style`を定義していても、別ファイルに切り出しておくことで実際に使用するView側のファイルでは、画面をスクロールする頻度が減りました。なのでxamlの画面の改修の際も、改修が行い易くなった気がします。