Delphi 製アプリを macOS 風の動きにする

# Delphi 製アプリ
やあ、みなさん！
今日もモリモリ Delphi でアプリを作っていますか！？
僕はボチボチ作っています。

## Delphi MainForm の動き
Delphi で色々な OS 用のアプリを作れますが、気になるのが macOS での動き。
Delphi アプリは MainForm が閉じるとアプリが終了してしまうのです。
個人的には、それで構わないと思うのですが、macOS ユーザーにとって、それは不自然な動きになります。
つまり MainForm が閉じてもアプリは生存し続けるべきなのです。

## macOS 風にする準備
では、MainForm が閉じても終わらないようにしてみましょう。
ただし、メニューからは終わらせられる必要があるので MainMenu が必須になります。
現在の TMainMenu は一番左側のメニューがアプリケーションメニューとして表示されるので、一番左側をアプリケーションメニューにしたメニューを作っておきます。
さらに、非表示になった MainForm を再度表示させるために「ウィンドウ」メニューに「表示」というメニューも作っておきます。ここは「ファイル」メニューの「新規」として新たに MainForm を作る、という方法でも大丈夫ですが、今回は一番簡単な方法を示します。

![q1.png](https://qiita-image-store.s3.amazonaws.com/0/12977/9aa63d03-7936-6f4c-ce4a-e6a6bd43f1fa.png)

さらに menuAppQuit のショートカットキーを Cmd+Q に設定すると良いでしょう。

![q2.png](https://qiita-image-store.s3.amazonaws.com/0/12977/583badd7-8ff1-a1d5-5426-76f7b640fd3c.png)

menuAppQuit の OnClick イベントハンドラは次のようにして終了できるようにします。
Close ではなく Applicatio.Terminate を使うところがポイントです。
また同様に「表示」メニューには Show を書いて MainForm を表示するようにします。

```Pascal
procedure TfrmMain.menuAppQuitClick(Sender: TObject);
begin
  Application.Terminate;
end;

procedure TfrmMain.menuWindowShowClick(Sender: TObject);
begin
  Show;
end;
```

## macOS 風にするステップ

最も簡単な手順は以下の通りです。

1. ダミーフォームを作ります (TDummyMain とします)
1. ドックのコンテキストメニューの「隠す」で隠したあと表示させると TDummyMain が見えてしまうので、それを防ぐために TDummyMain.CanShow を override して False を返すようにします。
1. MainForm.OnClose にイベントハンドラを設定して Action := TCloseAction.caHide; としてウィンドウ左上のボタンを押してもフォームを閉じないようにします。
1. Application.MainForm を TDummyMain のインスタンスで置き換えます
1. 元の MainForm に載っていた MainMenu を TDummyMain に載せ替えます。

と、このような形になります。

これを全て実装した物が以下です。

```pascal
(*
 * 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 を呼ぶだけです。

```pascal
implementation

{$R *.fmx}

uses
  PK.Utils.MacDummyMain;

procedure TfrmMain.FormCreate(Sender: TObject);
begin
  ReplaceMainFormForMac;
end;
```
## 動作中

<img width="388" alt="スクリーンショット 2018-03-28 19.36.11.png" src="https://qiita-image-store.s3.amazonaws.com/0/12977/ecf17494-cdfd-9a04-17c0-692a76674e90.png">


この動作中のスクリーンショットを見ても解りませんが…
左上の赤いボタンを押してもフォームが消えるだけで、アプリケーションは終了しません。

他にもフォームを出すようなアプリケーションであれば、「ファイル」メニューなどを作り「新規xxx」といったメニューでフォームを呼び出すようにすると、より macOS 風の動作になるでしょう。

# まとめ
macOS の動きに合わせる場合は PK.Utils.MacDummyMain を使うと簡単です。
ですが、MainForm が閉じられると同時にアプリケーションが終わっても、個人的にはアリです。