VB6のQueryUnloadイベントにおけるUnloadModeのように、フォームが閉じられる時にどうしてフォームが閉じられようとしているのか(ウィンドウの「閉じる」ボタンのクリックにより閉じられようとしているのか、コードのCloseメソッドにより閉じられようとしているのか等)知るにはどのようにすればよいのかについて説明します。
.NET Framework 2.0からはFormClosingイベントが追加され、フォームが閉じられようとしているときに、その理由を簡単に知ることができるようになりました。FormClosingイベントハンドラで取得できるFormClosingEventArgsオブジェクトのCloseReasonプロパティで閉じられる理由を知ることができます。
まったく同じように、FormClosedイベントハンドラでフォームが閉じられた理由を知ることができます。
以下にCloseReasonの値と、そのときの理由を表に示します。
CloseReasonのメンバ | フォームが閉じられる原因 |
---|---|
ApplicationExitCall | Application.Exitが呼び出された。 |
FormOwnerClosing | このフォームを所有しているフォームが閉じられようとしている。 |
MdiFormClosing | このフォームのMDI親フォームが閉じられようとしている。 |
TaskManagerClosing | タスクマネージャーがアプリケーションを終了させようとしている。 |
UserClosing | ユーザーが、ウィンドウの「閉じる」ボタン(ウィンドウの右上のXボタン)を押した、コントロールメニューの「閉じる」を選択した、Alt + F4キーを押したなど。フォームのCloseメソッドを呼び出したときも。 |
WindowsShutDown | OSが終了するために、アプリケーションを終了させようとしている。 |
None | 不明 |
フォームが閉じられるときにその理由を表示するサンプルを以下に示します。
'フォームのFormClosingイベントハンドラ Private Sub Form1_FormClosing(ByVal sender As System.Object, _ ByVal e As System.Windows.Forms.FormClosingEventArgs) _ Handles MyBase.FormClosing Select Case e.CloseReason Case CloseReason.ApplicationExitCall Console.WriteLine("Application.Exitによる") Case CloseReason.FormOwnerClosing Console.WriteLine("所有側のフォームが閉じられようとしている") Case CloseReason.MdiFormClosing Console.WriteLine("MDIの親フォームが閉じられようとしている") Case CloseReason.TaskManagerClosing Console.WriteLine("タスクマネージャによる") Case CloseReason.UserClosing Console.WriteLine("ユーザーインターフェイスによる") Case CloseReason.WindowsShutDown Console.WriteLine("OSのシャットダウンによる") Case CloseReason.None Console.WriteLine("未知の理由") Case Else Console.WriteLine("それ以外") End Select End Sub
//フォームのFormClosingイベントハンドラ private void Form1_FormClosing(object sender, FormClosingEventArgs e) { switch (e.CloseReason) { case CloseReason.ApplicationExitCall: Console.WriteLine("Application.Exitによる"); break; case CloseReason.FormOwnerClosing: Console.WriteLine("所有側のフォームが閉じられようとしている"); break; case CloseReason.MdiFormClosing: Console.WriteLine("MDIの親フォームが閉じられようとしている"); break; case CloseReason.TaskManagerClosing: Console.WriteLine("タスクマネージャによる"); break; case CloseReason.UserClosing: Console.WriteLine("ユーザーインターフェイスによる"); break; case CloseReason.WindowsShutDown: Console.WriteLine("OSのシャットダウンによる"); break; case CloseReason.None: default: Console.WriteLine("未知の理由"); break; } }
.NET Framework 1.1以前で最も一般的なのは、フォームのWndProcメソッドをオーバーライドし、送られてくるメッセージを調べるという方法です。次の例では、WM_ENDSESSION、WM_SYSCOMMAND、WM_CLOSEが送られてきたか調べ、フォームが閉じられる原因がOSのシャットダウンによるか、Xボタン(「閉じる」ボタン)やコントロールメニューによるか、コードによるか、判断しています。
'Imports System.Security.Permissions <SecurityPermission(SecurityAction.Demand, _ Flags:=SecurityPermissionFlag.UnmanagedCode)> _ Protected Overrides Sub WndProc(ByRef m As Message) Const WM_CLOSE As Integer = &H10 Const WM_ENDSESSION As Integer = &H16 Const WM_SYSCOMMAND As Integer = &H112 Const SC_CLOSE As Long = &HF060L Select Case m.Msg Case WM_ENDSESSION 'OSのシャットダウンで閉じられようとしている Console.WriteLine("WM_ENDSESSION") Exit Select Case WM_SYSCOMMAND If (m.WParam.ToInt64() And &HFFF0L) = SC_CLOSE Then 'Xボタン、コントロールメニューの「閉じる」、 'コントロールボックスのダブルクリック、 'Atl+F4などにより閉じられようとしている Console.WriteLine("SC_CLOSE") End If Exit Select Case WM_CLOSE 'Application.Exit以外で閉じられようとしている Console.WriteLine("WM_CLOSE") Exit Select End Select MyBase.WndProc(m) End Sub
//using System.Security.Permissions; [SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)] protected override void WndProc(ref Message m) { const int WM_CLOSE = 0x0010; const int WM_ENDSESSION = 0x16; const int WM_SYSCOMMAND = 0x112; const long SC_CLOSE = 0xF060L; switch (m.Msg) { case WM_ENDSESSION: //OSのシャットダウンで閉じられようとしている Console.WriteLine("WM_ENDSESSION"); break; case WM_SYSCOMMAND: if ((m.WParam.ToInt64() & 0xFFF0L) == SC_CLOSE) //Xボタン、コントロールメニューの「閉じる」、 //コントロールボックスのダブルクリック、 //Atl+F4などにより閉じられようとしている Console.WriteLine("SC_CLOSE"); break; case WM_CLOSE: //Application.Exit以外で閉じられようとしている Console.WriteLine("WM_CLOSE"); break; } base.WndProc(ref m); }
この方法はStackFrameを使うというちょっと変わった方法で、「GotDotNet Message Boards - Form.Closing...」(リンク切れ)で紹介されています。
ここで紹介されているYeahIGotDotNetさん、MikeWill34さんの書いたコード及び、「The Code Project - Find out what's closing your application」で紹介されているEvilDoctorSmithさんのコードを参考にさせていただき、次のようなコードを書いてみました。詳しくは、これらのリンク先をご覧ください。
注意:このコードは、.NET Framework 1.1以前を考慮して書かれています。.NET Framework 2.0以降では、FormクラスのClosing、Closedイベントの代わりに、FormClosing、FormClosedイベントを使用してください。
Private Sub Form1_Closing(ByVal sender As Object, _ ByVal e As System.ComponentModel.CancelEventArgs) _ Handles MyBase.Closing Dim stack As New System.Diagnostics.StackTrace(True) Dim frame7 As System.Diagnostics.StackFrame = stack.GetFrame(7) Select Case frame7.GetMethod().Name Case "DispatchMessageW" Console.WriteLine("タスクマネージャーによる") Case "SendMessage" Console.WriteLine("コードによる") Case "CallWindowProc" If stack.FrameCount > 14 Then Dim frame14 As System.Diagnostics.StackFrame = _ stack.GetFrame(14) If frame14.GetMethod().Name = "WmSysCommand" Then Console.WriteLine( _ "Xボタンまたはコントロールメニューによる") Else If frame14.GetMethod().Name = "WndProc" Then Console.WriteLine("OSのシャットダウンによる") End If End If End If Case "DefMDIChildProc" Console.WriteLine( _ "MDI子フォームのXボタンまたはコントロールメニューによる") Case "DefFrameProc" Console.WriteLine("MDI親フォームが閉じられたことによる") Case "ShowDialog" Console.WriteLine("モーダルダイアログが閉じられたことによる") Case Else Console.WriteLine("原因不明") End Select End Sub
private void Form1_Closing(object sender, System.ComponentModel.CancelEventArgs e) { System.Diagnostics.StackTrace stack = new System.Diagnostics.StackTrace(true); System.Diagnostics.StackFrame frame7 = stack.GetFrame(7); switch (frame7.GetMethod().Name) { case "DispatchMessageW": Console.WriteLine("タスクマネージャーによる"); break; case "SendMessage": Console.WriteLine("コードによる"); break; case "CallWindowProc": if (stack.FrameCount > 14) { System.Diagnostics.StackFrame frame14 = stack.GetFrame(14); if (frame14.GetMethod().Name == "WmSysCommand") Console.WriteLine( "Xボタンまたはコントロールメニューによる"); else if (frame14.GetMethod().Name == "WndProc") Console.WriteLine("OSのシャットダウンによる"); } break; case "DefMDIChildProc": Console.WriteLine( "MDI子フォームのXボタンまたはコントロールメニューによる"); break; case "DefFrameProc": Console.WriteLine("MDI親フォームが閉じられたことによる"); break; case "ShowDialog": Console.WriteLine("モーダルダイアログが閉じられたことによる"); break; default: Console.WriteLine("原因不明"); break; } }
最後に紹介するのは、hidden windowを使った方法です。これは、「Visual Studio Magazine - Determine a Form's UnloadMode」(リンク切れのため、Internet Archiveへのリンク)で紹介されているものです。先に紹介したWndProcメソッドによる方法では判断できない原因を調べるために、隠れたフォームを使うという方法のようです。詳しくは、リンク先をご覧ください。