Quantcast
Channel: C# – grabacr.nét
Viewing all articles
Browse latest Browse all 30

クラシック デスクトップ アプリの Windows テーマ追従

$
0
0

Windows には、パーソナライズの一部としてテーマ設定が存在し、「アクセント カラー」を選択することができます。 また、Windows 10 build 14316 から、アクセント カラーとは別に「app mode」として Light/Dark テーマを選択できるようになりました。

個人設定

Light/Dark

UWP アプリは SystemAccentColor などの一部のリソースを ThemeResource として指定することで、Windows のアクセント カラーやテーマによって決定される色に追従することが可能です。

一方で、クラシック デスクトップ アプリにおいては、例えば WPF ではそのようなリソースが定義されておらず、フレームワークとして Windows テーマに追従する機能は実装されていません。 今回は、クラシック デスクトップ アプリにおいて、Windows のテーマ設定に追従する方法について解説します。

ちなみに余談ですが、私は Windows デスクトップ アプリが「クラシック」と表現されていることに納得していません。

アクセント カラーの取得

アクセント カラーは DwmGetColorizationColor 関数を使用して取得します。 マネージ コードで書く場合は以下のように宣言しましょう。

public static class Dwmapi
{
    [DllImport("Dwmapi.dll")]
    public static extern void DwmGetColorizationColor([Out] out int pcrColorization, [Out] out bool pfOpaqueBlend);
}

第一引数でアクセント カラーが返ってきます。 整数型で 0xAARRGGBB のフォーマットになっていますので、以下のようにして Color 型にすると良いでしょう。

public static Color GetColorFromInt64(long color)
{
    return Color.FromArgb((byte)(color >> 24), (byte)(color >> 16), (byte)(color >> 8), (byte)color);
}

この Color 型をもとに、例えば WPF アプリでは SolidColorBrush を生成すれば、クラシック デスクトップ アプリでも Windows のアクセント カラーを取得してアプリに反映することができます。

ちなみに余談ですが、私は Windows デスクトップ アプリが「クラシック」と表現されていることに納得していません。

テーマ カラーの取得

前述のとおり、Windows 10 build 14316 から「app mode」として Light/Dark テーマを選択できるようになりました。 しかしながら、それ以前のビルドでも、設定 UI が提供されていないだけでテーマ設定自体は可能でした。

設定値はレジストリに保存されており、以下のキーと値で指定されます。
キー: HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize
値: AppsUseLightTheme

0 の場合は Dark テーマ、1 の場合は Light テーマが設定されています。 具体的にいつのビルド番号からは覚えていませんが、少なくとも Windows 10 build 10586 (4/27 時点でのリリース バージョン) では、上記レジストリ値を手動で設定することで、テーマ設定の変更が可能です (変更の適用にはサイン アウトが必要です)。

アクセント カラーと違い、テーマ設定は RGB 値などの具体的な色情報を持っていないため、Light/Dark テーマでどのような色を使用するかは、アプリ開発者が設計および実装する必要があります。

例えば、Visual Studio のように Light/Dark などのテーマ設定が存在するクラシック デスクトップ アプリにおいて、Windows のテーマ設定によって自動的に切り替えるような実装が可能となります (Visual Studio にはそのような機能は備わっていませんが…)。

ちなみに余談ですが、私は Windows デスクトップ アプリが「クラシック」と表現されていることに納得していません。

アクセント カラーの動的な変更に対する追従

これまでの内容は設定値を取得するまでで、アプリの起動中にユーザーがアクセント カラーやテーマ設定を変更した場合に対応できません。 このような動的な変更に対しては、ウィンドウ プロシージャで特定のウィンドウ メッセージに対する処理を記述することで可能となります。

アクセント カラーの変更を通知するウィンドウ メッセージは WM_DWMCOLORIZATIONCOLORCHANGED です。 wParam から 0xAARRGGBB のフォーマットで変更後の色が取得できます。

マネージ コードで書くなら、以下のようになるでしょう。

if (msg == (int)WindowsMessages.WM_DWMCOLORIZATIONCOLORCHANGED)
{
    var color = ColorHelper.GetColorFromInt64((long)wParam); // ↑ で定義したやつ

    // アクセント カラーの変更通知とか諸々

    handled = true;
}

この方法は、ウィンドウ メッセージを受信するために、1 つ以上のトップレベル ウィンドウが必要です。 WPF のウィンドウでウィンドウ プロシージャと同等のコードを実装する方法は、多くの方が既に解説されています

しかしながら、ウィンドウを持たないライブラリ等に上記の機能を実装する必要がある場合でも、以下のようにして不可視のウィンドウを生成し、メッセージを受信させることで対応可能です。

public void ShowTransparentWindow()
{
    var parameters = new HwndSourceParameters("")
    {
        Width = 1,
        Height = 1,
        WindowStyle = (int)WindowStyles.WS_BORDER,
    };
    var source = new HwndSource(parameters);
    source.AddHook(WndProc);
}

protected IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
    // ウィンドウ メッセージの受信ほげもげ
    return IntPtr.Zero;
}

HwndSource クラスは、WPF 向けに低レイヤーの Win32 機能を公開しており、HwndSourceParameters 構造体を使用してインスタンス化することで、不可視となるようなウィンドウ スタイルを指定して Win32 ウィンドウを作ることができます。

これらの手段により、クラシック デスクトップ アプリ、ならびにそのライブラリにおいて、動的に変更されるアクセント カラーを都度取得して対応することができます。

また WPF の場合、取得した色情報は SolidColorBrush としてリソース化しつつ設定変更を検知したら書き換えることにより、DynamicResource で参照すれば、UWP の ThemeResource と同様のことができます。

ちなみに余談ですが、私は Windows デスクトップ アプリが「クラシック」と表現されていることに納得していません。

テーマ カラーの動的な変更に対する追従

このセクションの内容は、Windows 10 build 14316 以降のみで使用できます。

前述のとおり、Windows 10 build 14316 ではテーマに対する設定 UI が提供され、リアルタイムで Light/Dark の切り替えが可能となりました。 アクセント カラーと同様に、テーマ設定の変更はウィンドウ メッセージで通知されるため、アクセント カラーと同じ方法で対応することができます。

テーマ設定の変更を通知するウィンドウ メッセージは WM_SETTINGCHANGE です。 lParam ならびに wParam で変更後の設定値は提供されないため、アクセント カラーと違ってメッセージを受信したら再度レジストリから取得する必要がある点に注意してください。

マネージ コードで書く場合は以下のようになるでしょう。

if (msg == (int)WindowsMessages.WM_SETTINGCHANGE)
{
    var systemParmeter = Marshal.PtrToStringAuto(lParam);
    if (systemParmeter == "ImmersiveColorSet")
    {
        // 再度レジストリから Dark/Light をとってくるとか

        handled = true;
    }
}

ちなみに余談ですが、私は Windows デスクトップ アプリが「クラシック」と表現されていることに納得していません。

MetroRadiance

今回の内容をすべて実装するのは骨が折れそうですし、例によって既にライブラリ化しておきました。

MetroRadiance に含まれる MetroRadiance.Core プロジェクトで、以下を実装しています。

  • アクセント カラーの取得
  • アクセント カラーの変更への追従
  • テーマ設定の取得
  • テーマ設定の変更への追従
  • ライブラリでウィンドウ メッセージ受信してあれこれ (HwndSource で不可視ウィンドウあれこれ) の例

同梱されているサンプル アプリ (MetroRadiance.Showcase) で UI をテーマ設定に追従させた様子が以下になります。 左側が自作アプリ、右側が Windows 10 14328 の設定アプリです。

wpf

実装例として参考にして頂ければと思います。 もしくは、NuGet から MetroRadiance.Core 2.2.0 以降をインストールして使ってみてください。

使用方法は GitHub の readme をご覧隊頂ければ。 また不明点や不具合等ありましたら @Grabacr07 までお願いします。

おわりに

私は Windows デスクトップ アプリが「クラシック」と表現されていることに納得していません。


Viewing all articles
Browse latest Browse all 30

Trending Articles