• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 1351
  • Last Modified:

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.
0
steinonline
Asked:
steinonline
  • 4
  • 3
  • 2
  • +2
1 Solution
 
mikelittlewoodCommented:
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.
0
 
steinonlineAuthor Commented:
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.
0
 
mikelittlewoodCommented:
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.
0
What does it mean to be "Always On"?

Is your cloud always on? With an Always On cloud you won't have to worry about downtime for maintenance or software application code updates, ensuring that your bottom line isn't affected.

 
mikelittlewoodCommented:
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)
0
 
Russell LibbySoftware Engineer, Advisory Commented:
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.
0
 
Eddie ShipmanAll-around developerCommented:
Russell, you didn't post the code for the GetDoubleClickTime function..
0
 
Russell LibbySoftware Engineer, Advisory Commented:
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.

0
 
ZhaawZSoftware DeveloperCommented:
aargh.. all those "long sollutions"... here's a short one:


unit Unit1;

interface

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

type
  TButton = class(StdCtrls.TButton)
  protected
    procedure Click; override;
  end;
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TButton.Click;
begin
// if user does double-click, show message.. or just ignore clicking
if GetTickCount - Tag < GetDoubleClickTime then begin
  // ShowMessage('only 1 click is needed');
end else begin
  // if user hasn't clicked for at least GetDoubleClickTime() miliseconds, process onClick procedure
  inherited;
  // remember the time when button is clicked
  Tag := GetTickCount;
end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
// for testing try to click button1 several times
Beep;
end;

end.
0
 
ZhaawZSoftware DeveloperCommented:
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;
0
 
Eddie ShipmanAll-around developerCommented:
Oh, cool, I was unaware of that WindowsAPI fnction.
0
 
steinonlineAuthor Commented:
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.  
0
 
steinonlineAuthor Commented:
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"
0
 
steinonlineAuthor Commented:
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.
0

Featured Post

Free Tool: Site Down Detector

Helpful to verify reports of your own downtime, or to double check a downed website you are trying to access.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

  • 4
  • 3
  • 2
  • +2
Tackle projects and never again get stuck behind a technical roadblock.
Join Now