本投稿は Delphi Advent Calendar 2022 #10 の記事です。
はじめに
Delphi 11.2では,GetItを使うとFMXで使うスタイルを手に入れられます。
しかし,まだWindows11のデーマがまだありません。VCLにはライトとダークのスタイルがあるのに,,
そこで,VCLのスタイルを,FMXのスタイルに変換します。
また,FMXのスタイルファイルは,テキストベースで,イメージがテキストにエンコードされています。
これをリソースとして使うとEXEファイルのサイズが大きくなってしまいます。
そこで,FMXのスタイルデザイナでFireMonkeyスタイルバイナリファイル(*.fsf)として保存し,リソースに追加したいと思います。
環境
Windows 10 Pro 21H2 / Delphi 11.2 Alexandria
Delphi Community Editionではテストしていません。
作業の手順
手順は以下のようになります。
- Windows11 Modern VCLスタイルをGetItで導入する。
- ビットマップスタイルデザイナでWindows11 Modern VCLスタイルをFMXスタイルに変換する。
- ダミーのFMXプロジェクトを作り,FireMonkeyスタイルバイナリを生成する
- スタイルを確認する。
- ダークモード切替するプロジェクトを作成する。
では始めましょう。
1. Windows11 Modern VCLスタイルをGetItで導入する。
DelphiのIDEを起動し,ツールメニューのGetItパッケージマネージャを選択します。
表示されたGetItパッケージマネージャの左ペインを以下のように設定します。
絞り込み: すべて
ソート順: 名前
カテゴリ: Styles
すると,VCL Style - Windows11Dark 1.0とVCL Style - Windows11Dark 1.0
が表示されますのでこの2つを指示に従ってインストールします。
VCL スタイルは,パブリックのドキュメントの Embarcadero\Studio\22.0\Styles にインストールされます。
2. ビットマップスタイルデザイナでWindows11 Modern VCLスタイルをFMXスタイルに変換する。
DelphiのIDEを起動し,ツールメニューのビットマップスタイルデザイナを選択します。
するとビットマップスタイルデザイナが起動します。
ファイルメニューの開くを選択して,パブリックのドキュメントの Embarcadero\Studio\22.0\Styles を開きます。
VCLのWindows11のスタイルを選択します。モードとファイル名は以下の関係になります。
モード | ファイル名 |
---|---|
ライトモード | Windows11_Modern_Light.vsf |
ダークモード | Windows11_Modern_Dark.vsf |
読み込んだ後,オブジェクトペインでImagesをクリックし,対象の画像をエクスポートして,画像を編集し更新することができます。
(試しに緑色で表示されるプログレスバーを青くしてみたところうまくいきました。)
ファイルメニューの名前を付けて保存を選択します。
ファイルの種類で**FireMonkeyスタイル(*.style)を選択して保存します。ファイル名は以下のようにしておくと分かりやすいです。
モード | ファイル名 |
---|---|
ライトモード | Win11L.style |
ダークモード | Win11D.style |
3. ダミーのFMXプロジェクトを作り,FireMonkeyスタイルバイナリを生成する。
FMXスタイルデザイナは,FMXフォームのStyleBookからでないと起動できません。
上記の作業でIDEが起動したままですので,IDEのファイルメニューの新規作成ーマルチデバイスアプリケーションを選択します。
マルチデバイスアプリケーションダイアログの空のアプリケーションを選択しOKボタンをクリックします。
フォームにStyleBookを張り付け,張り付けられたStyleBook1のアイコンをダブルクリックして,FMXスタイルデザイナを起動します。
スタイルデザイナタブの開くボタンをクリックして,前の作業で保存したFMXスタイルファイルを指定します。
スタイルデザイナタブの保存ボタンをクリックします。
ファイルの種類でFireMonkeyスタイルバイナリ(*.fsf)を選択して保存します。以下のように拡張子は必ず書いてください。
モード | ファイル名 |
---|---|
ライトモード | Win11L.fsf |
ダークモード | Win11D.fsf |
FMXスタイルデザイナを閉じます。そのとき,変更を反映させてください。
4. スタイルを確認する。
Form1のオブジェクトインスペクタで,StyleBookに変更を反映させたStyleBook1を指定すると,編集画面でフォームの外観以外のコントロールのスタイルが確認できます。
このような仮のプロジェクトファイルを使いまわせば,FMXフォームのスタイルを確認しながらFireMonkeyスタイルバイナリに変換できるでしょう。
確認できたら,プロジェクトを閉じます。
5. ダークモード切替するプロジェクトを作成する。
昨年のアドベントの記事で紹介したuThemeFMX.pasユニットを使って,ダークモード切替に津出生するアプリを作ります。
IDEのファイルメニューの新規作成-マルチデバイスアプリケーションを選択します。
IDEのファイルメニューの新規作成-ユニットを選択し,以下のコードをコピー&ペーストします。
Alexandriaで以前のコードがエラーを出していましたので修正しました。(元のコードも修正しています。)
unit uThemeFMX;
interface
uses
FMX.Forms, FMX.Platform.Win,
Winapi.Windows,Winapi.Messages;
type
TisATF=(fdAuto,fdTrue,fdFalse); // 自動と強制設定を管理する
type
TColorF=record
case Cardinal of
0: (C: Cardinal);
1: (R, G, B, A: System.Byte);
end;
var
isWindows10:boolean; // Windows10か?
AccentColor:TColorF; // アクセントカラー
function isLightTheme:boolean; // LightThemeかDarkThemeか
procedure isLightThemeSet(AisATF:TisATF);
function ThemetFormWndProc(var uMsg:DWORD;var wParam:WPARAM):boolean; // テーマ更新の監視
implementation
uses
System.SysUtils,
System.Win.Registry;
procedure isWindows10Set; // Windows10か?
begin
isWindows10:=CheckWin32Version(10, 0);
end;
function SysAccentColor:TColorF; // アクセントカラー
var
Registry: TRegistry;
C:TColorF;
begin
Result.C:=$00000000;
Registry := TRegistry.Create(KEY_READ);
try
Registry.RootKey := HKEY_CURRENT_USER;
Registry.OpenKey('Software\Microsoft\Windows\DWM', False);
C.C :=(Registry.ReadInteger('AccentColor') or $FF000000);
Result.R:=C.B;
Result.G:=C.G;
Result.B:=C.R;
Result.A:=$FF;
finally
Registry.Free;
end;
end;
procedure SysAccentColorUpdate(wParam:NativeUInt);
var
d:TColorF;
begin
d.C:=wParam or $FF000000;
AccentColor.R:=d.R;
AccentColor.G:=d.G;
AccentColor.B:=d.B;
AccentColor.A:=$FF;
end;
function isSysLightTheme:boolean; // テーマは Light / Dark か?
var
Registry: TRegistry;
begin
Result:=True;
Registry := TRegistry.Create(KEY_READ);
try
Registry.RootKey:= HKEY_CURRENT_USER;
if Registry.OpenKey('Software\Microsoft\Windows\CurrentVersion\Themes\Personalize', False) then begin
Result := (Registry.ReadInteger('AppsUseLightTheme')=1);
end;
finally
Registry.Free;
end;
end;
var
FisLightTheme:integer=1;
function isLightTheme:boolean;
begin
Result:=((FisLightTheme and 1)=1)or(not isWindows10); // Windows7は常にLightTheme
end;
procedure isLightThemeAutoSet;
begin
if isSysLightTheme then begin
FisLightTheme:=1;
end else begin
FisLightTheme:=0;
end;
end;
procedure isLightThemeSet(AisATF:TisATF); // テーマをセットする
begin
case AisATF of
fdAuto: isLightThemeAutoSet;
fdTrue: FisLightTheme:=3;
fdFalse: FisLightTheme:=2;
end;
end;
procedure isLightThemeUpdate; // 自動更新なら更新する
begin
if (FisLightTheme and 2)=0 then isLightThemeAutoSet;
end;
function AccentColorWndProc(var uMsg:DWORD;wParam:WPARAM):boolean; // アクセントカラーの監視
var
ba:boolean;
begin
ba:=uMsg = WM_DWMCOLORIZATIONCOLORCHANGED;
if ba then begin
SysAccentColorUpdate(wParam); // アクセントカラーの変更
end;
Result:=ba;
end;
function ThemetFormWndProc(var uMsg:DWORD;var wParam:WPARAM):boolean; // テーマ更新の監視
// 親フォームのWndProcで監視する
var
ba,bt:boolean;
begin
ba:=AccentColorWndProc(uMsg,wParam); // アクセントカラーの監視
bt:=uMsg = WM_SETTINGCHANGE;
if bt then begin
isLightThemeUpdate; // テーマ カラーの変更
end;
Result:=ba or bt;
end;
initialization
isWindows10Set; // isWindwos10をセット
AccentColor:=SysAccentColor; // アクセントカラー
isLightThemeUpdate; // テーマ カラーの変更
end.
uThemeFMX.pasという名前で保存します。
切り替えるFMXスタイルバイナリをリソースに読み込みます。プロジェクトメニューからリソースと画像を選びます。
以下のようにスタイルバイナリを追加し,識別子を入力します
スタイル | 識別子 |
---|---|
Win11L.fsf | Win11L |
Win11D.fsf | Win11D |
Form1にRectangleコンポーネントを追加し,Unit1を以下のように編集します。
unit Unit1;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.StdCtrls,
FMX.Edit, FMX.Controls.Presentation, FMX.Objects;
type
TForm1 = class(TForm)
ProgressBar1: TProgressBar;
Edit1: TEdit;
Button1: TButton;
Rectangle1: TRectangle;
StyleBook1: TStyleBook;
procedure FormCreate(Sender: TObject);
procedure FormShow(Sender: TObject);
private
{ private 宣言 }
public
{ public 宣言 }
procedure ThemeUpdate;
end;
var
Form1: TForm1;
implementation
{$R *.fmx}
uses
FMX.Styles, FMX.Platform.Win,
Winapi.Windows,
uThemeFMX;
var
WndProc:Pointer;
procedure ThemeUpdate(AForm:TForm1);
begin
AForm.ThemeUpdate;
end;
function ThemeWndProc( hWnd: HWND; uMsg: DWORD; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
begin
if ThemetFormWndProc(uMsg,wParam) then begin
ThemeUpdate(Form1);
end;
Result := CallWindowProc(WndProc, hWnd, uMsg, wParam, lParam);
end;
function AddThemeWndProc(const AForm: TCommonCustomForm): Boolean;
var
Wnd: HWND;
begin
Wnd := FormToHWND(AForm);
WndProc := Pointer(SetWindowLong(Wnd, GWL_WNDPROC, Integer(@ThemeWndProc)));
Exit(WndProc <> nil);
end;
{ TForm1 }
procedure TForm1.FormCreate(Sender: TObject);
begin
AddThemeWndProc(Self);
end;
procedure TForm1.FormShow(Sender: TObject);
begin
ThemeUpdate;
end;
procedure TForm1.ThemeUpdate;
var
FMXStyle: TFmxObject;
begin
if isLightTheme then begin
FMXStyle:= TStyleStreaming.LoadFromResource(HInstance,'Win11L',RT_RCDATA);
end else begin
FMXStyle:= TStyleStreaming.LoadFromResource(HInstance,'Win11D',RT_RCDATA);
end;
TStyleManager.SetStyle(FMXStyle);
Rectangle1.Fill.Color:=AccentColor.C;
Invalidate;
end;
end.
動作中でもダークモード切替が動くようにしていますが,少し不安定な気がします。単体では安定して動作しているのですが,,
デーマ切換のメッセージに反応する部分は実装しないで,起動時の未反応するようにしたほうが良いのかもしれません。
ファイルサイズは,styleファイルをリソースとして使ったときよりfsfファイルをリソースとして使った方が約600KB小さくなりました。
1つのスタイル当たり300KBの減量です。今回はVCLのスタイルを使ったためイメージファイルが大きかったのですが,もともとFMXスタイルだったものであれば,その違いはもっと小さくなるのではないかと思います。
謝辞(引用元および参考文献)
-
Windows 10 でUIをダークテーマ (ダークモード) に変更する (Windows 10 Tips) - iPentec
-
クラシック デスクトップ アプリの Windows テーマ追従 - grabacr.nét PaaS (Patchouli as a Shachiku)
-
簡単にUIの印象を変える方法 - Styleの適用 - FireMonkey と VCL (Japan) - Embarcadero Blogs
-
Delphi 10.1 BerlinのFireMonkeyアプリケーションでスタイルをリソースから読み込んで適用する - 山本隆の開発日誌
貴重な情報を提供してくださった皆様,ありがとうございます。感謝します。