動作環境
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 はこちらからダウンロードできます。
- Visual Studioのこのバージョンで .NET Core 3.0 を使うために、「ツール > オプション > 環境 > プレビュー機能 > Use previews of the .NET Core SDK」にチェックを入れるのを忘れないようにしてください。この設定には再起動が必要です。
概要
.NET Core 3.0 のおかげで、.NET Core 環境でも WPF を利用することができますね!私はこれからも WPF のお世話になりそうですが、未だにその全容を把握していないので苦戦することもあります。
今回は、xaml内でResourceDictionary
タグを使って定義したリソースを他の場所から利用する方法に関する記事なのですが、ただの参照ではなく、あるアセンブリのリソースを、他のアセンブリから参照して使います。
つまづきどころ
私は以下の点でつまづきました。
- xamlをリソースとしてアセンブリに埋め込む方法が分からなかった
- 他のアセンブリの埋め込みリソースをどのように参照すればよいか分からなかった
- 他のアセンブリを参照して得られる
ResourceDictionary
内のxaml要素をKeyで参照するにはどうすればよいか分からなかった
それぞれ、以下のように解決します。
- xamlファイルの「ビルド アクション」を「ページ」に設定します(デフォルトでそうなっているはず)。
- パックURI という仕組みを用いて他のアセンブリ内のリソースを参照します。
-
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
が生成されると思いますが、要らないので削除してしまいましょう
- MyApp.csproj
プロジェクト タイプを "WPF App (.NET Core)" にしないと、WPF向けの機能が使えないので注意してください。特に、 Styles
プロジェクトはこれからクラス ライブラリとして使いますが、 "クラス ライブラリ (.NET Core)" とかで作成すると上手くいきません。
1. ResourceDictionary
を定義する
プロジェクトができたら、まずは ResourceDictionary
にスタイルを定義する必要がありますね。Styles
プロジェクトに、 MyDictionary
という名前で "リソース ディクショナリ(WPF)" を新規作成し、中身は以下のようにしてみましょう。 SolidColorBrush
のスタイルを定義しています:
<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
内の要素を他のアセンブリから参照するには、以下のようにキーを定義します:
<SolidColorBrush Color="HotPink" x:Key="{ComponentResourceKey ResourceId=PinkBrush}"/>
PinkBrush
としていた部分が、複雑なマークアップに替わっていますね。 ComponentResourceKey
マークアップ拡張が今回の肝です。これにより、ResourceId
に指定したキーを使って利用側のアセンブリから参照することができるようになります。とは言っても、通常のように {DynamicResource PinkBrush}
というふうに指定できるキーが得られたわけではなく、参照側も工夫が必要になります。これについては3節で解説します。
2. 別のアセンブリ内のResourceDictionary
を読み込む
定義側ができたので、参照側を作りましょう。MyApp
プロジェクトにあるMainWindow.xaml
を編集して、ResourceDictionary
を読み込みます。
<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
と書かれている部分を書き換えなければなりません。以下のように:
<ResourceDictionary Source="pack://application:,,,/Styles;component/MyDictionary.xaml"/>
なんだか複雑な記法が出てきましたね。これは「パックURI」と呼ばれるもので、WPFにおいて他のアセンブリに埋め込まれているリソースを参照するために用いる URI です。URLのようですが、URL も URI の仕様で決められているものの一部らしいです。
この記法について解説しましょう。基本的に公式の仕様解説に従えばOKですが、ちょっと分かりやすくした画像を置いておきますね。
場面に応じて変えなければならないのは、「Styles」の部分(その時のアセンブリ名を入れる)と、「/MyDictionary.xaml」の部分(その時のパスを入れる)です。
;component
の部分、書き換えなくてよいので問題ないですが、自分にはあまり意味が分かりませんでした。
3. ResourceDictionary
内のリソースにアクセスする
最後に、ResourceDictionary
内に定義したPinkBrush
を利用してみましょう。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
に使うリソースのキーを指定している部分です。これは以下のように書き換えます:
<Rectangle Fill="{DynamicResource {ComponentResourceKey ResourceId=PinkBrush}}"/>
これは、単にリソースを定義したときに決めたキーを丸ごと、DynamicResource
マークアップ拡張のキーに与えています。MyDictionary.xaml
を書いたときにこのSolidColorBrush
に与えたキーは {ComponentResourceKey ResourceId=PinkBrush}
ですから、これを DynamicResource
マークアップ拡張に与えて、
{DynamicResource {ComponentResourceKey ResourceId=PinkBrush}}
にしているというわけです。
4. 完成!
これにて、私の MainWindow
は美しく落ち着いた色合いのウィンドウと化しました。美しく落ち着いていますね?(アッハイ)
注意点
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
URI 【 Uniform Resource Identifier 】 - e-Words
WPF におけるパッケージの URI - Microsoft
WPF のブラシの概要 - Microsoft