Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

Delphi 製アプリ

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

Delphi MainForm の動き

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

macOS 風にする準備

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

q1.png

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

q2.png

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 風にするステップ

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

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

動作中

スクリーンショット 2018-03-28 19.36.11.png

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

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

まとめ

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

pik
シリアルゲームズ取締役 エンバカデロ・テクノロジーズ Delphi MVP Delphi で iOS / Android / Windows / macOS / Linux のアプリを作ったりしてるみたい。 たまに C#, .NET, Unity も使ってます。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした