Question

Consider the following piece of code executed within the OnClick event of a given button:

procedure TForm1.Button1Click(Sender: TObject);
begin
  button1.enabled := false;    //Line 1  
  application.processmessages; //Line 2  
  Sleep(3000);                 //Line 3
  button1.enabled := True;     //Line 4
  Release;                     //Line 5
end;

In Delphi 2010, if after clicking this button you manage to perform yet another click on it while the execution is busy in Line 3, the subsequent click event will apparently get stored in the queue of commands, thus when Release(Line 5) procedure is called, the application will attempt to process it. Consequently the click event will be triggered once again. The second time around, the button component has already been destroyed, hence "access violation" error get's raised.

The whole concept of acknowledging the second click by the system when the respective button is disabled does not seem to be sound. Any explanations to this shady behavior?

Was it helpful?

Solution

The system is behaving exactly as designed, but be aware that your code is going against all sound design principles. Specifically the use of Sleep and ProcessMessages in an input event handler are both to be frowned upon.

The reason that the program behaves this way is as follows:

  1. The user generates an input message by clicking the mouse.
  2. This input event is placed in the input queue for the appropriate thread.
  3. That thread is not servicing its input queue (it is sleeping) and so the input message, which is a mouse down, mouse up combo, sits there.
  4. The thread wakes up and enables the button.
  5. The button OnClick handler returns and the application's message loop continues.
  6. In due course the mouse down and mouse up messages are processed (before the CM_RELEASE message) and so the button OnClick handler runs again.
  7. The button OnClick handler calls ProcessMessages which then handles the CM_RELEASE and kills the form.
  8. BOOM!

The whole concept of acknowledging the second click by the system when the respective button is disabled does not seem to be sound.

The key point is that the enabled state of the button is checked when the input message is processed and not when the input message is generated. It has to be this way because input messages are extremely low level things, and it's only the application that can interpret them as things like button clicks.

There are plenty of ways to fix your code, but I'm loathe to suggest any because this is clearly code for illustration. But I will say that all sound solutions will involve the removal of the calls to Sleep and `ProcessMessages.

OTHER TIPS

During the Sleep your application is non responsive. The click message is queued and is only processed after you re-enabled the button (actually after the event handler method is fully executed and the application has gone idle again).

To solve it, also execute Application.ProcessMessages after sleep, before enabling the button. That will empty your message queue first and will discard the click message.

Or just don't enable the button at all. Why would you, if you're going to release the form anyway?

A (probably) better solution would be to execute the Sleep in a separate thread, but since Sleep here is probably just a stub for some real code, it's hard to say how much effort it will take to do that.

Anyway, your current application isn't good, and calling Application.ProcessMessages in situations like this is likely to generate 'random' bugs once in a while. The best you can do is limit the risk, but there is no good way to solve it, other than radically change this implementation.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top