こんにちは、トイロジックでツール業務を担当しているプログラマーのIです。主にプロジェクトで使用するGUI/CUIツールやDCCツールの作成・整備を行っています。本記事では少しマニアックな内容をお届けすることになるかと思います。

皆さんはWPFで使用するカラーを直接指定するか、カラーを定義したResourceDictionaryを用意していませんか? 弊社のツールでも以前はResourceDictionaryにてカラーを管理して毎回「ResourceDictioanry.MergedDictionaries」にてマージしていましたが、毎回マージしないといけない手間と、インテリセンスの分かりづらさから悩んでいる部分ではありました。

本記事では、ResourceDictionary を使用してカラーを管理・設定から、マークアップ拡張を使用してカラーを指定する方法をご紹介できればと思います。

マークアップ拡張って何?

マークアップ拡張(※1)というのはプリミティブでも特定のXAML型でもない値を取得するためのXAMLの手法です。簡単に言うとXAMLにて、プロパティの値を設定する際に使用する { で始まり } で終わる中身の部分のことですね。

例えば、ビューモデルのプロパティをバインディングする際に使用する「Binding」や、定義されているリソースを取得する「StaticResource」や「DynamicResource」がマークアップ拡張になります。

このマークアップ拡張は「MarkupExtension」クラスを継承したサブクラスを作成することで、独自で作成することが可能です。

※1:XAML のマークアップ拡張機能の概要 – XAML | Microsoft Learn
 

カラーを指定するマークアップ拡張を作成するために必要なものは?

何が必要なのかは逆算していけば何となくわかるかと思います。

まずは、カラーを取得するためのマークアップ拡張クラス。そして、カラーを管理するクラスも必要です。マークアップ拡張でカラーを指定する際のカラーの一覧のEnumが必要ですね。

とりあえず以下があればなんとかなりそうです。

  • カラーの一覧のEnum
  • カラーを管理するクラス
  • カラーを取得するためのマークアップ拡張クラス

 

カラーを指定するマークアップ拡張の実装を見ていきましょう

上記に上げていた必要なものをそれぞれ実装していきましょう。
 

カラーの一覧のEnum

これは簡単ですね。使用するカラーを定義するEnumを作成しましょう。今回は「ColorPack」というEnumを作ることにします。


public enum ColorPack
{
    /// <summary>
    /// 背景色.
    /// </summary>
    Background,
 
    /// <summary>
    /// 前景色.
    /// </summary>
    Foreground,
 
    /// <summary>
    /// ボーダー色.
    /// </summary>
    Border,
}

 

カラーを管理するクラス

次に先ほど作成したEnumごとのカラーをテーブルと、Enumからカラーを取得するためのメソッドを持ったクラスを作成しましょう。

「ColorPackManager」という名前のクラスを作ることにします。


public static class ColorPackManager
{
    private static readonly FrozenDictionary<ColorPack, Brush> ColorMap = new Dictionary<ColorPack, Brush>
    {
        [ColorPack.Background] = new SolidColorBrush(Color.FromArgb(0xFF, 0x1D, 0x1F, 0x21)),
        [ColorPack.Foreground] = new SolidColorBrush(Color.FromArgb(0xFF, 0xD5, 0xD5, 0xD5)),
        [ColorPack.Border] = new SolidColorBrush(Color.FromArgb(0xFF, 0x4D, 0x4D, 0x4D)),
    };
 
    /// <summary>
    /// カラーを返します.
    /// </summary>
    /// <param name="kind">カラーの種類.</param>
    /// <return>カラーが返ります.</return>
    public static Brush GetColor(ColorPack kind)
    {
        if (ColorMap.TryGetValue(kind, out var brush))
            return brush;
 
        throw new NotSupportedException();
    }
}

 

カラーを取得するマークアップ拡張クラス

先ほど作成した「ColorPack」「ColorPackManager」を使用してカラーを取得するためのマークアップ拡張クラスを作成しましょう。「ColorPackExtension」という名前のクラスを作ることにします。


[MarkupExtensionReturnType(typeof(Brush))]
public sealed class ColorPackExtension : MarkupExtension
{
    /// <summary>
    /// 定義されたカラー.
    /// </summary>
    public ColorPack Kind { get; }
 
    /// <summary>
    /// 初期化します.
    /// </summary>
    /// <param name="kind">定義されたカラー.</param>
    public ColorPackExtension(ColorPack kind)
    {
        Kind = kind;
    }
 
    /// <summary>
    /// このマークアップ拡張機能のターゲット プロパティの値として提供されるオブジェクトを返します.
    /// </summary>
    /// <param name="serviceProvider">マークアップ拡張機能のサービスを提供できるサービス プロバイダーのヘルパー.</param>
    /// <returns>拡張機能が適用されたプロパティで設定するオブジェクト値が返ります.</returns>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return ColorPackManager.GetColor(Kind);
    }
}

 

実際に使用してみる

先ほど作成したマークアップ拡張クラスを実際に使用してみましょう。


<UserControl x:Class="SampleView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:core="..."
             mc:Ignorable="d"
             d:DesignHeight="450" d:DesignWidth="800">
 
    <Border Margin="5"
            Background="{core:ColorPack Background}"
            BorderBrush="{core:ColorPack Border}"
            BorderThickness="1">
        <TextBlock Text="テスト!" Foreground="{core:ColorPack Foreground}"/>
    </Border>
</UserControl>

※ 定義している core 名前空間の定義は実装しているフォルダ構成によって変わるためそれぞれで変える必要があります (名前空間のエイリアスもそれぞれで変えて大丈夫です)

このように実際にカラーを指定する際はEnumの値を指定することができますし、インテリセンスが機能しますのですぐに指定したい色を探し出すことができます。また、カラーリソースを定義したResourceDictionaryをマージする必要が無いため、リソースの読み込み、リソースサイズも減らすことができます。

 

最後に

本記事では、マークアップ拡張を使用したカラーの指定の実装に関して少しわかりやすいように変えてご紹介させていただきました。弊社では、カラー以外に画像リソースなどもResourceDictionaryでは管理せずに、マークアップ拡張を使用して指定するようにしています。

少しマニアックな内容になりましたが、少しでもお役に立てると幸いです。

 

著者紹介 I
2015年にトイロジックに新卒入社。プログラマーとして複数のプロジェクトでDCCツールの作成、UI、システム周りを経験し、現在はライブラリ班でツール業務を担当しています。

トイロジックでは現在、一緒に働くプログラマーを募集しています。

不明点などもお気軽にお問い合わせくださいフルリモート採用も行っております、ご応募お待ちしております!