none
Application.DoEvents() not working RRS feed

  • Question

  •  

    Hi,

    I was trying to explain why we should not use Application.DoEvents() in a Windows Forms app in a training session, when I found it didn't work as expected even in a very simple scenario like this:

     

    (1) I put a Start button that starts a For/Next loop of 100 with a Thread.Sleep(50) in each iteration to simulate a long-running method. I put an Application.DoEvents() in the loop so that the form can handle other events while it's executing the loop. I update a progress bar inside the loop so I can see if it's running the loop.

     

    (2) I put a Cancel button that changes a Boolean flag. Right after the DoEvents() call in the loop, I check the flag and abort the loop if canceled.

     

    The problem is that I can't cancel the loop with a single click on the Cancel button. I needed clicking it twice to cancel it. I put a breakpoint in the Cancel button's click event handler and it didn't fire for the very first click.

     

    After I clicked the Start button, I immediately tried to move the form by dragging the window title area only to fail. However, if I tried doing the same thing one more time before the loop finished, it worked.

     

    It seems that the very first event after the Start button's click event is always ignored.

     

    Here's the entire code:

     

    Public Class Form1

        Private m_Canceled As Boolean

     

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

            m_Canceled = False

            ToolStripProgressBar1.Value = 0

            RunTask()

        End Sub

     

        Private Sub RunTask()

            For i = 0 To 100

                Thread.Sleep(50)

                ToolStripProgressBar1.Value = i

                Application.DoEvents()

                If m_Canceled Then

                    Exit For

                End If

            Next

        End Sub

     

        Private Sub btnCancel_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCancel.Click

            m_Canceled = True

        End Sub

    End Class

     

    The point of the training was to teach how to use asynchronous delegates or the Background worker to cope with a long-running task, and I really don't care if the DoEvents() doesn't work at all. But the same technique worked with VB6 and it was really embarrassing. Could anyone tell what I am doing wrong here? I was using VS2008 pro SP1 with V3.5 .NET on Vista SP1.

     

    Thanks,


    tetsu
    Sunday, November 2, 2008 6:47 PM

Answers

  • Add this line to btnStart_Click to solve your problem:

      btnStart.Capture = False
      ' etc...


    Hans Passant.
    • Proposed as answer by Arjun Paudel Sunday, November 2, 2008 7:44 PM
    • Marked as answer by tetsu1 Sunday, November 2, 2008 11:04 PM
    Sunday, November 2, 2008 7:43 PM
    Moderator

All replies

  • hi, i removed the line

    Thread.Sleep(50)

    and the event is firing correctly.

    Love to program in C#.
    Sunday, November 2, 2008 6:55 PM
  • If you want your program to operate as you want, you'll have to exit the btnStart_Click event before you RunTask().  The easiest way is to start a timer and place RunTask in the timer's tick event.
    Sunday, November 2, 2008 7:18 PM
  • Using DoEvents is not recommended any more, So its better to use Thread in your case.

    Your button click is not firing up when you click once since ui is unresponsive at that time but if you click twice or thrice might work.

    yes as you said it ignores first event!!

    Arjun Paudel
    Sunday, November 2, 2008 7:36 PM
  • Add this line to btnStart_Click to solve your problem:

      btnStart.Capture = False
      ' etc...


    Hans Passant.
    • Proposed as answer by Arjun Paudel Sunday, November 2, 2008 7:44 PM
    • Marked as answer by tetsu1 Sunday, November 2, 2008 11:04 PM
    Sunday, November 2, 2008 7:43 PM
    Moderator
  • Thanks I tested it works fine, by the way Hans, I never learn about capturing mouse by a control, Nice suggestion

    Arjun Paudel
    Sunday, November 2, 2008 7:50 PM
  • nobugz Will you explain a bit what's happening  in the code?, btnStart.Capture=False

    Love to program in C#.
    • Edited by Yam Sapkota Sunday, November 2, 2008 7:57 PM
    Sunday, November 2, 2008 7:55 PM
  • The timer approach will work for any event.  I think Microsoft recommends exiting events as quickly as possible and this approach accomplishes that:


    Public
    Class Form1

      Private m_Canceled As Boolean

      Private Sub RunTask()

        For i As Integer = 0 To 100

          Threading.Thread.Sleep(50)

          Application.DoEvents()

          If m_Canceled Then

            Exit For

          End If

        Next

      End Sub

      Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click

        m_Canceled = True

      End Sub

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

        m_Canceled = False

        Timer1.Interval = 1

        Timer1.Start()

      End Sub

      Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick

        Timer1.Stop()

        RunTask()

      End Sub

    End Class

    Sunday, November 2, 2008 7:58 PM
  • The button control uses the MouseDown event to redraw the button, making it look pressed.  To make sure it sees the MouseUp event, and redraw the button again to look unpressed, it must capture the mouse.  So that when the user clicks, moves, and releases the button, everything still paints correctly.  Calling DoEvents() in a loop during the Click event gums up that logic, the button doesn't get a chance to release the capture itself.

    Such are just one of the problems with DoEvents(), there are many more.  Really bad ones are the user clicking the button again or closing the form.  A quick fix for the capture problem is using the MouseUp event instead of Click.


    Hans Passant.
    Sunday, November 2, 2008 8:26 PM
    Moderator
  • If you complete all UI events using a timer when you want to synchronously call a method, I think you will avoid the pitfalls of using DoEvents.  Alternately, including the timer in a looping method to allow some idle time will accomplish the same thing as DoEvents, which is to allow the message queue to flush.
    Sunday, November 2, 2008 8:57 PM
  • I discovered a very effective technique for this that doesn't require a Timer.  I've used it many times to avoid event nesting problems.  Especially with TreeView, a very temperamental control.  It also solves the Capture problem:

    Public Class Form1
      Private mQuit As Boolean
      Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        Button1.Enabled = False
        Me.BeginInvoke(New MethodInvoker(AddressOf DelayedClick))
      End Sub
      Private Sub DelayedClick()
        Do While Not mQuit
          Application.DoEvents()
        Loop
      End Sub
      Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
        MsgBox("Click!")
      End Sub
      Private Sub Form1_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
        mQuit = True
      End Sub
    End Class


    Hans Passant.
    Sunday, November 2, 2008 10:28 PM
    Moderator
  • So, btnStart.Capture = False is the secret ingredient...

    Thanks nobugz and others who joined the discussion.

     

    FYI, I found that the same solution had already been posted here - http://social.msdn.microsoft.com/Forums/en-US/vblanguage/thread/fc47d9c4-f374-4420-99fb-6dc3b6a8f3df.

     

    Thanks anyways.


    tetsu
    Monday, November 3, 2008 5:55 PM
  • i have never had a problem using application.doevents properly.  i have seen a lot of issues directed at the improper use of it.

    i did notice in the documentation that doevents no longer calls thread.sleep.  i wonder if that means no other thread can execute?
    • Edited by dbasnett Monday, November 3, 2008 7:41 PM
    Monday, November 3, 2008 6:17 PM
  • I haven't used DoEvents since 1982...

    tetsu1 said:

     

    I was trying to explain why we should not use Application.DoEvents() in a Windows Forms app in a training session, when I found it didn't work as expected even in a very simple scenario like this:

     


    I think this sums up your issue - DoEvents seems like a simple thing to use, but takes a bit of mastering, and work to figure out the repercussions. However, once you master when you can or cannot (should or should not) use DoEvents effectively, you will find you no longer need it.

    Sounds like your demonstration of why we don't normally advocate the use of DoEvents was quite apt.

    Stephen J Whiteley
    Monday, November 3, 2008 8:45 PM
    Moderator
  • SJWhiteley said:

    I haven't used DoEvents since 1982...


    SJWhiteley, how will you write the following code :

    Private Sub Button6_Click(ByVal sender As System.ObjectByVal e As System.EventArgs) Handles Button6.Click 
            Do 
                 
                Me.Text = DateTime.Now.Ticks 
                 
                Application.DoEvents() 
            Loop 
        End Sub 


    if you found answer for your question , always click the 'Mark as Answer' button on the respective answers.
    Monday, November 3, 2008 9:31 PM
  • Yam Sapkota said:

    SJWhiteley said:

    I haven't used DoEvents since 1982...


    SJWhiteley, how will you write the following code :

    Private Sub Button6_Click(ByVal sender As System.ObjectByVal e As System.EventArgs) Handles Button6.Click 
            Do 
                 
                Me.Text = DateTime.Now.Ticks 
                 
                Application.DoEvents() 
            Loop 
        End Sub 


    if you found answer for your question , always click the 'Mark as Answer' button on the respective answers.

    I wouldn't ever do that (which just looks like a disaster waiting to happen).

    If I wanted to display ticks in a text box repeatedly like that I'd use a separate thread or a timer: depends on what I was doing. There's no real advantage to updating the display faster than 30 milliseconds, anyway: 100-300 millisecs is usually fine.

    To do 'just' the above, I'd do something like:
    Public Class Form1 
     
        Private WithEvents UpdateTimer As Windows.Forms.Timer 
     
        Private Sub Form1_Load(ByVal sender As System.Object, _ 
         ByVal e As System.EventArgs) _ 
         Handles MyBase.Load 
            UpdateTimer = New Windows.Forms.Timer 
            UpdateTimer.Interval = 45 
            UpdateTimer.Start() 
        End Sub 
     
        Private Sub UpdateTimer_Tick(ByVal sender As Object, _ 
          ByVal e As System.EventArgs) _ 
          Handles UpdateTimer.Tick 
            ' Who is 'label1'? 
            Label1.Text = DateTime.Now.Ticks.ToString 
        End Sub 
     
    End Class 
     

    I know a lot of (some? a few?) people use DoEvents. I don't, because threads are just too sexy ;)



    Stephen J Whiteley
    Monday, November 3, 2008 10:26 PM
    Moderator