Jump to content
Hafedh TRIMECHE

Prevent OnClick to be handled twice

Recommended Posts

This overridden procedure wont prevent multiple clicks

 

procedure TUButton.Click;
var
  CanGo : Boolean;
begin
  FLock.Lock;
  CanGo := (not FBusy);
  if CanGo then FBusy := True;
  Flock.Unlock;
  if (not CanGo) then Exit;
  inherited;
  FLock.Lock;
  FBusy := False;
  Flock.Unlock;
end;

Any possible solution?

 

Thanks.

Share this post


Link to post

Managing multiple clicks is a common issue that offer many solutions...one simple way around it is the following code:

 

procedure TUButton.Click;
begin
  TUButton.Enabled := False;
  try
  
    //do stuff
    
  except on E:Exception do
  begin 
    //handle errors
  end;

  TUButton.Enabled := True;
end;

 

  

Share this post


Link to post

Thanks for your reply.

 

Tried this solution but not solved the issue.

 

Disabling and Enabling the button itself is only valid for OnClick handler which takes a time larger than the one elapsed between 2 clicks.

 

I modified the code:

 

procedure EmptyKeyQueue;
var
  Msg : TMsg;
begin
  while PeekMessage(Msg,0,WM_KEYFIRST,WM_KEYLAST,PM_REMOVE or PM_NOYIELD) do;
end;

procedure EmptyMouseQueue;
var
  Msg : TMsg;
begin
  while PeekMessage(Msg,0,WM_MOUSEFIRST, WM_MOUSELAST,PM_REMOVE or PM_NOYIELD) do;
end;

procedure FlushInput;
begin
  EmptyKeyQueue;
  EmptyMouseQueue;
end;


procedure TUButton.Click;
begin
  FlushInput;
  if MilliSecondsBetween(Now,FLastClicked)<600 then Exit;
  FLastClicked := Now;
  inherited;
end;

 

 

Share this post


Link to post

Looks like you may have a bouncy mouse. I'd try another mouse and see if it works better. This shouldn't be an issue.

Share this post


Link to post

Which problem exactly are you trying to solve? Describe the scenario you are faced with.

 

Solution given by @Darian Miller is good. One similar is:

 

    protected
        FClickRunning : Boolean;

    procedure TUButton.Click;
    begin
        if FClickRunning then
            Exit;
        try
            FClickRunning := TRUE;

            // Do the stuff, call inherited and more

        finally
            FClickRunning := FALSE;
        end;
    end;

 

Share this post


Link to post

I guess that more than one message events are already queued before TUbutton.Click starts executing. So, This approach is applicable only if previous messages are removed from the Queue ; otherwise,  OnClick events will continue processing.

 

Removing messages is done using FlushInput defined into my reply above.

Share this post


Link to post

Set OnClick to nil in the event handler and leave it as nil until the program terminates. Then the event handler can never run again.

 

I presume that you want the event to run no more than once during the life of the process. Or is that assumption incorrect. 

Share this post


Link to post

If you want to prevent doubleclicking by users who still don't know the difference between single and double click (yes, they are still around, we have some overhere 🙂 ), we use a simple trick.

(Yelling to the users don't work 🙂 )

 

You set a timer on enabled in the onclick, something like

(at start of program you set the interval to like 250 orso,).

 

not a beauty, but it works here. 
 

(not exact code we use, but similar just out of the head):

const
   CNST_PREVEN_DOUBLECLICK_INTERVAL_MSECS = 250;

procedure TForm1.Button1Click(Sender : TObject);
begin
   if not Button1Timer.Enabled then 
  begin
      Button1Timer.Interval := CNST_PREVEN_DOUBLECLICK_INTERVAL_MSECS;
      Button1Timer.Enabled := True;
      DoButton1Code(); 
  end;
end; 

procedure TForm1.Button1TimerTimer(Sender : TObject);
begin
   Botton1Timer.Enabled := False;
end;
[/code]


maybe just use a "global" var, to remember lasttime clicked, and compare msecs to that could also be possible.

Edited by mvanrijnen

Share this post


Link to post

It's already done:

procedure TUButton.Click;
begin
  FlushInput;
  if MilliSecondsBetween(Now,FLastClicked)<600 then Exit;
  FLastClicked := Now;
  inherited;
end;

No need to set a timer. Any click invoked within 600 milliseconds interval will be discarded.

 

Thanks.

  • Like 1

Share this post


Link to post
1 hour ago, Hafedh TRIMECHE said:

The Event handler must resume execution.

Oh, I am confused. You said you don't want it to run twice.

Share this post


Link to post

I think you are solving the wrong problem.

 

Why are you getting multiple clicks enqueued that are so close together? They're supposed to be translated into double-clicks.

 

Clicks at the short intervals you're showing are NOT from a single user!

 

Find out what's spewing out all of those clicks and fix it.

 

Share this post


Link to post

Can you verify that you are not calling Click from other methods / event handlers?

Is it triggered from OnClick, or from  OnMouseUp/OnMouseDown?

Share this post


Link to post
Guest

I had once implemented almost exactly the same solution OP pasted above with checking against an interval, my client asked my to solve this problem because there was few operations that took long time on a server, few of his clients complained about the delay in that process, which was including over TCP data transaction and update locally and on server it was took in some cases long time ( more than few seconds !), so they were clicking buttons (eg. refresh ) many times causing DoS and stressing the server and the connection even more, that wasn't the problem per se, the problem started when he explained this and asked them to please don't over click these buttons, then and there started the problem, when few of them started to use AutoIt for fun !

 

Anyway, the difference from the above was that i made these intervals and behavior configurable form the server side per group of buttons, and everyone lived happily after.

 

ps: enabling and disabling the buttons didn't help as they were enabling them using tools and stuff, enabling and removing the event will do but will require timer to do it, in my opinion for this case the best solution was the interval as i didn't exit silently but a nag message made an educative lesson for the AutoIt users who hit it a button more than twice per second.

Share this post


Link to post
17 hours ago, Hafedh TRIMECHE said:

It's already done:


procedure TUButton.Click;
begin
  FlushInput;
  if MilliSecondsBetween(Now,FLastClicked)<600 then Exit;
  FLastClicked := Now;
  inherited;
end;

No need to set a timer. Any click invoked within 600 milliseconds interval will be discarded.

 

Thanks.

 

Note:  once you start down this path of customizing basic component behavior, it's best to derive a custom set of components.  Derive your own component from TButton and reuse whatever custom code that ends up working out for you.  You may find yourself with a custom version of all the basic components and you'll end up with customized behavior controlled in a central location.

 

Whatever happens, please don't copy-n-paste this code into 27 different Click events throughout your app...  

 

Share this post


Link to post
Quote
On 8/18/2021 at 9:00 PM, Darian Miller said:

 

Note:  once you start down this path of customizing basic component behavior, it's best to derive a custom set of components.  Derive your own component from TButton and reuse whatever custom code that ends up working out for you.  You may find yourself with a custom version of all the basic components and you'll end up with customized behavior controlled in a central location.

 

Whatever happens, please don't copy-n-paste this code into 27 different Click events throughout your app...  

 

 

You could have single click event and assign all custom TUButtons onClick to it.

 

This could stored in eventForm code.  Somewhat like a DataModule   Only we keep

message handlers in there. Also panels to use as needed by setting panel parent property to different form.     

 

The DataModule would use the eventForm code for future events Say enable submit button

when channel is on line and data fields all entered. 

 

   

Share this post


Link to post
Guest
1 hour ago, Pat Foley said:

You could have single click event and assign all custom TUButtons onClick to it.

 

This could stored in eventForm code.  Somewhat like a DataModule   Only we keep

message handlers in there. Also panels to use as needed by setting panel parent property to different form.     

 

The DataModule would use the eventForm code for future events Say enable submit button

when channel is on line and data fields all entered. 

image.png.2e571f8e910c69a4ba1d6e4d3b445626.png

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×