imani-cの日記

WPFスタイルとテンプレートの本をAmazonで出しました。ちょっととっつきにくいこれらについて知りたいお方は、覗いてみてください。「WPF スタイル」で検索すると、トップに出ます。gRPCの本もあります。「gRPC入門」で検索するとすぐ見つかります。

WPF -ほかのファイルで定義したリソースを参照

WPFで作った画面の見た目をそろえるために使われるのがStyleなどのリソースです。 リソースは、いろいろな場所で宣言できます。

  • 各コントロールの中
  • トップのコントロールの中(Window・UserControl・Pageなど)
  • 同じプロジェクトのリソース用Xamlファイル
  • ほかのプロジェクトのリソース用Xamlファイル

各コントロールや、Xamlファイルトップのコントロール(Windowなど)に書く方法は、簡単です。しかし、画面間やプロジェクト間で見た目を統一したければ、そのような場所に書いていては統一も取りづらく、変更に対応しきれません。

ここでは、リソースをリソース用Xamlファイルに書く方法と、それを取り込む方法を紹介します。

目次

リソース用Xamlファイルの作り方

リソース用Xamlファイルは、ResourceDictionary要素だけを持つXamlファイルです。

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <Style x:Key="StackStyle" TargetType="{x:Type StackPanel}">
        <Setter Property="HorizontalAlignment" Value="Center"/>
        <Setter Property="VerticalAlignment" Value="Center"/>
        <Setter Property="Background" Value="Silver"/>
    </Style>
    <Style ... ほかのリソースを続けて書けます />
</ResourceDictionary></ResourceDictionary>

この例では、StackPanelを与えられた領域の中心に表示するスタイルを宣言しています。 ここでは一つしかリソースを宣言していませんが、当然ながら、二つ以上のリソースを宣言することもできます。

同じプロジェクトのリソースファイルを取り込む

以下のコードにより、このファイルで宣言されたリソースを取り込むことができます。上記のコードがUserResource.xamlというファイルあるものとしています。

<Window.Resources>
    <ResourceDictionary Source="UserResource.xaml"/>
</Window.Resources>

ResourceDictionary要素のSourceにファイル名を指定するだけです。

ほかのプロジェクトのリソースファイルを取り込む

リソースがほかのプロジェクトにある場合には、少しだけ面倒になります。

リソースがResourceProj1プロジェクトのResourceFile1.xamlというファイルで宣言されているものとします。 このリソースを使うには、以下の二つの作業が必要です。

まず、リソースが宣言されているプロジェクトをリソースを使うプロジェクトの参照プロジェクトに追加してください。

次に、以下のコードを書きます。

<Window.Resources>
    <ResourceDictionary Source="/ResourceProj1;component/ResourceFile1.xaml"/>
</Window.Resources>

ResourceDictionary要素のSourceに、プロジェクト名を書いています。プロジェクト名の先頭に「/」をつけると、ソリューショントップからのパスでプロジェクトを指定することになります。上記の例は、「ソリューションのトップフォルダにあるResourceProj1フォルダが含むResourceFile1.xaml」を取り込んでいます。

リソースファイルから他のリソースファイルを取り込む

リソースファイルがほかのリソースファイルを取り込むこともできます。方法は、コントロールが取り込む場合と同じです。

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <!-- 共通リソースを取り込む -->
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="/ResourceProj1;component/ResourceFile1.xaml"/>
    </ResourceDictionary.MergedDictionaries>

    <!-- 追加するリソースの宣言 -->
    <Style .../>
</ResourceDictionary>

このリソースファイルの中で取り込んだリソースを利用できます。例えば、取り込まれるResourceFile1.xamlで色などを宣言し、それを利用したスタイルをこのリソースファイルで宣言すれば、スタイルと表示色の宣言を分離して保守性を高めることができます。 なお、ResourceDictionary.MergedDictionaries要素には、複数の「」を書くことができますので、より細かくリソース宣言を分割したい場合には有効ですが、分割しすぎると保守する人が大変そうです。

取り込んだリソースに独自のリソースを追加する

取り込んだリソースに加えて、各画面独自のリソースを追加することも簡単です。

<Window.Resources>
    <ResourceDictionary>
        <!-- ほかのリソースファイルを取り込む -->
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="/ResourceProj2;component/ResourceFile2.xaml"/>
        </ResourceDictionary.MergedDictionaries>

        <!-- リソースを追加する -->
        <Style x:Key="UniformButtonStyle" TargetType="{x:Type Button}">
            <Setter Property="Width" Value="{StaticResource ControlWidth}"/>
        </Style>
    </ResourceDictionary>
</Window.Resources>

このように、ResourceDictionary.MergeDictionariesでほかのリソースを取り込んだ後に独自のリソースを追記するだけです。

すべての例を含んだサンプル

ここで説明したすべての例を含んだサンプルを以下に掲げます。

サンプルソリューションの構造は、下図のとおりです。

f:id:imani-c:20220418104234p:plain
サンプルソリューションの構造

このサンプルを試す場合には、VisualStudioでソリューションエクスプローラーのコンテキストメニューにある「追加」を使って、この図の通りにプロジェクトとファイルを追加してください。

黄色いマーカーで示したプロジェクトを各プロジェクトの参照に追加してください。忘れると、「リソースファイルが見つからない」というエラーを起こします。

青いマーカーで示したファイルは、自動生成されたものを変更する必要があります。以下がそれらのソースコードです。

ResourceProj1\ResourceFile1.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:system="clr-namespace:System;assembly=mscorlib">
    <system:Double x:Key="ControlWidth">300</system:Double>
</ResourceDictionary>

ResourceProj2\ResourceFile2.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="/ResourceProj1;component/ResourceFile1.xaml"/>
    </ResourceDictionary.MergedDictionaries>

    <Style x:Key="UniformTextBlockStyle" TargetType="{x:Type TextBlock}">
        <Setter Property="Width" Value="{StaticResource ControlWidth}"/>
    </Style>
</ResourceDictionary>

UserProj\UserResource.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Style x:Key="StackStyle" TargetType="{x:Type StackPanel}">
        <Setter Property="HorizontalAlignment" Value="Center"/>
        <Setter Property="VerticalAlignment" Value="Center"/>
        <Setter Property="Background" Value="Silver"/>
    </Style>
</ResourceDictionary>

UserProj\MainWindow.xaml

<Window x:Class="UserProj.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"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">

    <Window.Resources>
        <ResourceDictionary>
            <!-- リソース取り込み -->
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/ResourceProj2;component/ResourceFile2.xaml"/>
                <ResourceDictionary Source="UserResource.xaml"/>
            </ResourceDictionary.MergedDictionaries>

            <!-- 独自リソースを追加-->
            <Style x:Key="UniformButtonStyle" TargetType="{x:Type Button}">
                <Setter Property="Width" Value="{StaticResource ControlWidth}"/>
            </Style>
        </ResourceDictionary>
    </Window.Resources>

    <StackPanel Style="{StaticResource StackStyle}" Margin="20">
        <TextBlock Text="TextBlock with unified width" FontSize="20"
                   Style="{StaticResource UniformTextBlockStyle}"/>
        <Button Content="Button with unified width" FontSize="20"
                Style="{StaticResource UniformButtonStyle}"/>
    </StackPanel>
</Window>