Application.DoEvents behavior (Bug?)

  • Thread starter Thread starter Armin Zingler
  • Start date Start date
A

Armin Zingler

Hi again, (VB 2003, Framework 1.1)

the behavior of Application.DoEvents depends on the fact whether the form is
shown modeless or modally. Here is a short sample to reproduce:

1. Create a new WindowsApplication and add a Button to the Form
2. Add this code to the Form:

Shared Sub main()
Dim f As New Form1
'f.ShowDialog()
Application.Run(f)
End Sub

Private Sub Button1_Click( _
ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles Button1.Click

Do
Application.DoEvents()
Loop While Me.IsHandleCreated
End Sub

3. Start and click the button
4. Click the X to close the form

Everything works as expected. Now replace "Application.Run(f)" by
"f.Showdialog()". Start again and click the button. Now, the Form can not be
closed by clicking the X anymore! My short question is: Why?

One bug you'll also see: The first time when you try to click the X, the
Button is actually clicked!
 
ShowDialog() actually contains its own message loop, similar to the one
you've got in your Button1_Click. Sort of like this:

Public Sub ShowDialog()
SetModalFlagsAndShow()
Do
Application.DoEvents()
While Not Closed
CloseFormAndDestroyWindowHandle()
End Sub

That's a horrific oversimplification (take a look at the code in
Reflector if you're really curious), but that's the gist of it.

So, when you call ShowDialog, it starts its message loop. Then your
Button1_Click method starts another message loop. You never return
control to ShowDialog(), so it never gets around to destroying the
window handle -- which is what your Button1_Click is testing for, so
Button1_Click never terminates either.

A modal window doesn't get to finish "closing" until its ShowDialog
modal loop ends. This is unlike a non-modal window, where the window
handle is destroyed more-or-less as soon as you close the window.
That's why your code (loop until handle destroyed) works in one case and
not the other.

As far as how to fix it... I could tell you how to fix this in Delphi,
but I haven't spent that much time in the guts of Windows.Forms yet. My
first recommendation would be not to do what you're doing -- don't loop
until the form closes, at least not when the form will be shown modally.
Consider using a timer or a thread, to do your background work,
instead of a message loop.

If you have to do what you're doing, then try looping until
Form.DialogResult <> DialogResult.None (untested, but I'm guessing it
should work) -- but be aware that this will only work for a form that
was shown with ShowDialog(), and not one shown with Show().

Or, you could check the form's Modal property. If Modal is True, then
use DialogResult as your exit condition. If Modal is False, then use
IsHandleCreated.
 
Joe, thanks for your reply. More inline.

Joe White said:
ShowDialog() actually contains its own message loop, similar to the
one you've got in your Button1_Click. Sort of like this:

Public Sub ShowDialog()
SetModalFlagsAndShow()
Do
Application.DoEvents()
While Not Closed
CloseFormAndDestroyWindowHandle()
End Sub

That's a horrific oversimplification (take a look at the code in
Reflector if you're really curious), but that's the gist of it.

So, when you call ShowDialog, it starts its message loop. Then your
Button1_Click method starts another message loop.


So far I knew. :)

You never return

control to ShowDialog(), so it never gets around to destroying the
window handle -- which is what your Button1_Click is testing for, so

Button1_Click never terminates either.

A modal window doesn't get to finish "closing" until its ShowDialog
modal loop ends. This is unlike a non-modal window, where the
window
handle is destroyed more-or-less as soon as you close the window.
That's why your code (loop until handle destroyed) works in one case
and not the other.

I don't understand this: When I click the X, WM_CLOSE is sent. This is true
for modeless and modal Forms (I checked it by overriding WndProc). WM_CLOSE
should close the window (DestroyWindow). This happens when calling
Application.DoEvents. After calling DoEvents, the handle should have been
destroyed. Why is there a difference between modeless and modal forms?
Application.Run is a message loop just like ShowDialog, so I don't see the
difference.

Why does ShowDialog interpret WM_CLOSE and destroys the window? I thought
it's the window procedure that catches wm_close and destroys itself, and the
window procedure is the same in modal and modeless forms.

I'm not sure whether the same thing happens when showing a modal form in
VB6. There, I can show it modally and I am still able to close it during
the loop containing doevents.
As far as how to fix it... I could tell you how to fix this in
Delphi, but I haven't spent that much time in the guts of
Windows.Forms yet. My first recommendation would be not to do what
you're doing -- don't loop until the form closes, at least not when
the form will be shown modally.
Consider using a timer or a thread, to do your background work,
instead of a message loop.

If you have to do what you're doing, then try looping until
Form.DialogResult <> DialogResult.None (untested, but I'm guessing it
should work) -- but be aware that this will only work for a form
that
was shown with ShowDialog(), and not one shown with Show().

Or, you could check the form's Modal property. If Modal is True,
then use DialogResult as your exit condition. If Modal is False,
then use IsHandleCreated.

The example is not "real-world" code. :) I only came across this issue doing
something else, so there is no need to find a solution. I was just curious
why it doesn't work as expected. I'm afraid but I still didn't get it. :-/
 
I don't understand this: When I click the X, WM_CLOSE is sent. This is true
for modeless and modal Forms (I checked it by overriding WndProc). WM_CLOSE
should close the window (DestroyWindow). This happens when calling
Application.DoEvents. After calling DoEvents, the handle should have been
destroyed. Why is there a difference between modeless and modal forms?

Yes, WM_CLOSE is sent. However, AFAIK, if the form is modal, then
WM_CLOSE sets ModalResult instead of immediately destroying the window
handle.

Application.Run is a message loop just like ShowDialog, so I don't see the
difference.

Well, I'm not totally sure. But keep in mind that Application.Run has
totally arbitrary exit conditions, which aren't even specified by the
Application class -- IIRC, it keeps looping until someone (usually the
ApplicationContext) calls ThreadTerminate.

Form.ShowDialog has very specific exit conditions: it exits when the
form's DialogResult changes to something other than DialogResult.None.
It's actually not that simple -- the code is a tangled maze about six
methods deep -- so there may be other factors.

So there's not necessarily an automatic reason why Application.Run
should behave the same as Form.ShowDialog. Yes, it might be nice if
they were similar, but I suspect there are a lot of details in the way.

Why does ShowDialog interpret WM_CLOSE and destroys the window? I thought
it's the window procedure that catches wm_close and destroys itself, and the
window procedure is the same in modal and modeless forms.

You're asking for a little more depth of knowledge than I've got, but
I'll hazard some guesses. First, I don't think ShowDialog processes
WM_CLOSE; I think the Form processes WM_CLOSE, but uses it to set
DialogResult instead of destroying its window handle immediately. (I
haven't confirmed this, but the code that's invoked as a result of
ShowDialog is a real mess, and aside from your questions I haven't had
any reasons to dive too far into it.)

A non-modal form *has* to destroy its own window handle, because there's
nobody else to clean up after it. A modal form *can* wait for someone
else (ShowDialog) to clean up after it, but exactly why they chose to
make it work that way, I'm not sure. I do have some guesses. In
particular, ShowDialog sets up some application state (disabling all
other forms) -- and if resetting that state is non-trivial, it would
make more sense for ShowDialog to do it. ShowDialog probably has local
variables that store various bits of initial state, and it's cleaner to
have ShowDialog restore that state than to somehow pass it into the
Form. And it's plausible enough that some of that state-restoration
could require the form's window handle still exist at the time.

But bottom line, I'm not sure. I guess I haven't had any reason to
worry about it too much yet. <grin> If you don't have Lutz Roeder's
Reflector yet, download it and take a look at the ShowDialog code.
 
You're absolutely right. I had a look at the docs of ShowDialog (why not
earlier?) and there are also some words describing this behavior. I also did
some IL debugging confirming everything.

Anyway, it is like it is - and now I know how it is. ;-)

Thanks for your participation!
 
Back
Top