Jump to content
Xtreme .Net Talk

Recommended Posts

Posted (edited)

I was experimenting with DoEvents and have an oddity... I'm having trouble having it acknowledge the Form_Closing() Event properly.

 

In it's simplest incarnation, I tried this:

Private Sub btnStart_Click(ByVal sender As System.Object, _
                           ByVal e As System.EventArgs) _
                           Handles btnStart.Click
   Do
       Application.DoEvents()
   Loop
End Sub

Now this didn't do so well, LOL. When the btnStart was clicked, the use of 'Application.DoEvents' did allow the Form to exit, but:

 

(1) I had to click the Red Close <X> twice to get it to acknowledge.

(2) Although the Form did in fact disappear, it was still "running", which I could tell because the Immediate window in the debugger was still open.

 

The fact that it was still "running" although "gone" is interesting, this could not occur in VB6, but I guess as long as the Code is "alive" (that is, still running), then I guess it is still on the stack and the Object/Form (or at least some part of it) still exists?

 

 

My next idea was to do two things:

 

(1) Use Mod 1000 so that I am not flooding so many DoEvents calls. I didn't expect anything from this, although it should speed up the loop since DoEvents is pretty heavy.

 

(2) I added a 'Private m_Shutdown As Boolean' to trigger the exit of the Loop.

 

 

Private m_Shutdown As Boolean

Private Sub btnStart_Click(ByVal sender As System.Object, _
                           ByVal e As System.EventArgs) _
                           Handles btnStart.Click
   Dim count As Long
   Do
       count += 1
       If (count Mod 1000) = 0 Then
           Application.DoEvents()
       End If
   Loop Until m_Shutdown
End Sub

Private Sub Form1_Closing(ByVal sender As Object, _
                         ByVal e As System.ComponentModel.CancelEventArgs) _
                         Handles MyBase.Closing
   MessageBox.Show ("Form1_Closing()")
   m_Shutdown = True
End Sub

Private Sub Form1_Closed(ByVal sender As Object, _
                        ByVal e As System.EventArgs) _
                        Handles MyBase.Closed
   MessageBox.Show ("Form1_Closed()")
End Sub

The results of the above were much better... but not perfect:

 

(1) It did now close cleanly. That is, when the Form Closed, the Loop was no longer running. Perfect. :)

 

(2) However, it does still require that the Form's Red Close <X> be clicked twice.

 

 

Any ideas on why the <X> needs to be hit twice? (See Attached VB.Net Project.)

 

Hmmm... ok, wait, if you run the project, hit the <Start> button on the Form and then hit the <X> button, the interesting thing is that on the first click, the <Start> button will depress. This seems to occur no matter where you click on the Form or Titlebar as well.

 

So is this a sort of "TakeFocusOnClick" sort of situation? There must be a simple way to allow for the first click to actually go where the User wants, no? I'm just too n00b in .Net to quite understand what is happening here.

 

Thanks in advance for all those interested/help out... :)

DoEventsTester.zip

Edited by PlausiblyDamp

Posting Guidelines

 

Avatar by Lebb

Posted
When the start button is clicked, the Button Click event is sent to the Application's event que but is never allowed to run because you create an infinite loop in the button click event. The next time you click a button the start button's click event is deqeued and run and the program in sent into an infinite loop again. The process is repeated for each event that is raised, which is why it takes two click of the close button to close the form.
Posted (edited)

Thank you, Diesel, for that excellent explanation, that was really clear.

 

So how now do I fix it? This must be a very common situation, no? The User clicks a button which kicks off some long process... The process reports to a ProgressControl or the like over time, while calling 'DoEvents' within the loop so that the User can Cancel out of it, if he/she wishes. But here it's not executing cleanly.

 

I tried the following protection, in an attempt to block the infinite loop when re-entering the 2nd time:

Private Sub btnStart_Click(ByVal sender As System.Object, _
				       ByVal e As System.EventArgs) _
				       Handles btnStart.Click
   Static count As Long
   If count = 0 Then
       Do
           count += 1
           If (count Mod 1000) = 0 Then
               Application.DoEvents()
           End If
       Loop Until m_Shutdown
   End If
End Sub

But it did not improve things. Then to just see if it was getting called twice as you stated, I added this:

MessageBox.Show("Count = " & count)

to the first line in the routine (after declaring the 'Static Count As Long') and, amazingly, this fixed it! The Sub does not even get called a 2nd time. Hmmm....

 

So I figured that the issue was that the MessageBox.Show() was forcing the App to yield, much like calling 'DoEvents' or 'Sleep()', so I tried replacing the MessageBox.Show() and got the following:

Private Sub btnStart_Click(ByVal sender As System.Object, _
				       ByVal e As System.EventArgs) _
				       Handles btnStart.Click
   Static count As Long

   Application.DoEvents()           ' <-- Added
   System.Threading.Thread.Sleep(0) ' <-- Added

   If count = 0 Then
       Do
           count += 1
           If (count Mod 1000) = 0 Then
               Application.DoEvents()
               System.Threading.Thread.Sleep(0) ' <-- Added
           End If
       Loop Until m_Shutdown
   End If
End Sub

And the above fails, clicking the Red Close <X> the first time still visibly depresses the <Start> button instead. Yet the MessageBox.Show() worked... But there must be another way other than having to fire up a MessageBox, no?

 

What is the standard approach to handling this situation?

Edited by Mike_R

Posting Guidelines

 

Avatar by Lebb

  • *Experts*
Posted

Normally, the button would call a function that does the looping. If you want to allow a cancel mid-process, you'll want a form-level variable to handle closing. For example:

 

public class Form1 : System.Windows.Form
{
   private bool bRunning = false;
   private bool bPaused = false;
   
   private void ClickEvent()
   {
       bRunning = true;
       do
       {
           if(!bPaused)
           {
               // Your processing code here, including progress bar updates
           }
           Application.DoEvents();
       } while(bRunning);

       // If needed:
       // Application.Exit();
   }

   private void Form1_Closing(...)
   {
       if(bRunning)
       {
           bPaused = true;
           DialogResult result = MessageBox.Show("Are you sure you want to Cancel?");
           if(result == DialogResult.Yes)
           {
               bRunning = false;
           }
           {
               e.Cancel = true;
               bPaused = false;
           }
       }
   }
}

 

All typed in here, so no syntax checking etc. Should give you the jist.

 

-ner

"I want to stand as close to the edge as I can without going over. Out on the edge you see all the kinds of things you can't see from the center." - Kurt Vonnegut
Posted (edited)

Nerseus, thank you so much for taking a look at this... but it didn't seem to work. :(

 

 

Normally' date=' the button would call a function that does the looping.[/quote'] Yes, my original program has the button calling a Monte Carlo simulation that takes a while... and the Button_Click() routine is still held on the Stack. In VB6 this would not affect other Events from firing (as long as DoEvents, Sleep() or some other Yielding process is called) but here it is giving me trouble...

 

 

If you want to allow a cancel mid-process' date=' you'll want a form-level variable to handle closing. For example...[/quote'] Ok, I looked at your code and could not believe that it would work. It's a cleaner example than what I was doing, but if you look at the basic elements they are the same:

 

  1. They are both preventing recursion into the loop. My code is using 'If count = 0 Then' while yours is using 'if(!bPaused)'.
     
  2. They both Loop, cutting off with 'Until m_Shutdown' or 'While(bRunning)' respectively.

 

I would have been surprised if your code somehow did anything different. (And it didn't. :() Here is the exact code I used:

private bool bRunning = false;
private bool bPaused = false;
private int btnClickCount = 0;

private void btnStart_Click(object sender, System.EventArgs e)
{
   btnClickCount++;
   bRunning =true;
   Do
   {
       if(!bPaused)
       {
           // Processing code here, Progress bar updates...
       }
       Application.DoEvents();
   } while(bRunning);
}

private void Form1_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
   if(bRunning)
   {
       bPaused = true;
       DialogResult result = MessageBox.Show("Are you sure you want to Exit?",
           "Exit?",
           MessageBoxButtons.YesNo);
       if(result == DialogResult.Yes)
       {
           bRunning = false;
       }
       Else
       {
           e.Cancel = true;
           bPaused = false;
       }
   }
}

private void Form1_Closed(object sender, System.EventArgs e)
{
   string msgStr = "btnClickCount = " + btnClickCount.ToString("0");
   MessageBox.Show(msgStr);
}

It is exactly the code you provided, although I added the int btnClickCount to see how many times the btnStart_Click() Event was actually getting called.

 

The result is that btnClickCount returns 1, even though, graphically/visibly the Start button seems to depress a 2nd time when one clicks the Red [X] button to close the Form. And it is only on the second click on the Red [X] that the Form actually closes.

 

The project is attached, if you want to give it a try for yourself. Just hit the <Start> buttton and then click the Red [X] to exit the Form. You should have to click the [X] twice. And if you look closely, you'll see the <Start> button depress instead of the [X]. Very odd...

 

Thanks so much for you time so far! But I do hope you can get it... actually, I'm surprised that it is proving tricky at all!

 

Thanks again. :)

DoEventsTesterCS.zip

Edited by PlausiblyDamp

Posting Guidelines

 

Avatar by Lebb

  • *Experts*
Posted

I see what you mean - only it's a bit more than you describe. Meaning, when you click the start button it does run the code immediately. But, it's not waiting for a second click - it's waiting for any input. If you click anywhere on the form - the title bar, the form itself, etc. - it seems to cancel that click.

 

I didn't test this, but my guess is you could fix this one of two ways (maybe) or definitely one way (hacky). The easiest seems to use the API SendMessage with a LBUTTONUP message (can't remember the constant offhand). This would fake it so it seems like the user clicked and then let go of the mouse.

The other possible solution would take more experimenting but would involve overriding WndProc and intercepting clicks in there (maybe in conjuction with the form's KeyPreview set to true).

 

The definite solution is to enable a timer in the button's click and then have the timer call the function to do the loop. I call it hacky, but it would work because it uncouples the events from the procedural looping code.

 

An interesting problem - one I'd look into if I had more time.

 

-ner

"I want to stand as close to the edge as I can without going over. Out on the edge you see all the kinds of things you can't see from the center." - Kurt Vonnegut
Posted

Ner,

 

Thanks so much for taking a look at this...

 

 

 

I see what you mean - only it's a bit more than you describe. Meaning' date=' when you click the start button it does run the code immediately. But, it's not waiting for a second click - it's waiting for any input. If you click anywhere on the form - the title bar, the form itself, etc. - it seems to cancel that click.[/quote'] Yes, I wrote earlier:

 

"If you run the project, hit the <Start> button on the Form and then hit the <X> button, the interesting thing is that on the first click, the <Start> button will depress. This seems to occur no matter where you click on the Form or Titlebar as well."

 

 

 

I didn't test this' date=' but my guess is you could fix this one of two ways (maybe) or definitely one way (hacky)....[/quote'] Yes the SendMessage/Subclassing ideas are interesting, but sure seem overkill for something like this!

 

I did think of the Timer idea, and I agree with you that it is best. I don't even think of it as "Hacky" as you say, it's really pretty clean...

 

But it's also pretty "Dumb" if you think about it! I mean does every click that kicks off a long running process always have to call such a process indirectly? And has no-one else hit this before? Seems impossible, it's too common a situation... Very odd!

 

 

 

An interesting problem - one I'd look into if I had more time.

-ner

Oh, no problem, I thank you very much for the time you took!

 

Thanks Ner :)

Posting Guidelines

 

Avatar by Lebb

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...