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 strongm on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

controls are still feeling your clicks 4

Status
Not open for further replies.

leopardo

Technical User
May 14, 2005
18
AR
Hello my Friends.

Working with D2 in W98SE.


The property "Enabled", does it really enable or disable the related control ???. Here's a simple example:

procedure Button1.OnClick(Sender: TObject);
begin
Button1.Enabled:= false;

... Do the process required, it takes some seconds

Button1.Enabled:= true;
end;

Simple code for preventing the user to re-start the execution of the process while it's running.

But If you click again (accidentally?) on this disabled button while the process is being executed, the process will be executed again after the line 'end' is reached. Furthermore, the number of times you click the button while it's disabled, the number of times the process will be executed.
This happens not only to buttons but to other controls too; checkboxes, TEdit's etc. Even though they look disabled, any action with the mouse or keystrokes on them, whill be evident when they become enabled again. What is happening and how can I turn them 'insensible' when they're disabled?.

I'll appreciate so much your comments. [morning]
 
Hi,

my code with the timer was just to prove that there wasn't any buffering taking place (sorry gcaramia, didn't see your post), not to offer a solution.

I think a viable solution is to make descendant class of TButton. like TInsensitiveButton and implement the TTimer trick into that class, register the new class and use the newly created component.

Please keep in mind that this behaviour isn't delphi's fault. in fact it is due to windows queuing message for the application when it is not responding.

take this code for instance :

Code:
procedure TForm1.Button1Click(Sender: TObject);

var I : integer;

begin
 button1.Enabled:=False;
 I:=StrToInt(Label1.Caption);
 Inc(I);
 Label1.Caption:=IntToStr(I);
 sleep(1000);
 button1.Enabled:=True;
end;

as you see I call sleep(1000) to simulate lengthy processing. this code will expose the buffering behaviour.

this is what happens in reality when you click on the button 2 times fast in a row :

-first click
windows detects mouseclick and sends WM_LBUTTONDOWN message to app
app message loop will get the WM_LBUTTONDOWN message
dispatches it to the form
and executes the Onclick procedure,
button is disabled and Sleep function will halt the thread
-second click
windows windows detects mouseclick and sends WM_LBUTTONDOWN message to app, since app is still halted message is queued.

sleep function ends button is enabled and returns to message loop.
message loop sees the second WM_LBUTTONDOWN and reexecutes the Onclick proc since the button is enabled at this point

now see this code :

Code:
procedure TForm1.Button1Click(Sender: TObject);

var I,J : integer;

begin
 button1.Enabled:=False;
 I:=StrToInt(Label1.Caption);
 Inc(I);
 Label1.Caption:=IntToStr(I);
 for j:=1 to 100 do
  begin
   sleep(10);
   Application.ProcessMessages
  end;
 button1.Enabled:=True;
end;

big difference with previous code part is that I call
Application.Processmessages at REGULAR interval (10 millseconds), thus giving the app the possibility to process it's message queue. this means that the SEES the mouse clicks while still being in the OnCLick procedure.
since the button is disabled, the WM_LBUTTONDOWN messages will be ignored for the disabled button.
In fact I'm just repeating what donvittorio said a few posts back. put ENOUGH application.processmessages in your lenghty processing part (like loops) and you will not have this problem anymore...




--------------------------------------
What You See Is What You Get
 
Whosrdaddy.

Your code really works. Your explanation is awesome.
The sun is shining again.

Thanks you all my friends again. I'm really eating the crumbs of bread from the gods here.

Leopardo.
 
I've just done a quick test with the following:
Code:
procedure TForm1.Button1Click(Sender: TObject);
var a:integer;
begin
  button1.Enabled := false;
  for a := 0 to 10000 do
  begin
    edit1.Text := inttostr(a);
    edit1.Refresh
  end;
  application.ProcessMessages;
  button1.Enabled := true
end;

It seems, therefore, that ProcessMessages need only be called once - immediately before button1.Enabled := true, thus flushing the cache before re-enabling the button, and avoiding unnecessarily slowing the program by running in timed loop.
 
Thanks whosrdaddy for your explanations, as always they are plenty of thechnical informations, big profit for we all.

Your idea and the one of donvittorio have determined the best solution from earthandfire.

A star for him too.

Giovanni Caramia
 
though earthandfire's code is correct,
keep in mind that lengthy operations make your app irresponsive, a better implementation would be :

Code:
procedure TForm1.Button1Click(Sender: TObject);
var a:integer;
begin
  button1.Enabled := false;
  for a := 0 to 10000 do
  begin
    edit1.Text := inttostr(a);
    edit1.Refresh
    if (a mod 100) = 0 then
     application.ProcessMessages;
  end;
  button1.Enabled := true
end;

this give you the best of both worlds, a responsive app and no message buffering...

--------------------------------------
What You See Is What You Get
 
If I might offer one further refinement to that proposed by whosrdaddy.

I tested the following by repeatedly clicking the button and the problem was still there.
Code:
procedure TForm1.Button1Click(Sender: TObject);
var a:integer;
begin
  button1.Enabled := false;
  for a := 0 to [b]9999[/b] do
  begin
    edit1.Text := inttostr(a);
    edit1.Refresh;
    if (a mod 100) = 0 then
     application.ProcessMessages;
  end;
  button1.Enabled := true
end;

The important point is that the message queue must be flushed after a careless user's last click and before re-enabling the button. Mod x does not guarantee this as shown above. I agree that if the process is lengthy, ProcessMessages should be called at regular intervals. It must also be called immediately before re-enabling the button.

Code:
procedure TForm1.Button1Click(Sender: TObject);
var a:integer;
begin
  button1.Enabled := false;
  for a := 0 to [b]9999[/b] do
  begin
    edit1.Text := inttostr(a);
    edit1.Refresh;
    if (a mod 100) = 0 then
     application.ProcessMessages;
  end;
  [b]application.ProcessMessages;[/b]
  button1.Enabled := true
end;
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top