フォームの閉じ方と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~系を使うと動作が不安定になる場合があるので、VCL、FMXフレームワークのオブジェクトの生成では注意が必要だ。
また、自動生成される定義を使わないことに注意。
#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を使って可視性を制御する。