ダークモード対応

提供: MeryWiki
ナビゲーションに移動 検索に移動

Windows 10 のダークモードに対応するためには?

現時点 (Windows 10 1809) では、Windows 10 のダークモードに対応するためには UWP アプリケーションである必要があります。

しかし、Windows 10 1809 では explorer.exe がダークモードに対応したことで、Win32 アプリケーションのダークモード対応も徐々に開発されているような気がします。

Windows 10 1809 ではいくつかの非公開 API の存在が確認されており、これらを呼び出すことで Win32 アプリケーションでもダークモードに対応できる可能性が見えてきました。

Windows 10 のダークモードの隠し API 研究

下記は Delphi でダークモードの API を呼び出す実験で作成した Delphi XE2 のソースコード (雑) です。

アプリケーションの起動時に 1 度、AllowDarkModeForApp を呼び出しておけばメインメニューなどは Windows 10 のダークモードの設定の ON・OFF に連動して色が変わりました。

また、AllowDarkModeForWindow を使用すれば TButton や TComboBox などに対してもダークモードを有効にできるらしいのですが、私の環境ではうまく動きませんでした。

unit DarkModeClasses;

interface

uses
{$IF CompilerVersion > 22.9}
  Winapi.Windows, System.SysUtils;
{$ELSE}
  Windows, SysUtils;
{$IFEND}


const
  THEME_LIB = 'uxtheme.dll';

type
  TShouldAppsUseDarkMode = function: BOOL; stdcall;
  TAllowDarkModeForWindow = function(hwnd: HWND; allow: BOOL): BOOL; stdcall;
  TAllowDarkModeForApp = function(allow: BOOL): BOOL; stdcall;
  TFlushMenuThemes = procedure; stdcall;
  TRefreshImmersiveColorPolicyState = procedure; stdcall;

var
  FHandle: THandle;
  FLoaded: Boolean;
  RefreshImmersiveColorPolicyState: TRefreshImmersiveColorPolicyState;
  ShouldAppsUseDarkMode: TShouldAppsUseDarkMode;
  AllowDarkModeForWindow: TAllowDarkModeForWindow;
  AllowDarkModeForApp: TAllowDarkModeForApp;
  FlushMenuThemes: TFlushMenuThemes;
  DarkModeSupported: Boolean;

implementation

function DarkModeLoadLibrary: Boolean;
begin
  if CheckWin32Version(10) and (TOSVersion.Build >= 17763) then
    FHandle := LoadLibrary(PChar(THEME_LIB));
  Result := FHandle <> 0;
  if Result then
  begin
    @RefreshImmersiveColorPolicyState := GetProcAddress(FHandle, MakeIntResource(104));
    @ShouldAppsUseDarkMode := GetProcAddress(FHandle, MakeIntResource(132));
    @AllowDarkModeForWindow := GetProcAddress(FHandle, MakeIntResource(133));
    @AllowDarkModeForApp := GetProcAddress(FHandle, MakeIntResource(135));
    @FlushMenuThemes := GetProcAddress(FHandle, MakeIntResource(136));
    if (@RefreshImmersiveColorPolicyState <> nil) and
      (@ShouldAppsUseDarkMode <> nil) and (@AllowDarkModeForWindow <> nil) and
      (@AllowDarkModeForApp <> nil) and (@FlushMenuThemes <> nil) then
      DarkModeSupported := True;
  end;
end;

procedure DarkModeFreeLibrary;
begin
  if FHandle <> 0 then
    FreeLibrary(FHandle);
  FHandle := 0;
end;

initialization

if not FLoaded then
  FLoaded := DarkModeLoadLibrary;

finalization

if FLoaded then
  DarkModeFreeLibrary;

end.

TButton や TComboBox などをダークモードに対応させる

Winapi.UxTheme の SetWindowTheme メソッドを使用してダークモードのテーマを割り当ててやることで、一部のコンポーネントはダークモードに対応させることができるようです。

SetWindowTheme(Handle, 'DarkMode_Explorer', nil);

しかしながら まだ情報が公開されていないこともあって 'DarkMode_Explorer' の部分は謎ですが、例えば TTreeView や TButton ですと 'DarkMode_Explorer'、TComboBox ですと 'DarkMode_CFD'、TListView ですと 'DarkMode_Explorer' か 'DarkMode_ItemsView' でダークモードが適用されるようです。

タイトルバーをダークモードに対応させる

ウィンドウのタイトルバーをダークモードに対応させるのは簡単で、DwmSetWindowAttribute を FormShow などで呼び出します。

var
  LDark: BOOL;
begin
  LDark := True;
  DwmSetWindowAttribute(Handle, 19, @LDark, SizeOf(LDark));

Mery のダークモード対応

上記の方法を使うと Win32 アプリケーションでも、ある程度はダークモードに対応することができます。

dark-mode-1.png

まだ、TCoolBar や TToolBar は挙動不審、TStatusBar、TPageControl、TCheckBox、TRadioButton などは未対応ということで実用レベルには達していませんが、今後、Microsoft さんが対応を進めてくれることを望みます。

dark-mode-2.png

上の画像の通り、TListView はカラムヘッダーの文字色が黒のままだったり、TCheckBox や TGroupBox の文字の色が黒のままだったりしますが、こういった部分は見た目を気にしなければ OwnerDraw や WM_PAINT で頑張ることで一応、色を変えることは出来そうな気もします。

注意事項

この記事の内容は Windows 10 1809 のみで動作するであろう非公開 API を使用した裏技的な方法ですので、今後、API の仕様が変更されたりそもそも使用できなくなる可能性もありますから、実験目的以外では使用しないほうが良いと思います。

その他のアプローチ

Delphi の VCL スタイルを使う

Delphi 10.3 Rio だと VCL スタイルの動作はある程度安定してきてはいるものの、Per-Monitor DPI 対応は今ひとつです。

dark-mode-3.png

スタイルのリソースを exe に内蔵することになるのでファイルサイズの肥大化と起動速度の低下が問題。

dark-mode-4.png

隠し API を使いつつ Microsoft が対応してくれなさそうな部分のみ自前で実装する

TCoolBar、TToolBar、TPageControl、TCheckBox、TRadioButton、TGroupBox などは現在のところ隠し API ではダークモードに対応していないようなので自前で実装してみます。

dark-mode-5.png

Windows の描画を横取りしてすべて自前で描画する作戦。これだと Delphi の VCL スタイルを使うよりも高速に動作するし、実行ファイルのサイズも肥大化しません。

dark-mode-6.png

問題は、開発が非常に難しいということです。

  • 2019/08/09
基本的なコンポーネントは自前でダークモードっぽく描画するようにしてみました。

dark-mode-7.png

スポンサーリンク