この記事は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.StyleとWindows11_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.WinとTStyleManagerを処理する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;
-
アクセントカラーの処理
ApplyAccentでRectangle1をアクセントカラーで描画します。アクセントカラーの処理をしない場合には不要です。
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
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の処理をまとめても良いかもしれません。
参考文献
- Windows 10 でUIをダークテーマ (ダークモード) に変更する (Windows 10 Tips) - iPentec
- 簡単にUIの印象を変える方法 - Styleの適用 - FireMonkey と VCL [Japan] - Embarcadero Blogs
- Delphi 10.1 BerlinのFireMonkeyアプリケーションでスタイルをリソースから読み込んで適用する - 山本隆の開発日誌
- FireMonkey と Windows Qiita - @pik
-
DocWiki FMX.Forms.TApplication.OnIdle
貴重な情報を提供してくださった皆様,この場を借りて感謝いたします。
この記事に誤りがありましたらお知らせいただければ幸いです。