A common question on the TopSpeed forum and the Clarion newsgroup is “How can I keep my window from closing when the user presses the Escape key?” One answer to this question lies in learning to control the CloseWindow and CloseDown events. This tip looks at methods for controlling the way that these events are handled and how those choices affect the way that a window closes.
A Clarion window is almost always associated with an ACCEPT loop. The ACCEPT loop processes the Windows messages destined for a particular window and handles most of the tasks that make a window work correctly. The basic code structure that supports this is:
OPEN(Window) ACCEPT END CLOSE(Window)
This code opens a window, processes messages in an ACCEPT loop, and then closes the window when the program exits the loop. There are two ways to exit the ACCEPT loop: the ACCEPT loop will automatically terminate after a CloseWindow or CloseDown event or the program can explicitly break out of the ACCEPT loop using a BREAK statement.
Several user actions can generate a CloseWindow event, including:
All of these actions generate a Windows message that is received in the ACCEPT loop as EVENT:CloseWindow.
The CloseDown event only occurs after an application frame receives a CloseWindow event. The CloseWindow event is first posted to the application frame's thread and then the CloseDown event is posted to each of the other threads to indicate that the application will be closing down. If any of the child window ACCEPT loops does not terminate, the application frame will not close. Except for this difference, the events are essentially the same, so I will only refer to the CloseWindow event for the rest of this tip. In most situations you will end up using the same code for CloseWindow and CloseDown in a structure similar to this:
OPEN(Window) ACCEPT CASE EVENT() OF EVENT:CloseWindow OROF EVENT:CloseDown !Do your stuff here END END CLOSE(Window)
The = GAnalytics::WriteAnchor('closewin.zip','/download/closewin.zip') ?>sample program for this tip illustrates the techniques described in this tip. The program opens an MDI frame and allows you to open child windows that use the control structures described below. Rather than including the code in this tip, the sample program illustrates the techniques discussed.
The first two items on the sample program menu, Basic MDI Child and Basic Non-MDI Child demonstrate the basic window support code shown above. Additional code has been added to show when the CloseWindow and CloseDown events occur, but other than that the code structure is exactly the same.
When the CloseWindow event is received, the default action is to break out of the ACCEPT loop at the end of the cycle. Additional actions can be added with embedded code, but unless a CYCLE statement is executed, the program will exit the ACCEPT loop when it reaches the bottom of the loop.
This technique is illustrated with the Window That Always CYCLEs choice from the menu. A CYCLE statement is executed whenever a CloseWindow event is received. This arbitrary cycle statement is not the best solution because it requires some other way to break out of the ACCEPT loop. A much cleaner solution is to use some kind of conditional structure to determine if the CYCLE statement should be executed or not. The next two menu items illustrate this.
When Window with MESSAGE Box receives a CloseWindow event, it uses the Clarion MESSAGE function to display a dialog window asking if the window should close. If the user does not press the Yes button then a CYCLE statement is executed to keep the window from closing. Window with Check Box has a check box to determine if the window should be allowed to close. If the check box is not checked then a CYCLE statement executes to keep the window open.
Many windows provide a Close or Cancel button as an additional way for a user to close the window. I provided a Close button on the Window That Always CYCLEs so that there would be some method to get out of the ACCEPT loop and close the window. In order for a button to close the window, it must either manually BREAK out of the ACCEPT loop or post a CloseWindow event so that the ACCEPT loop terminates on its own. I recommend posting a CloseWindow event to accomplish this. Window With CLOSE Button demonstrates this in action.
It is possible to use this technique in reverse - to have the CloseWindow event post an Accepted event to the Close button and have the Close button contain the logic for closing the window. Window With Incorrect CLOSE Button uses this method. A problem with this method shows up when the CloseDown event is posted to the window.
When the application frame gets a CloseWindow event and a CloseDown event is posted to each of the child windows. When the CloseDown event is received in a child window using this technique, an Accepted event is posted to the Close button but the ACCEPT loop must be CYCLEd in order for the loop to stay active so that the Close button can receive its event and close the window. When the CloseDown event is CYCLEd, it keeps the application frame from closing. Even if the logic in the Close button code closes the window, the application frame will not close.
The best way to avoid this problem is to always put any logic that should happen when the user cancels a window (Cancel button, Escape key, or close application) in the CloseWindow/CloseDown event block and have the Close or Cancel button post the CloseWindow event. Logic for when a user completes a window (OK button or Save button) should be placed in that button's code and the code should explicitly BREAK out of the ACCEPT loop.
If you only want to block the Escape key and don't mind what else happens with the window, you can avoid having to twiddle with events at all. If you ALERT the Escape key for a window (or for a single control) then the Escape key generates an AlertKey event instead of a CloseWindow event when it is pressed. Except for this change, the window's behavior is not affected at all. Thanks to Tom Foley for bringing this to my attention.