Recent

Author Topic: Is there build in way to distinguish between single mouse down and double clicks  (Read 1856 times)

daniel_sap

  • Jr. Member
  • **
  • Posts: 64
Hi,

I have a custom control which executes code on mouse down.
But when user double clicks I would like to execute different code.
Now, cause the double click event comes after second mouse down the first code is executed (even twice)

I'm planning to implement internal timer after every mouse down, to see if second mouse down will occur in short time,
but wanted to ask if there is something already developed in for custom controls in LCL,
or may be you know better approach to this.
(hm, may be I have to use click, double click, drag)
I'm mentioning drag here, cause this is also needed, not only double clicks, also drags

Thanks
« Last Edit: November 22, 2024, 10:57:59 am by daniel_sap »

silvercoder70

  • Full Member
  • ***
  • Posts: 125
    • Tim Coates
Doing this from memory...

you would need to look at (i) using OnMouseDown events, (ii) click counter and (iii) timer to detect time-out for a single click action.

the click counter has to reset at end of each complete action. Hope that makes sense.
Explore the beauty of modern Pascal programming with Delphi & Free Pascal - https://www.youtube.com/@silvercoder70

LV

  • Full Member
  • ***
  • Posts: 197
This trick works for me:

Code: Pascal  [Select][+][-]
  1. procedure TForm1.Panel1MouseDown(Sender: TObject; Button: TMouseButton;
  2.   Shift: TShiftState; X, Y: Integer);
  3. begin
  4.   if ssDouble in Shift then
  5.   begin
  6.     if ssLeft in Shift then
  7.       ShowMessage('Panel Left Double')
  8.     else
  9.       ShowMessage('Panel Right Double');
  10.   end;
  11. end;  
  12.  

Updated: see also topic https://forum.lazarus.freepascal.org/index.php/topic,62991.msg476740.html#msg476740
« Last Edit: November 22, 2024, 02:26:26 pm by LV »

silvercoder70

  • Full Member
  • ***
  • Posts: 125
    • Tim Coates
I think the easiest way is ....  :( :-[
Code: Pascal  [Select][+][-]
  1. procedure TForm1.ClickTimerTimer(Sender: TObject);
  2. begin
  3.   clickCount := 0;
  4.   ClickTimer.Enabled := False;
  5.   showMessage('code for single click');
  6. end;
  7.  
  8. procedure TForm1.Label1MouseDown(Sender: TObject; Button: TMouseButton;
  9.   Shift: TShiftState; X, Y: Integer);
  10. begin
  11.   Inc(clickCount);
  12.   if (clickCount = 1) then
  13.   begin
  14.     ClickTimer.Enabled := True;
  15.   end
  16.   else if (clickCount = 2) then
  17.   begin
  18.     clickCount := 0;
  19.     ClickTimer.Enabled := False;
  20.     showMessage('code for double click');
  21.   end;
  22. end;
  23.  
Explore the beauty of modern Pascal programming with Delphi & Free Pascal - https://www.youtube.com/@silvercoder70

Warfley

  • Hero Member
  • *****
  • Posts: 1855
You can simply do the following:
Code: Pascal  [Select][+][-]
  1. procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton;
  2.   Shift: TShiftState; X, Y: Integer);
  3. const
  4.   counter: Integer = 0;
  5. var
  6.   start: QWord;
  7. begin
  8.   Inc(counter);
  9.   if counter > 1 then
  10.     exit;
  11.   try
  12.     start := GetTickCount64;
  13.           { Threshold for how long to wait for double click in milliseconds }
  14.     while (GetTickCount64-start < 100) and
  15.           { if you want to allow for tripple or quadruple clicks change here }
  16.           (counter < 2) do
  17.       Application.ProcessMessages;
  18.  
  19.     ShowMessage('Clicks: ' + counter.ToString);
  20.   finally
  21.     counter := 0;
  22.   end;
  23. end;

PS: You should probably (as in my example) use mouseup not mousedown. Typically mous click events are triggered when the user lifts the button (so when you click and realize mid click you didn't want to you can just not release the button and move the mouse somewhere else). Triggering on mousedown would lead to rather confusing behavior

jamie

  • Hero Member
  • *****
  • Posts: 6791
Is there something wrong with the "OnDlbClick" that is already there?
The only true wisdom is knowing you know nothing

daniel_sap

  • Jr. Member
  • **
  • Posts: 64
This trick works for me:

Code: Pascal  [Select][+][-]
  1. procedure TForm1.Panel1MouseDown(Sender: TObject; Button: TMouseButton;
  2.   Shift: TShiftState; X, Y: Integer);
  3. begin
  4.   if ssDouble in Shift then
  5.   begin
  6.     if ssLeft in Shift then
  7.       ShowMessage('Panel Left Double')
  8.     else
  9.       ShowMessage('Panel Right Double');
  10.   end;
  11. end;  
  12.  
Very simple and clear approach, but still it triggers the Double click after one Mouse down. This will be perfect if you don't care about this one extra mouse down.

silvercoder70, Something like your solution I was thinking to implement(and may be still will go in this direction)
but when I saw your approach Warfley, I started to think that mouse ups are also important in this case.
And even another interesting situation can happen. Double click without release of the button (with hold).

Also, I read that windows by itself is starting timer for double click.

May be the timer solution is good, not to do a lot of empty iterations, combined with proper mouse up handling.
And I can implement it in some base custom control - TMyBaseCustomControl - so all inherited controls can benefit of it.

But if it is implement in LCL by default could be beneficial to all.

I saw this comment in TControl.WndProc - so I see there is that the focus is also involved in the game
It explains also why the mouse down event is sent immediately

Code: Pascal  [Select][+][-]
  1.       LM_LBUTTONDOWN,
  2.       LM_LBUTTONDBLCLK:
  3.         begin
  4.           Include(FControlState, csLButtonDown);
  5.           { The VCL holds up the mouse down for dmAutomatic
  6.             and sends it, when it decides, if it is a drag operation or
  7.             not.
  8.             This decision requires full control of focus and mouse, which
  9.             do not all LCL interfaces provide. Therefore the mouse down event
  10.             is sent immediately.
  11.  
  12.             Further Note:
  13.               Under winapi a LM_LBUTTONDOWN ends the drag immediate.
  14.             For example: If we exit here, then mouse down on TTreeView does
  15.               not work any longer under gtk.
  16.           }
  17.           if FDragMode = dmAutomatic then
  18.             BeginAutoDrag;
  19.         end;
  20.  
  21.       LM_LBUTTONUP:
  22.         begin
  23.           Exclude(FControlState, csLButtonDown);
  24.         end;
  25.     end;
  26.  


Ten_Mile_Hike

  • Jr. Member
  • **
  • Posts: 91
Is there something wrong with the "OnDlbClick" that is already there?

Code: Text  [Select][+][-]
  1. Jamie...Always asking the right questions :)
:D
When any government, or any church for that matter, undertakes to say to its subjects, This you may not read, this you
must not see, this you are forbidden to know, the end result is tyranny and oppression no matter how holy the motives.

Robert A. Heinlein

Ten_Mile_Hike

  • Jr. Member
  • **
  • Posts: 91
Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. var Diff: double;
  3. begin
  4.   if Button1.Tag = 0 then  //First user click after Oncreate (Keep your Tag at 0 at design time)
  5.   begin
  6.     Button1.Tag := 1;
  7.     D2 := Now + 1;
  8.   end;
  9.   Button1.tag := Button1.Tag * -1;
  10.   case Button1.Tag of
  11.     -1: D1 := now;
  12.      1: D2 := now;
  13.   end;
  14.   Diff := ABS(D2 - D1) * 1000000000;
  15.   if Diff < 5000 then                         {<- You can adjust this value}
  16.   begin
  17.     Button1.Caption := 'Double';
  18.     D1 := Now;
  19.     D2 := now;
  20.   end
  21.   else
  22.   begin
  23.     Sleep(300); {<-This is the trick to it all. Adjust the value to suit yourself but the smaller you make it the more chance "Single" will appear when DblClick is tried}
  24.     Button1.Caption := 'Single';
  25.   end;
  26. end;

Code: Text  [Select][+][-]
  1. Here is a "Dubious" solution to your question
When any government, or any church for that matter, undertakes to say to its subjects, This you may not read, this you
must not see, this you are forbidden to know, the end result is tyranny and oppression no matter how holy the motives.

Robert A. Heinlein

jamie

  • Hero Member
  • *****
  • Posts: 6791
This is my version, close to another above but I found a 100 ms not working very well.

Code: Pascal  [Select][+][-]
  1. procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton;
  2.   Shift: TShiftState; X, Y: Integer);
  3. Const WaitingForDouble:Boolean = false;
  4. Var
  5.   T:Dword;
  6. begin
  7.   If WaitingForDouble Then
  8.    Begin
  9.      Caption := 'Double Click';
  10.      WaitingForDouble := false;
  11.      Exit;
  12.    end;
  13.   WaitingForDouble := True;
  14.   T := GetTickCount;
  15.   While GetTickCount-T < 250 Do Application.PRocessMessages;
  16.   If WaitingForDouble Then
  17.    Begin
  18.      Caption := 'Single Click';
  19.    End;
  20. WaitingForDouble := false;
  21. end;                                  
  22.  

Hows that?

I double that will work in other widgets; seems something is strange with them, but this was a windows test.
The only true wisdom is knowing you know nothing

jamie

  • Hero Member
  • *****
  • Posts: 6791
The proper way of doing this in reality is to structure the code in such a way that the first click will always execute but in a such a way that the Double event will either add to or remove the first click results event.

 Normally double clicks are for additional options but whatever, I guess.
The only true wisdom is knowing you know nothing

LV

  • Full Member
  • ***
  • Posts: 197
This is my version, close to another above but I found a 100 ms not working very well.

If the end-user hasn't slept enough today, the 250ms delay for double-click may not work well. I prefer more reliable alternatives.

Code: Pascal  [Select][+][-]
  1. procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton;
  2.   Shift: TShiftState; X, Y: Integer);
  3. begin
  4.   if ssCtrl in Shift then
  5.     ShowMessage('Left + Ctrl') // or execute code A
  6.   else
  7.     ShowMessage('Left'); // or execute code B
  8. end;
  9.  

 

TinyPortal © 2005-2018