Friday, March 12, 2004

Dealing with Exit in an MDI App

So, I had this problem. I'm writing an MDI application (FlexWikiPad, if you're wondering), and I finally got around to implementing the feature that asks you to save your documents when you close the application. I figured it would be easy. And it was, ultimately, but it took a fair amount of experimentation and documentation-diving. So I figured I'd post the answer here, to benefit future Googlers.


The issue I'm having comes out of the order in which messages are received. When you close an MDI child window, it gets a Closing message, which receives a CancelEventArgs argument. If you don't set the Cancel property to true, you'll later receive a Closed event as well. This is handy, because during the Closing event handler, you can pop up the classing “Would you like to save? Yes, No, or Cancel” dialog box, and cancel the close of the window if they choose the Cancel button. Then you can do any cleanup in the Closed event handler, which will only fire if the close is not canceled.


This has worked great for me for dealing with situations where a user explicitly closes each individual child window. The problem arises when they close the parent application. When they do this, I get Closing events on all of the open child forms before the Closing event of the main application fires. Then all the children forms get individual { Closing, Closed } event pairs. So if I have two children open, the sequence goes something like this:



  1. child1 Closing

  2. child2 Closing

  3. parent Closing

  4. child2 Closed

  5. child2 Closed

  6. parent Closed

Which pretty much sucks for me, because it means I don't know what to do when closing the app. If the parent's Closing method would only fire first, then I could set a flag somewhere that would say “Hey, we're in the middle of closing.” But because I don't know whether the first child1 Closing event is because of a user explicitly closed the child, or because someone did a File->Exit, I'm stuck.


“OK,” you're thinking, “Why not just choose not to prompt in the child Closed event?” Well, the issue here is, it's too late. The form has already closed. I want them to be able to pick Cancel and stop the application from shutting down if they realize halfway through that they've made a mistake. And I can't really just record what they chose in the first child Closing, because at the time the event first, I don't know if it's related to the application shutting down.


Fortunately, the answer appears in the documentation for the Form.Closing event. It turns out that if a child cancels their own close, the CancelEventArgs.Cancel that gets passed in to the parent's Closing event will be set to true. If you keep it set to true, the application close will cancel, and you'll be all set. If you decide you want to force a shutdown, just flip it back to true.


Oh, and if your File->Exit handler uses Application.Exit instead of this.Close(), none of these events will fire, and you'll be screwed. Don't do that. :)

3 comments:

  1. For future Googlers (and Craig), the FileDocument class in Genghis [1] handles these details.

    [1] http://www.genghisgroup.com

    ReplyDelete
  2. You can distinguish between the two forms of closing when override OnFormClosing, which gets a FormClosingEventArgs that has a CloseReason property.



    If the CloseReason in the MDI Child Window is UserClosing, somebody clicked the X button of the MDI Child.



    If the CloseReason in the MDI Child Window is MDIFormClosing, somebody clicked the X button of the MDI *Parent* Window!



    Cheers,

    Guus

    ReplyDelete
  3. Guus,

    You're my hero!

    ReplyDelete