.NET Fx:Load ハンドラの中で Let's Close!
- 調査のきっかけ
- 注意事項
- 調査環境
- 検証用プログラムの概要
- Form.Close() の実装
- ウィンドウハンドルが作成されるまでの流れ
- Load イベントが発生するまで
- Let's Close!
- まとめ
調査のきっかけ
Form.Load イベントハンドラの中で、Form.Close メソッドを呼び出しても大丈夫なのかと疑問に思ったので調べてみました。
注意事項
説明の内容が正しい保証は一切ありません。
Microsoft が公式に文書化していない内容を前提としてプログラムを書くことは推奨されません。
調査環境
- Windows11 22H2 (OS build 22621.819)
- .NET Framework 4.8.1
検証用プログラムの概要
- Form1 のボタンがクリックされた時に Form2 を生成し、続けて ShowDialog() を呼び出します
- Form2 の Load イベントの中で Form2 を Close します
各種イベントハンドラにブレークポイントを仕掛けて調査しました。
public partial class Form1 : Form { private void button1_Click(object sender, EventArgs e) { new Form2().ShowDialog(); } } public partial class Form2 : Form { private void Form2_HandleCreated(object sender, EventArgs e) {} protected override void OnCreateControl() { base.OnCreateControl(); } private void Form2_Load(object sender, EventArgs e) { this.Close(); } private void Form2_FormClosing(object sender, FormClosingEventArgs e) {} private void Form2_FormClosed(object sender, FormClosedEventArgs e) {} }
Form.Close() の実装
Form.Close() の実装は以下のようになっています。
public void Close() { if (GetState(STATE_CREATINGHANDLE)) throw new InvalidOperationException(SR.GetString(SR.ClosingWhileCreatingHandle, "Close")); if (IsHandleCreated) { closeReason = CloseReason.UserClosing; SendMessage(NativeMethods.WM_CLOSE, 0, 0); } else{ // MSDN: When a form is closed, all resources created within the object are closed and the form is disposed. Dispose(); } }
ウィンドウハンドルが作成されるまでの流れ
InvalidOperationException
例外(内容:CreateHandle() の実行中は Close() を呼び出せません)が発生するのは、コントロールの状態変数に STATE_CREATINGHANDLE
フラグが立っている時に Close() が呼び出された時のようです。
では、このフラグはいつ設定されて、いつ解除されるのでしょうか。
new Form2().ShowDialog() // (1) Forms.Form.ShowDialog() Forms.Form.ShowDialog(owner:null) // (A) Forms.Control.Handle.get() Forms.Form.CreateHandle() // (2) Forms.Control.CreateHandle() SetState(STATE_CREATINGHANDLE, true) // (3) Form.NativeWindow.CreateHandle(cp) Forms.UnsafeNativeMethods.CreateWindowEx(...) [...] Form2_HandleCreated(sender, e) // (4) SetState(STATE_CREATINGHANDLE, false) // (5)
- (1) で Form2 を ShowDialog() する
- (2) でハンドルの作成が始まる。ハンドルはフォームを new した時ではなく ShowDialog() の中で作成されていた
- (3) で状態変数に
STATE_CREATINGHANDLE
フラグが設定される - その先で Control.HandleCreated イベントが発生し、ユーザコードのイベントハンドラ (4) が実行される
- (5) で状態変数の
STATE_CREATINGHANDLE
フラグが解除される - (4) の中で Close() を呼び出したところ、
InvalidOperationException
例外が発生した
Load イベントが発生するまで
次に、Form.Load イベントが発生するまでの流れを見てみます。
Forms.Form.ShowDialog(owner:null) // 上のコードの (A) と対応 [...] Forms.Application.RunDialog(form:Form2) Forms.Application.ThreadContext.RunMessageLoop(reason:msoloopModalForm, context) Forms.Application.ThreadContext.RunMessageLoopInner(reason:msoloopModalForm, context) Forms.Control.Visible.set(true) Forms.Form.SetVisibleCore(ture) Forms.Control.SetVisibleCore(true) SafeNativeMethods.ShowWindow(hwnd, SW_SHOW:0x24) // (1) [Unmanaged Code] [Mnaged Code] Forms.NativeWindow.DebuggableCallback(hWnd, WM_SHOWWINDOW, w, l) Forms.Control.ControlNativeWindow.WndProc(m) Forms.Control.ControlNativeWindow.OnMessage(m) Forms.Form.WndProc(m) Forms.Form.WmShowWindow(m) Forms.ScrollableControl.WndProc(m) Forms.Control.WndProc(m) Forms.Control.WmShowWindow(m) Forms.Control.CreateControl() // (2) Forms.Control.CreateControl(fIgnoreVisible) Forms.Form.OnCreateControl() // (3) Forms.Form.OnLoad(e) // (4) Form2_Load(sender, e) // (5)
- (1) で ShowWindow() が呼ばれる
- (2) でコントロールが作成される
- (3) の Control.OnCreateControl メソッドは、オーバーライドすることで、コントロールの作成が完了した時に呼び出される
- (4) で Form.Load イベントが発生し、ユーザーコードのイベントハンドラ (5) が実行される
Let's Close!
お待ちかねの Form.Load イベントハンドラの中で Close() を呼び出してみます。
Form2_Load(sender, e) this.Close() // (1) Forms.Form.Close() Forms.Control.SendMessage(msg:WM_CLOSE, wparam:0, lparam:0) // (2) [...] Forms.Form.WmClose(m) dialogResult = DialogResult.Cancel // (3) Forms.Form.CheckCloseDialog(closingOnly:true) // (4) Forms.Form.OnFormClosing(e) // (5) Form2_FormClosing(sender, e) // (6) [...] Forms.Application.ThreadContext.RunMessageLoopInner(reason:msoloopModalForm, context) UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(...) UnsafeNativeMethods.IMsoComponent.FContinueMessageLoop(...) // (7) Forms.Form.CheckCloseDialog(closingOnly:false) // (8) Forms.Form.OnFormClosed(e) // (9) Form2_FormClosed(sender, e) // (10) UnsafeNativeMethods.IMsoComponent.FContinueMessageLoop(...) // (11) UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(...) Forms.Application.ThreadContext.EndModalMessageLoop(context) Forms.Application.ThreadContext.RunMessageLoopInner(reason:msoloopModalForm, context)
- (1) のロードイベントハンドラの中で Close() を呼び出す
- (2) で WM_CLOSE メッセージが送信される
- (3)
WM_CLOSE
ハンドラ WmClose() の中でdialogResult
にDialogResult.Cancel
が設定される - (4) で
dialogResult != DialogResult.None
&&CalledClosing == false
の場合(この時点では true)、(5) のForm.OnFormClosed メソッドが呼ばれ、ユーザーコードのイベントハンドラ (6) が実行される。その後、(4) の中でCalledClosing
にtrue
が設定される
ここで話は ShowDialog のメッセージループの中に移ります。
- (7) の FContinueMessageLoop() メソッド は、メッセージループを継続する場合は
true
それ以外の場合はfalse
を返す - (8) で
closingOnly == false
&&dialogResult != 0
なら、(9) が実行される。(3) でdialogResult
はDialogResult.Cancel
に設定されているので、結果的に (9) の Form.OnFormClosed メソッドが呼ばれ、ユーザーコードのイベントハンドラ (10) が実行される - (9) の Form.OnFormClosed メソッドが実行されると (11) の FContinueMessageLoop() メソッド が
false
を返し、ShowDialog のメッセージループが終了する - Form.ShowDialog(owner:null) の最後で ウィンドウハンドルが破棄される
まとめ
- Control.HandleCreated イベントハンドラの中で、Form.Close を呼び出してはいけない。
InvalidOperationException
例外が発生する - Form.Load イベントハンドラが呼ばれるタイミングでは、ウィンドウハンドルの作成が完了している
- Form.Load イベントハンドラの中で Form.Close を呼び出すと、FormClosing イベント、FormClosed イベント が順に発生し、ShowDialog() の呼び出しが終了する