9
2

VCLのスタイルをFMXに変換し,リソースファイルを小さくしてダークモード切替に追随するアプリを作る

Last updated at Posted at 2022-12-02

本投稿は 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ではテストしていません。

作業の手順

手順は以下のようになります。

  1. Windows11 Modern VCLスタイルをGetItで導入する。
  2. ビットマップスタイルデザイナでWindows11 Modern VCLスタイルをFMXスタイルに変換する。
  3. ダミーのFMXプロジェクトを作り,FireMonkeyスタイルバイナリを生成する
  4. スタイルを確認する。
  5. ダークモード切替するプロジェクトを作成する。

では始めましょう。

1. Windows11 Modern VCLスタイルをGetItで導入する。

DelphiのIDEを起動し,ツールメニューのGetItパッケージマネージャを選択します。
表示されたGetItパッケージマネージャの左ペインを以下のように設定します。
 絞り込み: すべて
 ソート順: 名前
 カテゴリ: Styles
すると,VCL Style - Windows11Dark 1.0VCL Style - Windows11Dark 1.0
が表示されますのでこの2つを指示に従ってインストールします。
VCL スタイルは,パブリックのドキュメントの Embarcadero\Studio\22.0\Styles にインストールされます。

2. ビットマップスタイルデザイナでWindows11 Modern VCLスタイルをFMXスタイルに変換する。

DelphiのIDEを起動し,ツールメニューのビットマップスタイルデザイナを選択します。
Snap0001.png
するとビットマップスタイルデザイナが起動します。
Snap0002.png
ファイルメニューの開くを選択して,パブリックのドキュメントの Embarcadero\Studio\22.0\Styles を開きます。
Snap0003.png
VCLのWindows11のスタイルを選択します。モードとファイル名は以下の関係になります。

モード ファイル名
ライトモード Windows11_Modern_Light.vsf
ダークモード Windows11_Modern_Dark.vsf

読み込んだ後,オブジェクトペインでImagesをクリックし,対象の画像をエクスポートして,画像を編集し更新することができます。
(試しに緑色で表示されるプログレスバーを青くしてみたところうまくいきました。)
ファイルメニューの名前を付けて保存を選択します。
Snap0004.png
ファイルの種類で**FireMonkeyスタイル(*.style)を選択して保存します。ファイル名は以下のようにしておくと分かりやすいです。
Snap0017.png

モード ファイル名
ライトモード Win11L.style
ダークモード Win11D.style

3. ダミーのFMXプロジェクトを作り,FireMonkeyスタイルバイナリを生成する。

FMXスタイルデザイナは,FMXフォームのStyleBookからでないと起動できません。
上記の作業でIDEが起動したままですので,IDEのファイルメニューの新規作成マルチデバイスアプリケーションを選択します。
Snap0005.png
マルチデバイスアプリケーションダイアログの空のアプリケーションを選択しOKボタンをクリックします。
Snap0006.png
フォームにStyleBookを張り付け,張り付けられたStyleBook1のアイコンをダブルクリックして,FMXスタイルデザイナを起動します。
Snap0007.png
スタイルデザイナタブの開くボタンをクリックして,前の作業で保存したFMXスタイルファイルを指定します。
Snap0008.png
スタイルデザイナタブの保存ボタンをクリックします。
Snap0009.png
ファイルの種類でFireMonkeyスタイルバイナリ(*.fsf)を選択して保存します。以下のように拡張子は必ず書いてください。
Snap0016.png

モード ファイル名
ライトモード Win11L.fsf
ダークモード Win11D.fsf

FMXスタイルデザイナを閉じます。そのとき,変更を反映させてください。

4. スタイルを確認する。

Form1のオブジェクトインスペクタで,StyleBookに変更を反映させたStyleBook1を指定すると,編集画面でフォームの外観以外のコントロールのスタイルが確認できます。
Snap0012.png 編集画面
Snap0013.png 実行画面
このような仮のプロジェクトファイルを使いまわせば,FMXフォームのスタイルを確認しながらFireMonkeyスタイルバイナリに変換できるでしょう。
確認できたら,プロジェクトを閉じます。

5. ダークモード切替するプロジェクトを作成する。

昨年のアドベントの記事で紹介したuThemeFMX.pasユニットを使って,ダークモード切替に津出生するアプリを作ります。
IDEのファイルメニューの新規作成マルチデバイスアプリケーションを選択します。
IDEのファイルメニューの新規作成ユニットを選択し,以下のコードをコピー&ペーストします。
Alexandriaで以前のコードがエラーを出していましたので修正しました。(元のコードも修正しています。)

uThemeFMX.pas
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スタイルバイナリをリソースに読み込みます。プロジェクトメニューからリソースと画像を選びます。
Snap0014.png
以下のようにスタイルバイナリを追加し,識別子を入力します

スタイル 識別子
Win11L.fsf Win11L
Win11D.fsf Win11D

Snap0015.png

Form1にRectangleコンポーネントを追加し,Unit1を以下のように編集します。

unit1.pas
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スタイルだったものであれば,その違いはもっと小さくなるのではないかと思います。

謝辞(引用元および参考文献)

貴重な情報を提供してくださった皆様,ありがとうございます。感謝します。

9
2
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
9
2