Delphi 製アプリ
やあ、みなさん!
今日もモリモリ Delphi でアプリを作っていますか!?
僕はボチボチ作っています。
Delphi MainForm の動き
Delphi で色々な OS 用のアプリを作れますが、気になるのが macOS での動き。
Delphi アプリは MainForm が閉じるとアプリが終了してしまうのです。
個人的には、それで構わないと思うのですが、macOS ユーザーにとって、それは不自然な動きになります。
つまり MainForm が閉じてもアプリは生存し続けるべきなのです。
macOS 風にする準備
では、MainForm が閉じても終わらないようにしてみましょう。
ただし、メニューからは終わらせられる必要があるので MainMenu が必須になります。
現在の TMainMenu は一番左側のメニューがアプリケーションメニューとして表示されるので、一番左側をアプリケーションメニューにしたメニューを作っておきます。
さらに、非表示になった MainForm を再度表示させるために「ウィンドウ」メニューに「表示」というメニューも作っておきます。ここは「ファイル」メニューの「新規」として新たに MainForm を作る、という方法でも大丈夫ですが、今回は一番簡単な方法を示します。
さらに menuAppQuit のショートカットキーを Cmd+Q に設定すると良いでしょう。
menuAppQuit の OnClick イベントハンドラは次のようにして終了できるようにします。
Close ではなく Applicatio.Terminate を使うところがポイントです。
また同様に「表示」メニューには Show を書いて MainForm を表示するようにします。
procedure TfrmMain.menuAppQuitClick(Sender: TObject);
begin
Application.Terminate;
end;
procedure TfrmMain.menuWindowShowClick(Sender: TObject);
begin
Show;
end;
macOS 風にするステップ
最も簡単な手順は以下の通りです。
- ダミーフォームを作ります (TDummyMain とします)
- ドックのコンテキストメニューの「隠す」で隠したあと表示させると TDummyMain が見えてしまうので、それを防ぐために TDummyMain.CanShow を override して False を返すようにします。
- MainForm.OnClose にイベントハンドラを設定して Action := TCloseAction.caHide; としてウィンドウ左上のボタンを押してもフォームを閉じないようにします。
- Application.MainForm を TDummyMain のインスタンスで置き換えます
- 元の MainForm に載っていた MainMenu を TDummyMain に載せ替えます。
と、このような形になります。
これを全て実装した物が以下です。
(*
* Supports macOS style behavior
*
* PLATFORMS
* macOS
*
* LICENSE
* Copyright (c) 2018 HOSOKAWA Jun
* Released under the MIT license
* http://opensource.org/licenses/mit-license.php
*
* HOW TO USE
* uses
* PK.Utils.MacDummyMain;
*
* type
* TForm1 = class(TForm)
* procedure FormCreate(Sender: TObject);
* end;
*
* procedure TForm1.FormCreate(Sender: TObject);
* begin
* ReplaceMainFormForMac;
* end;
*
* 2018/03/28 Version 1.0.0
* 2018/03/29 Version 1.0.1
* Programmed by HOSOKAWA Jun (twitter: @pik)
*)
unit PK.Utils.MacDummyMain;
interface
procedure ReplaceMainFormForMac;
implementation
uses
System.Classes
, System.UITypes
, System.SysUtils
, System.Rtti
, FMX.Types
, FMX.Forms
, FMX.Menus
;
type
TOpenForm = class(TCommonCustomForm)
end;
TDummyMain = class(TCommonCustomForm)
protected
procedure MainFormClose(Sender: TObject; var Action: TCloseAction);
function CanShow: Boolean; override;
public
constructor CreateNew(Owner: TComponent; Dummy: NativeInt = 0); override;
end;
{ TDummyMain }
function TDummyMain.CanShow: Boolean;
begin
Result := False;
end;
constructor TDummyMain.CreateNew(Owner: TComponent; Dummy: NativeInt = 0);
begin
inherited;
TThread.ForceQueue(
TThread.Current,
procedure
var
MainForm: TOpenForm;
Obj: TObject;
MM: TMainMenu absolute Obj;
RttiType: TRttiType;
RttiField: TRttiField;
begin
// MainForm 取得
MainForm := TOpenForm(Application.MainForm);
// OnClose を変更して閉じないようにする
MainForm.OnClose := MainFormClose;
// 自分を MainForm に指定
Application.MainForm := Self;
// Menu を載せ替える
Obj := MainForm.MainMenu;
if (Obj <> nil) and (Obj is TMainMenu) then
begin
AddObject(MM);
RttiType := SharedContext.GetType(ClassType);
if RttiType <> nil then
begin
RttiField := RttiType.GetField('FMainMenu');
if RttiField <> nil then
RttiField.SetValue(Self, MM);
end;
TMainMenu(MM).RecreateOsMenu;
end;
end
);
end;
procedure TDummyMain.MainFormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := TCloseAction.caHide;
end;
procedure ReplaceMainFormForMac;
begin
{$IFDEF OSX}
TDummyMain.CreateNew(Application);
{$ELSE}
// 他の OS では何もしない
{$ENDIF}
end;
end.
使い方
PK.Utils.MacDummyMain を uses して FormCreate で CreateDummyMainForm を呼ぶだけです。
implementation
{$R *.fmx}
uses
PK.Utils.MacDummyMain;
procedure TfrmMain.FormCreate(Sender: TObject);
begin
ReplaceMainFormForMac;
end;
動作中

この動作中のスクリーンショットを見ても解りませんが…
左上の赤いボタンを押してもフォームが消えるだけで、アプリケーションは終了しません。
他にもフォームを出すようなアプリケーションであれば、「ファイル」メニューなどを作り「新規xxx」といったメニューでフォームを呼び出すようにすると、より macOS 風の動作になるでしょう。
まとめ
macOS の動きに合わせる場合は PK.Utils.MacDummyMain を使うと簡単です。
ですが、MainForm が閉じられると同時にアプリケーションが終わっても、個人的にはアリです。