Handling that Pesky Windows ControlBox
The Red X, ControlBox, or whatever you call it, can be pesky. The ControlBox closes a window in a WinForms application. That's what it's for. If that form is the last open form or the main form, your application closes. If you handle the FormClosing event, you can Cancel the close operation. But, what happens if you write something like a wizard and the current form is not the main form? The current form closes, but now the user is left staring at a blank screen.
In a wizard scenario, what the user probably intends is that they don't want to complete the wizard; they want to bail. Well, you probably close or hide the form when you navigate between wizard forms and you definitely close the form when the user clicks the ControlBox. The former means navigate to the next or previous form; clicking the ControlBox (usually) means quit altogether.
This article will show you how to distinguish between a ControlBox close and every other close. You will get to use a little bit of the old-school part of Windows, but sometimes, like now, it's still handy.
The scenario is that you want to create a wizard. If the user clicks next or previous, the current form closes and navigation happens. If the user clicks a Cancel button, the wizard stops. If the user presses the Escape keym the wizard shuts down. Finally, if the user presses the ControlBox, the wizard again shuts down. Here is a summary:
- Any escape means stop the wizard.
- Any Cancel button press stops the wizard.
- Clicking the ControlBox means stop the wizard (only works by default on the main form).
- Pressing Alt+F4 is equivalent to the ControlBox close behavior.
Making Escape Mean Cancel
By default, pressing escape doesn't do anything on a Windows form. However, if you add a button to a form, you can indicate that the behavior button represents the Form's cancel behavior. Set the Form.CancelButton (in the Properties window) to your cancel button. Now, pressing Escape will run your Cancel behavior.
For your wizard, the Cancel button as indicated in the previous section means to stop the wizard. You can affect this by writing Application.Exit() in the button's click event handler. Coding the Cancel button event and associating that button with the Form.CancelButton property takes care of Items 1 and 2 in the previous section.
Making the ControlBox Mean Exit
The WndProc method is the message pump handler for WinForms applications. You can override this method in your forms, and if used sparingly it can afford you a bit of extra coolness.
When you click the minimize, maximize, and ControlBox buttons, Windows sends a WM_SYSCOMMAND message. If you click the ControlBox (see Figure 1), the message is WM_SYSCOMMAND and the WParam is SC_CLOSE. Use the FormClosing event to determine when your form is closing and check for WM_SYSCOMMAND and SC_CLOSE in the WndProc to determine whether a form is closing because the ControlBox was clicked.
Figure 1: The ControlBox button.
Listing 1 contains a do-nothing form that represents the main form. Click Next to go to the second form in the wizard. Click Cancel (or the ControlBox) to quit. Listing 2 contains a form that represents a subsequent wizard step. Click Back to return to Form1. Click Cancel or the ControlBox to shut down the wizard.
Listing 1: A Basic do-nothing form that is a stub for the main form in a wizard.
Public Class Form1 Private Sub Button2_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button2.Click Close() End Sub Private Sub Button1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button1.Click Hide() Dim form As Form2 = New Form2 form.Show() End Sub End Class
Notice that in Listing 1 Form1 is only hidden. If you close it, you close the wizard.
Listing 2: A Basic do-nothing form that shows you how to differentiate between a ControlBox close and any other close in your wizard.
Public Class Form2 Private Sub Button2_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button2.Click Application.Exit() End Sub Protected Overrides Sub WndProc(ByRef m _ As System.Windows.Forms.Message) MyBase.WndProc(m) If (m.Msg = WM_SYSCOMMAND And _ m.WParam.ToInt32() = SC_CLOSE) Then Application.Exit() End If End Sub Private Const WM_SYSCOMMAND As Integer = &H112 Private Const SC_CLOSE As Integer = &HF060 Private Sub Button1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button1.Click Close() If (My.Application.ApplicationContext.MainForm _ Is Nothing = False) Then My.Application.ApplicationContext.MainForm.Show() End If End Sub End Class