[WPF]あるアセンブリ内にあるResourceDictionaryを、他のアセンブリから参照する

# 動作環境

|||
|---|---|
|OS|Windows 10, ver1803, Build 17134.1006|
|IDE|Visual Studio Community 2019, ver16.2.5|
|SDK|.NET Core 3.0 preview9|

* .NET Core 3.0 は[こちらからダウンロードできます](https://dotnet.microsoft.com/download/dotnet-core/3.0)。
* Visual Studioのこのバージョンで .NET Core 3.0 を使うために、「ツール > オプション > 環境 > プレビュー機能 > Use previews of the .NET Core SDK」にチェックを入れるのを忘れないようにしてください。この設定には再起動が必要です。

# 概要

.NET Core 3.0 のおかげで、.NET Core 環境でも WPF を利用することができますね！私はこれからも WPF のお世話になりそうですが、未だにその全容を把握していないので苦戦することもあります。

今回は、xaml内で`ResourceDictionary`タグを使って定義したリソースを他の場所から利用する方法に関する記事なのですが、ただの参照ではなく、あるアセンブリのリソースを、他のアセンブリから参照して使います。

## つまづきどころ

私は以下の点でつまづきました。

1. xamlをリソースとしてアセンブリに埋め込む方法が分からなかった
2. 他のアセンブリの埋め込みリソースをどのように参照すればよいか分からなかった
3. 他のアセンブリを参照して得られる`ResourceDictionary`内のxaml要素をKeyで参照するにはどうすればよいか分からなかった

それぞれ、以下のように解決します。

1. xamlファイルの「ビルド アクション」を「ページ」に設定します(デフォルトでそうなっているはず)。
2. *パックURI* という仕組みを用いて他のアセンブリ内のリソースを参照します。
3. `ComponentResourceKey` マークアップ拡張を用いて、他のアセンブリ内のxaml内にある要素を参照します。

それでは、詳しく見ていきましょう。

# ソリューション内の他のアセンブリからxaml要素を読み込もう

他のアセンブリ内のxamlを読み込みたいシナリオとして例えば、UIのスタイルやテーマを定義した`ResourceDictionary`があって、それをエンドユーザー向けのGUIアプリケーションに適用したい場合が挙げられます。今回は、スタイル用のプロジェクトに単色で塗るためのブラシを定義し、それをWPFアプリケーション本体のためのプロジェクトから参照することで、ウィンドウを美しい色で塗りつぶしてみましょう。

## 0. 下準備

まずは、ソリューションを作成します。それと、必要なプロジェクトを2つ作成します。これが、xamlを定義する側・参照する側、になるわけです。プロジェクトの構成は以下のようになりました。

* MyApp.sln
    * MyApp.csproj
        * プロジェクトタイプは "WPF App (.NET Core)"
        * `Styles.csproj` を参照に追加しておきます
    * Styles.csproj
        * プロジェクトタイプは "WPF App (.NET Core)"
        * プロジェクトのプロパティを開き、出力の種類を「クラス ライブラリ」に変更しましょう
        * `App.xaml`, `MainWindow.xaml`が生成されると思いますが、要らないので削除してしまいましょう

プロジェクト タイプを "WPF App (.NET Core)" にしないと、WPF向けの機能が使えないので注意してください。特に、 `Styles` プロジェクトはこれからクラス ライブラリとして使いますが、 "クラス ライブラリ (.NET Core)" とかで作成すると上手くいきません。

## 1. `ResourceDictionary` を定義する

プロジェクトができたら、まずは `ResourceDictionary` にスタイルを定義する必要がありますね。`Styles`プロジェクトに、 `MyDictionary` という名前で "リソース ディクショナリ(WPF)" を新規作成し、中身は以下のようにしてみましょう。 `SolidColorBrush` のスタイルを定義しています：

```xml:MyDictionary.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <SolidColorBrush Color="HotPink" x:Key="PinkBrush"/>
</ResourceDictionary>
```

さて、このように作成した`ResourceDictionary`は、他のアセンブリから読み込もうとする場合には問題があります。他のアセンブリからだと、`PinkBrush`というキーではリソースを解決できないのです。

`ResourceDictionary`内の要素を他のアセンブリから参照するには、以下のようにキーを定義します：

```xml:MyDictionary.xaml(一部)
<SolidColorBrush Color="HotPink" x:Key="{ComponentResourceKey ResourceId=PinkBrush}"/>
```

`PinkBrush` としていた部分が、複雑なマークアップに替わっていますね。 `ComponentResourceKey`マークアップ拡張が今回の肝です。これにより、`ResourceId`に指定したキーを使って利用側のアセンブリから参照することができるようになります。とは言っても、通常のように `{DynamicResource PinkBrush}` というふうに指定できるキーが得られたわけではなく、参照側も工夫が必要になります。これについては3節で解説します。

## 2. 別のアセンブリ内の`ResourceDictionary`を読み込む

定義側ができたので、参照側を作りましょう。`MyApp`プロジェクトにある`MainWindow.xaml`を編集して、`ResourceDictionary`を読み込みます。

```xml:MainWindow.xaml
<Window x:Class="WpfRefAnotherAsmXaml.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfRefAnotherAsmXaml"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">

    <Window.Resources>
        <ResourceDictionary Source="MyDictionary.xaml"/>
    </Window.Resources>
</Window>
```

`Window.Resources`要素の中に、`MyDictionary.xaml` を参照する `ResourceDictionary` を書きました。これで読み込めると簡単ですが、アセンブリをまたいで参照したいとなると、そういうわけにも行きません。`MyDictionary.xaml`と書かれている部分を書き換えなければなりません。以下のように：

```xml:MainWindow.xaml(一部)
<ResourceDictionary Source="pack://application:,,,/Styles;component/MyDictionary.xaml"/>
```

なんだか複雑な記法が出てきましたね。これは「パックURI」と呼ばれるもので、WPFにおいて他のアセンブリに埋め込まれているリソースを参照するために用いる URI です。URLのようですが、URL も URI の仕様で決められているものの一部らしいです。

この記法について解説しましょう。基本的に[公式の仕様解説](https://docs.microsoft.com/ja-jp/dotnet/framework/wpf/app-development/pack-uris-in-wpf)に従えばOKですが、ちょっと分かりやすくした画像を置いておきますね。
![PackUri.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32800/25791ed3-18b5-5e6a-ee45-ee4a2bc641aa.png)
場面に応じて変えなければならないのは、「Styles」の部分(その時のアセンブリ名を入れる)と、「/MyDictionary.xaml」の部分(その時のパスを入れる)です。

`;component` の部分、書き換えなくてよいので問題ないですが、自分にはあまり意味が分かりませんでした。

## 3. `ResourceDictionary`内のリソースにアクセスする

最後に、`ResourceDictionary`内に定義した`PinkBrush`を利用してみましょう。`MainWindow.xaml`を以下のように書き換えてみます。例によって、これはこのままでは動かないコードです：

```xml:MainWindow.xaml
<Window x:Class="WpfRefAnotherAsmXaml.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfRefAnotherAsmXaml"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">

    <Window.Resources>
        <ResourceDictionary Source="pack://application:,,,/Styles;component/MyDictionary.xaml"/>
    </Window.Resources>

    <Grid>
        <Rectangle Fill="{DynamicResource PinkBrush}"/>
    </Grid>
</Window>
```

`Grid`要素と`Rectangle`要素を追加しました。この`Rectangle.Fill`に適切なブラシを指定すれば、目的を達成できるわけです。さて、ここには修正しなければならない箇所があります。それは、`Rectangle.Fill`に使うリソースのキーを指定している部分です。これは以下のように書き換えます：

```xml:MainWindow.xaml(一部)
<Rectangle Fill="{DynamicResource {ComponentResourceKey ResourceId=PinkBrush}}"/>
```

これは、単にリソースを定義したときに決めたキーを丸ごと、`DynamicResource`マークアップ拡張のキーに与えています。`MyDictionary.xaml`を書いたときにこの`SolidColorBrush`に与えたキーは `{ComponentResourceKey ResourceId=PinkBrush}` ですから、これを `DynamicResource` マークアップ拡張に与えて、

```
{DynamicResource {ComponentResourceKey ResourceId=PinkBrush}}
```

にしているというわけです。

## 4. 完成！

これにて、私の `MainWindow` は美しく落ち着いた色合いのウィンドウと化しました。美しく落ち着いていますね？（ｱｯﾊｲ）
![pink.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32800/ca8a351e-f978-7aaf-26d5-f043071d78f1.png)

# 注意点

## xamlファイルのビルド アクション

パックURIでxamlファイルを参照するには、そのxamlファイルがアセンブリのリソースとして扱われている必要があります。xamlデフォルトでアセンブリのリソースとして扱われていますが、上手くいかない場合はxamlファイルのプロパティを開き、「ビルド アクション」が「ページ」になっていることを確認してください。

## URIの解決エラーについて

パックURIが合っているのに、パックURIが解決できなかった旨のエラーをVisualStudioから受ける場合があります。私の場合、参照しているxaml内のリソースを実際に使っている部分で、キーの指定が間違っている場合に、このエラーが余分に表示されていました。キーを指定するときは特に、`DynamicResource`や`ComponentResourceKey`マークアップ拡張を忘れていないかチェックしてください。

# まとめ

WPFは今でもWebにある日本語の資料でカバーしきれていないシナリオがあるなあと感じています。今回の記事が同じ悩みを抱える皆さんのためになれば幸いです。

# 参考資料

[How to reference WPF style keys defined in a separate assembly in another library - Stack Overflow](https://stackoverflow.com/questions/2184871/how-to-reference-wpf-style-keys-defined-in-a-separate-assembly-in-another-librar)
[URI 【 Uniform Resource Identifier 】 - e-Words](http://e-words.jp/w/URI.html)
[WPF におけるパッケージの URI - Microsoft](https://docs.microsoft.com/ja-jp/dotnet/framework/wpf/app-development/pack-uris-in-wpf)
[WPF のブラシの概要 - Microsoft](https://docs.microsoft.com/ja-jp/dotnet/framework/wpf/graphics-multimedia/wpf-brushes-overview)