2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Delphi FMXでWindows11のダークモード(ダークテーマ)の切替に追随するアプリを作る(改訂)

Posted at

この記事はDelphi 13で動作を確認しています。

はじめに

これまで,以下の2つの記事でDelphiのFMXでスタイルを使ってダークモードに対応するアプリを作成してきました。

しかし,ダークモードを煩雑に切り替えるとアプリが落ちてしまう問題が起きるようになりました。そこで,VclとFMXから利用できるダークモードとアクセントカラーを管理できるユニットを作りました。

この記事ではFMXでの利用方法について説明したいと思います。

ダークモードとアクセントカラーを管理できるユニット

以下の記事で紹介しているダークモードとアクセントカラーを管理できるユニット KP.Themes.pasをダウンロードします。

このユニットではVclとFMXで共通のダークモードの状態とアクセントカラーを取得しますが,コンパイラ指令を使ってそれぞれ別の方法で,ダークテーマとアクセントカラーの更新を検知しています。

FMXで動作するデモ

フォームデザイン

  • マルチデバイス アプリケーションを新規作成します。

  • FMXフォーム(Form1: TForm)にRectangle1: TRectangleを配置します。

  • プロジェクト - リソースと画像... で表示されるリソースダイアログから
    $(BDSCOMMONDIR)\Embarcadero\Studio\37.0\StylesにあるWindows11_Modern_Dark.StyleWindows11_Modern_Light.Styleを追加し,以下のようにタイプトプロパティを設定します。

    ファイル名 タイプ 識別子
    Windows11_Modern_Dark.Style RCDATA WIN11MD
    Windows11_Modern_Light.Style RCDATA WIN11ML

※ $(BDSCOMMONDIR)のデフォルトは
C:\Users\Public\Documents\Embarcadero\Studio\37.0 です。

VCLのスタイルをFMXに変換し,リソースファイルを小さくしてダークモード切替に追随するアプリを作るでこの操作方法について詳しく説明しています。

  • FMXフォームをFMXUnit1.pasでプロジェクトをFMXProject1.dprojで保存します。

コード

  • **interfaceのusesにKP.Themesを追加します。
interface

uses
  ・・・中略・・・,
  KP.Themes;
  • TForm1のPrivate宣言に追加
    Private宣言にダークモードとアクセントカラーの変更を検知するためのFNotifierとどちらが更新されたかを記憶するFKindを追加し,検知した際に処理するThemeChangedとアクセントカラーを処理するApplyAccentとダークモードの処理をするApplyThemeそして,更新があった際にアイドルイベントが来たらフォームを更新するOnIdleApplyの宣言を追加します。
  private
    { private 宣言 }
    FNotifier: TWinThemeNotifier;
    FKind: TThemeChangeKind;
    procedure ThemeChanged(Sender: TObject; Kind: TThemeChangeKind);
    procedure ApplyAccent;
    procedure ApplyTheme;
    procedure OnIdleApply(Sender: TObject; var Done: Boolean);
  • implementationにusesを追加
    implementation節の先頭にFmxHandleToHWNDを処理するFMX.Platform.WinTStyleManagerを処理するFMX.Stylesを追加します。
implementation

uses
  FMX.Platform.Win, // FmxHandleToHWND
  FMX.Styles;       // TStyleManager
  • OnFormCreateイベントの追加
    フォームデザイナに戻り,Form1にOnFormCreateイベントを追加し,以下のように記述します。
    1行目でメモリリークをチェックするようにしています。
    2行目でダークモードを拾うかどうかを設定するDarkModeFlagを設定しています。指定により以下のように動作します。

    TDarkModeFlag 動作
    TDarkModeFlag.dmAuto モードを自動判定する
    TDarkModeFlag.dmTrue 常にダークモード
    TDarkModeFlag.dmFalse 常にライトモード

    次にダークモードの状態とアクセントカラーを監視するコードを記述し,現在のダークモードの状態とアクセントカラーを反映させます。
    Vclと違い,監視コード側でダークモードの状態やアクセントカラーの状態が変化したときだけイベントが発生するようにしています。

procedure TForm1.FormCreate(Sender: TObject);
begin
  ReportMemoryLeaksOnShutdown := True;
  DarkModeFlag := TDarkModeFlag.dmAuto;
  FNotifier := TWinThemeNotifier.Create(FmxHandleToHWND(Handle));
  FNotifier.OnThemeChanged := ThemeChanged;
  ApplyTheme;
  ApplyAccent;
end;
  • OnFormDestroyイベントの追加
    同様に,Form1にOnFormDestroyイベントを追加し,以下のように記述します。
    監視コードを終了しています。
procedure TForm1.FormDestroy(Sender: TObject);
begin
  FNotifier.Free;
end;
  • ダークモードの状態とアクセントカラー更新のイベント処理
    ThemeChangedを以下のように記述します。
    ダークモードの状態とアクセントカラーのどちらが変更されたのかをFKindに格納し,アプリケーションのアイドルイベントを追加します。
procedure TForm1.ThemeChanged(Sender: TObject; Kind: TThemeChangeKind);
begin
  FKind := Kind;
  Application.OnIdle := OnIdleApply;
end;
  • アクセントカラーの処理
    ApplyAccentRectangle1をアクセントカラーで描画します。アクセントカラーの処理をしない場合には不要です。
procedure TForm1.ApplyAccent;
begin
  Rectangle1.Fill.Color := GetAccentColor;
end;
  • ダークモードの処理
    ApplyThemeでダークモードの状態に合わせてフォームのスタイルを変更します。
procedure TForm1.ApplyTheme;
begin
  if IsDarkMode then begin
    TStyleManager.TrySetStyleFromResource('WIN11MD')
  end else begin
    TStyleManager.TrySetStyleFromResource('WIN11ML');
  end;
end;
  • OnIdleイベント処理
    OnIdleApplyでアイドル状態になったらFKindを見てフォームを更新します。そしてアプリケーションアイドルの登録を削除します。
procedure TForm1.OnIdleApply(Sender: TObject; var Done: Boolean);
begin
  Application.OnIdle := nil;
  case FKind of
    tcTheme:  ApplyTheme;
    tcAccent: ApplyAccent;
  end;
end;

FMXUnit1.pas

この作業をまとめるとFMXUnit1.pasは以下のようになります。

FMXUnit1.pas
FMXUnit1.pas
unit FMXUnit1;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Objects,
  KP.Themes;

type
  TForm1 = class(TForm)
    Rectangle1: TRectangle;
    procedure FormDestroy(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { private 宣言 }
    FNotifier: TWinThemeNotifier;
    FKind: TThemeChangeKind;
    procedure ThemeChanged(Sender: TObject; Kind: TThemeChangeKind);
    procedure ApplyAccent;
    procedure ApplyTheme;
    procedure OnIdleApply(Sender: TObject; var Done: Boolean);
  public
    { public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}

uses
  FMX.Platform.Win, // FmxHandleToHWND
  FMX.Styles;       // TStyleManager

{ TForm1 }

procedure TForm1.FormCreate(Sender: TObject);
begin
  ReportMemoryLeaksOnShutdown := True;
  DarkModeFlag := TDarkModeFlag.dmAuto;
  FNotifier := TWinThemeNotifier.Create(FmxHandleToHWND(Handle));
  FNotifier.OnThemeChanged := ThemeChanged;
  ApplyTheme;
  ApplyAccent;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  FNotifier.Free;
end;

procedure TForm1.ThemeChanged(Sender: TObject; Kind: TThemeChangeKind);
begin
  FKind := Kind;
  Application.OnIdle := OnIdleApply;
end;

procedure TForm1.ApplyAccent;
begin
  Rectangle1.Fill.Color := GetAccentColor;
end;

procedure TForm1.ApplyTheme;
begin
  if IsDarkMode then begin
    TStyleManager.TrySetStyleFromResource('WIN11MD')
  end else begin
    TStyleManager.TrySetStyleFromResource('WIN11ML');
  end;
end;

procedure TForm1.OnIdleApply(Sender: TObject; var Done: Boolean);
begin
  Application.OnIdle := nil;
  case FKind of
    tcTheme:  ApplyTheme;
    tcAccent: ApplyAccent;
  end;
end;

end.

まとめ

FMXについては以前のものから大幅に書き換えました。ダークモードを煩雑に切り替えても落ちなくなりました。

アクセントカラーは追いかけず,ダークモードだけを追随したいなら,ApplyAccent手続きを取り除くだけで動作します。また,OnIdleの処理をまとめても良いかもしれません。

参考文献

この記事に誤りがありましたらお知らせいただければ幸いです。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?