Link to home
Start Free TrialLog in
Avatar of steinonline
steinonline

asked on

How to keep a double click on a button from firing the OnClick Event twice?

Hello experts, My question as best as I can put it, is:

I have numerous buttons in my application. Now, after limited commercial release, I have found out that certain users are using double clicks on these buttons.  The OnClick event of course is happening twice.   This is causing quite a memory leak in my program as there are a couple of forms created and much data loaded into memory.  Is there any way I can cause double click events and single click events to be interpreted in exactly the same way?  as a single click?

Users do the darndest things....  

Has anyone encountered this issue before, if so what did you do to solve it?

Sounds simple but it's not.  

My program has almost 200 different buttons in it, on various forms,  all with the capability to spawn multiple instances of dialogs and pointers on a double click.
Avatar of Mike Littlewood
Mike Littlewood
Flag of United Kingdom of Great Britain and Northern Ireland image

One possible quick fix would be to disable the button once clicked and reenable it once the OnClick event has finished.
This way they can click as many times as they want and it should only fire once.

procedure TForm1.Button1Click(Sender: TObject);
begin
  try
    Button1.Enabled := False;

    // Original code here

  finally
    Button1.Enabled := True;
  end
end

This is the sort of thing that happens on webpages when people submit data.
Avatar of steinonline
steinonline

ASKER

Yes that is a possible fix, one which I have already contemplated.  Only problem is, nothing quick about it (so many procedures need retrofitting that this is sort of unweildy) I'm looking for something I can do at either form or, application level such as a WindowProc listener hook.  Yours may actually be the only viable solution.  This issue is of paramount importance to me, so I'm looking for the best possible technique to solve the problem.  Not awarding points yet.  Thanks for the advice.
If this is all you are trying to do

>> Is there any way I can cause double click events and single click events to be interpreted in exactly the same way?  as a single click?

Why not just associate the single click function to the double click one as well.
Regardless of what they do it will fire off the same event.

Bear in mind that a double click should (if I remember right) fire off the single click event as well anyway.
I think trying to do something at the WindowProc level would not be the way to go in my opinion.
I know the other way is a little unweildy as you will need to do this for every button (unless you made your own decendant of the button which automatically puts in the code for you), in the long run though it is probably the safest thing to do.

Remember for next time I guess :o)
Avatar of Russell Libby
Here is one possible solution that works pretty nicely, and doesn't force you to modify each and every button to implement. Let me know if there are questions / problems.

Regards,
Russell

----

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;

type
  TForm1            =  class(TForm)
     Button1:       TButton;
     Button2:       TButton;
     Button3:       TButton;
     Button4:       TButton;
     Button5:       TButton;
     Memo1:         TMemo;
     procedure      FormCreate(Sender: TObject);
     procedure      Button1Click(Sender: TObject);
  private
     // Private declarations
     FLastButton:   HWND;
     FLastTime:     LongWord;
  protected
     // Protected declarations
     procedure      AppOnMessage(var Msg: TMsg; var Handled: Boolean);
  public
     // Public declarations
  end;

var
  Form1:            TForm1;

implementation
{$R *.DFM}

procedure TForm1.AppOnMessage(var Msg: TMsg; var Handled: Boolean);
var  ctlMessage:    TWinControl;
begin

  // Get the control from the window handle
  ctlMessage:=FindControl(Msg.hwnd);

  // Check control
  if Assigned(ctlMessage) then
  begin
     // Check class of control
     if (ctlMessage is TButton) then
     begin
        // Check message
        case Msg.Message of
           // Single click
           WM_LBUTTONDOWN    :
           begin
              // Check against last state
              if (FLastButton = ctlMessage.Handle) and (FLastTime + GetDoubleClickTime >= GetTickCount) then
              begin
                 // Update last click time
                 FLastTime:=GetTickCount;
                 // Don't allow click to fire
                 Handled:=True;
              end
              else
              begin
                 // Save last state
                 FLastButton:=ctlMessage.Handle;
                 FLastTime:=GetTickCount;
                 // Allow click to fire
                 Handled:=False;
              end;
           end;
           // Discard the double click message
           WM_LBUTTONDBLCLK  :  Handled:=True;
        end;
     end
     else
        // Not handled
        Handled:=False;
  end
  else
     // Not handled
     Handled:=False;

end;

procedure TForm1.FormCreate(Sender: TObject);
begin

  // Set last state
  FLastButton:=0;
  FLastTime:=0;

  // Bind application message handler
  Application.OnMessage:=AppOnMessage;

end;

procedure TForm1.Button1Click(Sender: TObject);
begin

  Memo1.Lines.Add((Sender as TButton).Caption);

end;

end.
Russell, you didn't post the code for the GetDoubleClickTime function..
And what code would that be?

----

GetDoubleClickTime

The GetDoubleClickTime function retrieves the current double-click time for the mouse. A double-click is a series of two clicks of the mouse button, the second occurring within a specified time after the first. The double-click time is the maximum number of milliseconds that may occur between the first and second click of a double-click.

UINT GetDoubleClickTime(VOID);
Parameters
This function has no parameters.

Return Values
The return value specifies the current double-click time, in milliseconds.

Requirements
  Windows NT/2000 or later: Requires Windows NT 3.1 or later.
  Windows 95/98/Me: Requires Windows 95 or later.
  Header: Declared in Winuser.h; include Windows.h.
  Library: Use User32.lib.

ASKER CERTIFIED SOLUTION
Avatar of ZhaawZ
ZhaawZ
Flag of Latvia image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
p.s. same sollution can be applied to other buttons (not only TButton)


type
  TSpeedButton = class(Buttons.TSpeedButton)
    protected procedure Click; override;
  end;
  TForm1 = class(TForm)
    SpeedButton1: TSpeedButton;
    procedure SpeedButton1Click(Sender: TObject);
  end;
...
implementation
...
procedure TSpeedButton.Click;
begin
if GetTickCount - Tag > GetDoubleClickTime then begin
  inherited;
  Tag := GetTickCount;
end;
end;
Oh, cool, I was unaware of that WindowsAPI fnction.
ZhaawZ,  that is going to do the trick nicely, just decend a button with an overridden click method, count time and act accordingly.  That is pretty slick.  Works well. I also like the ability to inform users that their usage habits aren't quite correct.  It is always a good thing to help the user understand the application.  Thanks for your insightful solution.  I appreciate it very much.  
Actually after a little tinkering, I have found that it works better for me to just simply disable the button, call inherited, re-enable the button, did it in one spot... 3 lines of code.... works for the entire application... 500 pts just for getting me out of the "box"
My ultimate solution was this....

type TButton = Class(stdctrls.tbutton)
protected
     Procedure Click; overrride;
end;


Procedure tButton.Click;
begin
  enabled:=false;
  inherited;  
  enabled:=true;
end;

This was actually all that was required to solve my problem.