フォームの閉じ方とModalResultの設定について

VCLでフォームを表示させ終了時にフラグで処理を変更したいときにフォームのModalResultを参照するケースがある。
ただし、ModalResultの設定については注意しなければいけない点があるのでメモ。
また、フォームの閉じ方についてもメモ。

ShowModalで表示させたフォームの閉じ方

ShowModal()でフォームを呼び出した場合、フォームを閉じるには2つの方法がある。
1.Close()関数を呼ぶ。フォームのModalResultにはmrCancelがセットされる。
2.ModalResultにmrNone以外をセットする。

2の方法はTButtonなどではそのModalResultプロパティをセットするとコードレスで閉じられる。セットされたオブジェクトのOnClickイベントに何か記述している場合は、そのコードの実行後、自動で閉じられる。
メニュー(TMenuItem)にはModalResultプロパティがないので、OnClickイベントでModalResultにセットする。

void __fastcall TForm1::MenuItem1Click( TObject* Sender)
{
  ModalResult = mrOk;
}

余計なお節介で、Close()を記述するとModalResultがmrCancelになってしまうのでModalResultのセットで閉じる場合には注意する。

void __fastcall TForm1::MenuItem1Click( TObject* Sender)
{
  ModalResult = mrOk;
  Close(); // ここでModalResultにmrCancelがセットされる。
}

自動生成を使わずにフォームを作成した場合は、フォームを閉じて終了処理を行った後は、フォームオブジェクトの廃棄をすること。
再突入でフォームオブジェクトの複数コピーが発生するのを避けるためだ。

bool show_form1( TForm* pParent )
{
  Form1 = new TForm1( pParent );

  // 初期化処理を記述

  TModalResult mrRet = Form1->ShowModal();
  
  // 終了時処理を記述

  delete Form1; // 忘れず記述

  return true;
}

std::unique_ptrを使う場合は、make_uniqueを使うとおかしな挙動を示す場合があるので、無駄があるがnewの代入で行う。
自分の経験ではTObjectの派生クラスでstdのmake~系を使うと動作が不安定になる場合があるので、VCLFMXフレームワークのオブジェクトの生成では注意が必要だ。
また、自動生成される定義を使わないことに注意。

#include <memory>
bool show_form1( TForm* pParent )
{
  std::uniqe_ptr<TForm1> pForm( new TForm1( pParent ) );

  // 初期化処理を記述

  TModalResult mrRet = pForm->ShowModal();
  
  // 終了時処理を記述

  //delete pForm; // 不要

  return true;
}

複数回突入が仮定され、フォームを使い回す場合は次のようなコードになる。

TForm1* Form1 = nullptr; // bcc32.exeを使う場合はnullptrの替わりにNULL
bool show_form1( TForm* pParent )
{
  if( !Form1 )
    Form1 = new TForm1( pParent );

  // 初期化処理を記述

  TModalResult mrRet = Form1->ShowModal();
  
  // 終了時処理を記述

  return true;
}

個人的には気持ちが悪いのでこの方法は使わない。

Showで表示させたフォームの閉じ方

まず、Showで表示させた場合、ModalResultをセットしても閉じられないことに注意。
そのためClose()関数を必ず呼ぶ必要がある。
自動生成を使わずにフォームを作成した場合は、フォームオブジェクトの廃棄コードをOnCloseイベントに記述する。
ヘルプで、何やらややこしいコードが示されているが、フォームオブジェクトの自動定義と初期化を使えば下記のコードで済む。

TForm1* Form1 = nullptr; // bcc32.exeを使う場合はnullptrの替わりにNULL
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
  // 終了時処理をここに記述

  Action = TCloseAction::caFree;
  Form1 = nullptr;
}

bool show_form1( TForm* pParent );
{
  if( Form1 == nullptr )
  {
    Form1 = new TForm1( pParent );
    // 初期化処理
  }

  Form1->Show();

  return true;
}

show_form1の戻り値では呼び出し元に処理結果を渡せないので、終了時処理で呼び出し元を呼び出す。
おすすめは中継関数を使って呼び出し元に値を返して、変更を反映させる方法だ。

// 呼び出し元 ParentForm.h
class TParentForm : public TForm
{
  //<中略>

  public:
    void update();
};
//---------------------------------------------------------------------------
extern PACKAGE TParentForm* ParentForm;
//---------------------------------------------------------------------------
#endif

// 中継関数定義ファイル CnParent.h
void parent_update();

// 中継関数定義ファイル CnParent.cpp
#include "ParentForm.h"
void parent_update()
{
  ParentForm->update();
}

// 呼び出し先 Form1.cpp
#include "CnParent.h"
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
  // 終了時処理をここに記述
  parent_update();

  Action = TCloseAction::caFree;
  Form1 = nullptr;
}

中継関数を別ファイルで定義して使うメリットは、フォーム間の依存関係を最小限にできることだ。
update()がパブリックなのが気になるのであれば、friendを使って可視性を制御する。