Bug or bad design in System.Windows.Form.Form

S

Stephan Zaubzer

Hi
While developing a Windows forms application I encountered what I
consider to be a bug or a design flaw:

Consider to have 3 Forms namely A B and C. B is owned by A and C is
owned by B. If we close A, all three forms should close which is exactly
the design described in the class library reference on msdn.
However, if the windows run in NON modal mode (i.e. not showed using
ShowDialog(IWin32Window), closing A does call the OnFormClosing on A and
on B but NOT on C. C gets closed without a way to check for unsaved
data, because the FormClosing event doesn't get raised.
If window B and C run in Modal mode (ShowDialog(IWin32Window)), C raises
the FormClosingEvent (from within the OnFormClosing Method), but setting
e.cancle = false will NOT prevent the window from closing, and even
worse, the OnFormClosing method will run AFTER the window has already
been disposed.

Possible Reason:
The invaluable resource .NET Reflector gave me some insight to the
Implementation of the System.Windows.Forms.Form class. When a window
closes, it calls OnFormClosing on all of its MdiChildren and Owned
Windows. If e.cancle does not evaluate to false after these calls, the
form gets Disposed. Dispose(bool disposing) calls Dispose on all owned
forms, and thus it works recursively. But OnFormClosing does NOT. So
when I call A.Close() it "asks" B to close, and if B does not set
e.cancle, A disposes itself which in turn disposes B and C. So C gets
Disposed without a prior call to OnFormClosing and no event rising.
Shouldn't the (private) WMClose method in System.Windows.Forms.Form call
some RECURSIVE method to check recursively for all owner relationships
if the owned forms (in all levels of the owner relationship) can close?

Possible workaround: (NOT tested yet)
override the OnFormClosing method that it calls OnFormClosing for all
owned forms recursively. This however will cause the OnFormClosing
method to be called twice for all owned forms that are directly owned by
the "root" form, because WMClose calls OnFormClosing on all its directly
owned forms, and later calls this.OnFormClosing.
The loop which calls OnFormClosing for all owned forms shouldn't be in
WMClose but rather in OnFormClosing itself, thus be recursive.
For MdiWindows this problem does not exist, because MdiCild Relations
can only exist in one level, so for MdiChildren this problem does not
exist. But when one closes the MdiParent of an MdiChild which owns some
window X, OnFormClosing and the FormClosing Event will NOT be called for X.

Has anyone ever encountered problems with this behavior as I did? And
how did you solve the problem?
I would consider this behavior either a bug or a design flaw!

Regards
Stephan
 
R

Richard Coltrane

Hi Stephan,

The current behaviour seems appropriate to me. Basically when B is closing
it should notify (i.e you code it too notify) any child forms it spawned
that this event is about to occur and so they can do a state save. "A"
shouldn't need to be aware of C; nor even consider C, as A has no contract
with C; only B has that.

If C is an options type dialog for B then i would spawn it modal so A cannot
be closed with first closing C. If C is spawned by B and can used
independantly of B thereafter then i would look at the Command/Observer
pattern or something to tie it back into the root form A.i.e create a
contract.

I definitely wouldn't think it a bug nor a design flaw; your example is
very specific and simply doesn't fall within the out of the box
behaviour;but you can easily make your forms do what you want with a little
bit of code. You simply need to register C with B yourself.

Richard
 
B

Bryan Phillips

I was able to reproduce the problem even using Show(IWin32Window), but
no idea how to gracefully close FormC when FormA closes.
 
R

RobinS

Stephan Zaubzer said:
Hi
While developing a Windows forms application I encountered what I
consider to be a bug or a design flaw:

Consider to have 3 Forms namely A B and C. B is owned by A and C is
owned by B. If we close A, all three forms should close which is
exactly the design described in the class library reference on msdn.
However, if the windows run in NON modal mode (i.e. not showed using
ShowDialog(IWin32Window), closing A does call the OnFormClosing on A
and on B but NOT on C. C gets closed without a way to check for
unsaved data, because the FormClosing event doesn't get raised.
If window B and C run in Modal mode (ShowDialog(IWin32Window)), C
raises the FormClosingEvent (from within the OnFormClosing Method),
but setting e.cancle = false will NOT prevent the window from closing,
and even worse, the OnFormClosing method will run AFTER the window has
already been disposed.

Possible Reason:
The invaluable resource .NET Reflector gave me some insight to the
Implementation of the System.Windows.Forms.Form class. When a window
closes, it calls OnFormClosing on all of its MdiChildren and Owned
Windows. If e.cancle does not evaluate to false after these calls, the
form gets Disposed. Dispose(bool disposing) calls Dispose on all owned
forms, and thus it works recursively. But OnFormClosing does NOT. So
when I call A.Close() it "asks" B to close, and if B does not set
e.cancle, A disposes itself which in turn disposes B and C. So C gets
Disposed without a prior call to OnFormClosing and no event rising.
Shouldn't the (private) WMClose method in System.Windows.Forms.Form
call some RECURSIVE method to check recursively for all owner
relationships if the owned forms (in all levels of the owner
relationship) can close?

Possible workaround: (NOT tested yet)
override the OnFormClosing method that it calls OnFormClosing for all
owned forms recursively. This however will cause the OnFormClosing
method to be called twice for all owned forms that are directly owned
by the "root" form, because WMClose calls OnFormClosing on all its
directly owned forms, and later calls this.OnFormClosing.
The loop which calls OnFormClosing for all owned forms shouldn't be in
WMClose but rather in OnFormClosing itself, thus be recursive.
For MdiWindows this problem does not exist, because MdiCild Relations
can only exist in one level, so for MdiChildren this problem does not
exist. But when one closes the MdiParent of an MdiChild which owns
some window X, OnFormClosing and the FormClosing Event will NOT be
called for X.

Has anyone ever encountered problems with this behavior as I did? And
how did you solve the problem?
I would consider this behavior either a bug or a design flaw!

Regards
Stephan

Do you want C closed every time you close B? Couldn't you put that
in the Form_Closing event of Form B?

Another idea is if you want to close all open forms when form A is
closed, you can do that. In VB2005, try this in the Form Closing
event of form A.

For Each myForm As Form In My.Application.OpenForms
If myForm.Name <> me.Name Then
myForm.Close()
End If
Next

Good luck.
Robin S.
 
S

Stephan Zaubzer

I am aware of the fact, that there are several ways to circumvent this
problem without having to write too much code. I know that having this
owner hierarchy represents a bad design in my own software. But what is
considered to be a bug and what is a feature?
I consider a bug to be some behavior that is not in accordance with the
official documentation.
Regarding OnFormClosing and the FormClosing Event MSDN says "Occurs
before the form is closed". Obviously this event does not raise always.
So either the documentation or the class library is not correct.

As you said, A does not need to be aware of C, and should not be aware
of C. Only B needs to be aware of C. And if B is allowed to own a form C
while being owned by another Form A, it also hast to notify C before it
closes. This would easily be possible by designing the WMClose and
OnFormClosing method in a form in a different way. If this was
impossible, an owned form should not be allowed to own another form.
Only these to options provide that the documentation is consistent with
the implementation. Or the documentation should note that a Form that is
owned in a level of 2 or higher does not rise the FormClosing event.
Regards
Stephan
 
G

Guest

My only comment on you code it the comparing of the forms names is wasteful
and crude. This is better to ensure it isnt closing itself:
For Each myForm As Form In My.Application.OpenForms
If Not Object.Equals(myForm,me) Then
myForm.Close()
End If
Next
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top