Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Chriss Miller on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Button1.Enabled = False DOES NOT mean Button1 is disabled 1

Status
Not open for further replies.

earthandfire

Programmer
Mar 14, 2005
2,924
GB
There seems to be a lot of emotion regarding whether or not it is valid to use Application.DoEvents

The following code block is a time consuming process:

Code:
  Private Sub ProcessTakingALongTime()

    For a As Integer = 1 To 10
      For b As Integer = 1 To 100000000
        'do something really important here
      Next
      'simulate a progress bar
      Label2.Text = a.ToString
      Label2.Refresh()
    Next

  End Sub


To run this:

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

    Button1.Enabled = False
    ProcessTakingALongTime()
    Button1.Enabled = True

  End Sub

Two things:
(1) The program is unresponsive
(2) If the user clicks on the disabled button the code will run again (once for each click) despite it supposedly being disabled.

If ProcessTakingALongTime is spun off onto a separate thread, the program will become responsive, however the code will repeatedly run (once for each click).

Judicious use of Application.DoEvents solves both problems:

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

    Button1.Enabled = False
    ProcessTakingALongTime()
    Button1.Enabled = True
    Application.DoEvents()

  End Sub

gives an unresponsive program (unless multi-threaded) but processes the message queue before re-enabling the button.

Code:
  Private Sub ProcessTakingALongTime()

    For a As Integer = 1 To 10
      For b As Integer = 1 To 100000000
        'do something really important here
      Next
      'simulate a progress bar
      Label2.Text = a.ToString
      Label2.Refresh()
      Application.DoEvents()
    Next

  End Sub

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

    Button1.Enabled = False
    ProcessTakingALongTime()
    Button1.Enabled = True

  End Sub

gives a responsive program, if the user does happen to click on the disabled button, nothing will happen.

I would be interested to know what those who advocate not using Application.DoEvents would do in this situation.

By the way, this phenomenon or feature was discussed in detail in thread102-1068694
 
I would be interested to know what those who advocate not using Application.DoEvents would do in this situation.

I would launch the long time taking process in a seperate thread using delegates. I would disable the button before calling the new thread. I would then use a call back delegate to re-enable the button when the long time process completed.

-Rick

VB.Net Forum forum796 forum855 ASP.NET Forum
[monkey]I believe in killer coding ninja monkeys.[monkey]
 
That would not overcome the problem with the user clicking the "disabled" button. The process would still restart.
 
If the user clicks on the disabled button the code will run again (once for each click) despite it supposedly being disabled.

I think the button gets disabled but the buffer kicks in and will do another click once the button is enabled. Something like the keyboardbuffer. I think this since the code can only be executed sequentially.

Christiaan Baes
Belgium

I just like this --> [Wiggle] [Wiggle]
 
Yeah, chrissie, that's what does happen - and Application.DoEvents seems to be the only way round it (ie flushing the buffers), unless someone can provide an alternative, hence the reason for this thread. I've given up (more or less) the fight on Modules, but I can't see a way of avoiding this - unless it is possible to write a program to physically prevent a user form clicking a disabled button. [smile]
 
Since I think it's something windows. I think thy have to learn to live with it.

What do they expect if they click twice on a button then then thing will run twice, bad luck. if people click twice on the quick launch br then the program will open twice, bad luck next time only click once. I know most idiots are users and they don't always listen well bad luck I only go so far to make it idiotproof if they don't understand that cliking a button twice will make it run twice (without crashing or doing something dangerous), bad luck to them.

Christiaan Baes
Belgium

I just like this --> [Wiggle] [Wiggle]
 
You are heartless chrissie, but I do agree, if a button has been disabled it means "don't click me, I'm not available" - but for one additional line of code it can be made to mean "there is no point in clicking me, I'll just ignore you".
 
On the other hand, i don't know wy they are against doevents, I think it was chiph and I believe everything he says.

Christiaan Baes
Belgium

I just like this --> [Wiggle] [Wiggle]
 
Rick, you can click the mouse anywhere (and at any time) - the question is whether or not and by what the click is processed.

If the button is re-enabled while click messages for that button are still in the message queue they will then be passed on to the button. This will then cause the button to act as if it had been clicked again (thinking that the clicked occurred after being re-enabled), thus defeating any threading solution.

As far as I am aware the only way to prevent this is to flush these messages before re-enabling the button, which is exactly what Application.DoEvents does. Unless, of course you want to write something lower level to handle this - which I don't intend doing since I think that is overkill.
 
I know I'm not at your level, but please bear with me. Isn't the reason the button isn't really disabled in the original code due to the fact that the Click handler is never completed until the long running process is completed?

Why couldn't you do something like the following:
Code:
Private m_SystemTimer As System.Timers.Timer

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

   Button1.Enabled = False
   CreateSystemTimer()

End Sub

'Used to start long running process
Private Sub CreateSystemTimer()

   m_SystemTimer = New System.Timers.Timer
   With m_SystemTimer
      .Interval = 1
      .Enabled = True
      AddHandler .Elapsed, New ElapsedEventHandler(AddressOf SystemTimerElapsed)
   End With

End Sub

'Let button code finish
Private Sub SystemTimerElapsed(ByVal sender As System.Object, _ 
                               ByVal e As System.Timers.ElapsedEventArgs)

   ProcessTakingALongTime
   m_SystemTimer.Enabled = False
   m_SystemTimer.Dispose()

End Sub

Private Sub ProcessTakingALongTime()

   For a As Integer = 1 To 10
      For b As Integer = 1 To 100000000
         'do something really important here
      Next
      'simulate a progress bar
      Label2.Text = a.ToString
      Label2.Refresh()
      Application.DoEvents()
   Next

   Button1.Enabled = True

End Sub


Have a great day!

j2consulting@yahoo.com
 
Thanks for the compliment. One of the problems with being a one man show is that I can be pretty deep in levels I've had to learn about while almost entry level in others that I haven't been exposed to yet. That's why I love this board because guys like you, the ricks, earthandfire and others freely share what you have learned. Thanks again!

Have a great day!

j2consulting@yahoo.com
 
SBendBuckeye, I have to agree with chrissie
I'm not so sure about that.

However with regard to your suggested solution - all that to avoid Application.DoEvents, it it really worth it?
 
Sorry I've just spotted that you kept Application.DoEvents - which is all that is needed to flush the message queue.
 
yeah, shouldn't we better talk about modules?

Christiaan Baes
Belgium

I just like this --> [Wiggle] [Wiggle]
 
Code:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
   Button1.Enabled = False
   Protected del As ProcessDelegate
   del = New ProcessDelegate(AddressOf Me.Process)
   Dim cb As New AsyncCallback(AddressOf Me.ProcessComplete)
   del.BeginInvoke(cb, del)
End Sub

'The delegate that will launch the process
Protected Delegate Sub ProcessDelegate()

'The Callback method the will launch in the original thread
'when the thread completes
Protected Sub ProcessComplete(ByVal ar As System.IAsyncResult)
  me.button1.enabled = true
end sub

'The long process
Protected Sub Process()
   For a As Integer = 1 To 10
      For b As Integer = 1 To 100000000
         'do something really important here
      Next
      'simulate a progress bar (This stuff should be handled
      'in a thread safe manor, but this will work pre-2k5)
      Label2.Text = a.ToString
      Label2.Refresh()
   Next
End Sub

Yes it's more code then application.doevents, but check the performance between the two. And what good is doevents if instead of just doing integer math, you're doing an IO intensive process? DoEvents won't help while that process is going, just imediately before or after.

DoEvents is a crutch. It's a poor performer, it doesn't work well in many situations, and there are far better options.

-Rick

VB.Net Forum forum796 forum855 ASP.NET Forum
[monkey]I believe in killer coding ninja monkeys.[monkey]
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top