Url指定が鬼門

DataTemplate はxaml内に定義して、DataTemplateSelector で読み込んだりすることが定石ですが、アセンブリ内のxamlファイルからResourceDictionaryを生成して、動的にDataTemplateを作成することもできます。

通常、実行ファイル内のリソースにアクセスする場合は、下記のようになります。

1
2
3
4
5
6
7
var uri = new Uri("pack://application:,,,/Resources/DataTemplateResources.xaml", UriKind.Relative);
var info = Application.GetResourceStream(uri);
var reader = new XamlReader();
if (info != null)
{
Dictionary = reader.LoadAsync(info.Stream) as ResourceDictionary;
}

実行ファイルではなく、dllに定義してあるリソースにアクセスする場合は、

1
2
3
4
5
6
7
var uri = new Uri("ABC.DEF.GHI;component/Resources/DataTemplateResources.xaml", UriKind.Relative);
var info = Application.GetResourceStream(uri);
var reader = new XamlReader();
if (info != null)
{
Dictionary = reader.LoadAsync(info.Stream) as ResourceDictionary;
}

みたいになります。
どちらも、プロジェクトファイル直下にResoucesフォルダがあり、その直下にDataTemplateResources.xamlが存在するという形式です。
ABC.DEF.GHIはdllのファイル名です。拡張子であるdllは除去します。

xamlの中身は、

1
2
3
4
5
6
7
8
9
10
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DataTemplate x:Key="Test1">
<TextBlock Text="{TemplateBinding Content}"/>
</DataTemplate>

<DataTemplate x:Key="Test2">
<Button Content="{TemplateBinding Content}"/>
</DataTemplate>
</ResourceDictionary>

とします。
プログラムからのアクセスは

1
Dictionary?["Test1"] as DataTemplate

だけです。
この戻りをContentTemplateSelector等に設定するか、DataTemplateSelectorの戻り値に設定すれば動的にDataTemplateを使うことができます。

ここまでは普通の内容です。

LoadAsyncメソッドが例外を投げる

Uriの生成、Application.GetResourceStreamの呼び出しに成功し問題は無いように見えます。
しかし、何故か奇妙な例外を投げます。

インデックス 30 にあるバイト [FF] を指定されたコード ページから Unicode へ変換できません。

さっぱり意味がわかりません。
しかし、Unicodeという言葉に違和感を覚えました。
エディタでエンコードを確認すると、BOMの表示が…

BOMを外して、再度読み込むと例外を投げることなく読み込むことができました。

アクセス修飾子にも注意

DataTemplate内で定義したコントロールのアクセス修飾子にも注意する必要があります。
いくら、xamlが定義してあるアセンブリと同一のアセンブリに定義してあるコントロールだからといっても、そのコントロールの生成自体は、実行ファイルから生成が実行されるわけで。
つまり、DataTemplate内で使用されているコントロールは実行ファイルからアクセスできるようになっている、つまりpublicでないと下記のような例外を投げます。

System.Xaml.XamlObjectWriterException 不明な型 ‘{clr-namespace:ABC.DEF.GHI}CustomControl’ を作成できません。

XAML内の名前空間にも注意

DataTemplate内で定義した全ての要素の名前空間にも注意。
前段のアクセス修飾子での指摘の通り、要素の作成の基準はアプリが基準となるので、名前空間だけではアクセスできません。
ですので、DataTemplate内で、DataTemplateが存在するアセンブリ内の要素であったとしても、独自要素の名前空間の宣言は下記のように行う必要があります。

1
xmlns:visualizer="clr-namespace:ABC.DEF.GHI.CustomControl;assembly=ABC.DEF.GHI">

この場合は、アクセス修飾子での指摘と全く同じ例外を投げます。
確かに、意味合いとしては存在しない、あるいは見えないクラスにアクセスしようとしているのだから、言いたいことはわかりますが….

Conclusion

リソースは、リソース自身が、どのように見られるか、という意識付けをしないと、読み込むことすらできないことがわかりました。