* * *

Author Topic: Clear button pressed (and other GUI events) queue  (Read 565 times)

richardcgiroux

  • New member
  • *
  • Posts: 18
Clear button pressed (and other GUI events) queue
« on: November 13, 2017, 05:31:17 pm »
I have a program that takes some time to complete an action after a button press.  I run into a problem is the user keeps pressing the button, queuing additional button presses.  Is there a way that I can prevent any "queuing" of button presses until the present action is completed?

This is an issue throughout the entire program, not just with one button.

GetMem

  • Hero Member
  • *****
  • Posts: 2415
Re: Clear button pressed (and other GUI events) queue
« Reply #1 on: November 13, 2017, 05:39:20 pm »
Try this:
Code: Pascal  [Select]
  1.  TForm1 = class(TForm)
  2.     Button1: TButton;
  3.     procedure Button1Click(Sender: TObject);
  4.   private
  5.     FIsBusy: Boolean;
  6.   public
  7.  
  8.   end;
  9.  
  10. //....
  11.  
  12. procedure TForm1.Button1Click(Sender: TObject);
  13. begin
  14.   if FIsBusy then
  15.     Exit;
  16.  
  17.   FIsBusy := True;
  18.   try      
  19.     //lengthy operation
  20.   finally
  21.     FIsBusy := False;
  22.   end;
  23. end;  
             

You can also disable the button as a visual feedback.

Thaddy

  • Hero Member
  • *****
  • Posts: 4629
Re: Clear button pressed (and other GUI events) queue
« Reply #2 on: November 13, 2017, 05:57:06 pm »
That is correct:
I also would suggest to give the impatient user some feedback, under windows normally an hourglass cursor. Linux seems to mimic that...
And check your design...

Anyway: disable the possibility. I would disable the button during operations AND change the cursor.
« Last Edit: November 13, 2017, 05:59:49 pm by Thaddy »
"Logically, no number of positive outcomes at the level of experimental testing can confirm a scientific theory, but a single counterexample is logically decisive."

mse

  • Full Member
  • ***
  • Posts: 233
Re: Clear button pressed (and other GUI events) queue
« Reply #3 on: November 13, 2017, 06:10:00 pm »
MSEgui has application.beginwait()/application.endwait() for that purpose. After calling application.beginwait() the mouse pointer shape becomes cr_wait and mouse- and key-events will be skipped in application.processmessages(). application.waitescaped() returns true if the ESC-key has been pressed so one can cancel long running operations. Maybe Lazarus provides a similar mechanism?
« Last Edit: November 13, 2017, 06:18:45 pm by mse »

howardpc

  • Hero Member
  • *****
  • Posts: 2396
Re: Clear button pressed (and other GUI events) queue
« Reply #4 on: November 13, 2017, 06:13:42 pm »
Putting thaddy's suggestion into code gives
Code: Pascal  [Select]
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. var
  3.   b: TButton absolute Sender;
  4.   oldCursor: TCursor;
  5. begin
  6.   if Sender is TButton then begin
  7.     WriteLn('TForm1.Button1Click');
  8.     oldCursor:=Screen.Cursor;
  9.     b.Enabled:=False;
  10.     Screen.Cursor:=crHourGlass;
  11.     try
  12.       // some lengthy operation
  13.     finally
  14.       b.Enabled:=True;
  15.       Screen.Cursor:=oldCursor;
  16.     end;
  17.   end;
  18. end;

GetMem

  • Hero Member
  • *****
  • Posts: 2415
Re: Clear button pressed (and other GUI events) queue
« Reply #5 on: November 13, 2017, 06:50:48 pm »
@howardpc
That code(without Application.ProcessMessages) can byte back, especially under windows. Please try the following: declare a private variable FCounter then press the button 3-4 times in less then five seconds. It clearly shows the button wasn't disabled in the first place.
Code: Pascal  [Select]
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. var
  3.   b: TButton absolute Sender;
  4.   oldCursor: TCursor;
  5. begin
  6.   if Sender is TButton then begin
  7.     oldCursor:=Screen.Cursor;
  8.     b.Enabled:=False;
  9.     Screen.Cursor:=crHourGlass;
  10.     try
  11.       Sleep(5000);
  12.       Inc(FCounter);
  13.       Form1.Caption := IntToStr(FCounter);
  14.         // some lengthy operation
  15.     finally
  16.       b.Enabled:=True;
  17.       Screen.Cursor:=oldCursor;
  18.     end;
  19.   end;
  20. end;
  21.  

Thaddy

  • Hero Member
  • *****
  • Posts: 4629
Re: Clear button pressed (and other GUI events) queue
« Reply #6 on: November 13, 2017, 07:09:48 pm »
Yup, because of messaging (enable / disable are messages). Better to eat the message queue: Application.processmessages just after disabled. Not ideal.
"Logically, no number of positive outcomes at the level of experimental testing can confirm a scientific theory, but a single counterexample is logically decisive."

engkin

  • Hero Member
  • *****
  • Posts: 1721
Re: Clear button pressed (and other GUI events) queue
« Reply #7 on: November 13, 2017, 07:22:03 pm »
As Thaddy had pointed out earlier:
check your design...

The main thread is meant to handle GUI activities. Why would you assign an activity "that takes some time to complete an action after a button press" to the GUI thread? This activity should be assigned to a thread.

howardpc

  • Hero Member
  • *****
  • Posts: 2396
Re: Clear button pressed (and other GUI events) queue
« Reply #8 on: November 13, 2017, 08:23:16 pm »
@GetMem
You're right of course.
An Application.ProcessMessages is needed after the .Enabled:=False

I enjoyed the idea of code "byting" back ... very Pascalish (as opposed to biting back)!

engkin's caution is also relevant.

GetMem

  • Hero Member
  • *****
  • Posts: 2415
Re: Clear button pressed (and other GUI events) queue
« Reply #9 on: November 13, 2017, 09:11:36 pm »
@engkin
Quote
The main thread is meant to handle GUI activities. Why would you assign an activity "that takes some time to complete an action after a button press" to the GUI thread? This activity should be assigned to a thread.
Unfortunately is not that simple. Sometimes opening a big report or connecting to a remote server can take 3-4 seconds, more then enough for a "clumsy" user to press the button five times. Even if you move a lengthy operation to a worker thread the question still remains, how you prevent the user to create more then one thread. In my opinion the OP's question is a valid one.

@howardpc
Quote
I enjoyed the idea of code "byting" back ... very Pascalish (as opposed to biting back)!
My limited English knowledge bites back.  :D Thank you for pointing me out the mistake.
« Last Edit: November 13, 2017, 09:16:49 pm by GetMem »

Mike.Cornflake

  • Hero Member
  • *****
  • Posts: 1178
Re: Clear button pressed (and other GUI events) queue
« Reply #10 on: November 13, 2017, 10:18:53 pm »
...In my opinion the OP's question is a valid one

Aye, this very issue caused me no end of pain for many years.  Intermittent issues reported by end users that I just couldn't track down (who double clicks a button??).  Once I stumbled onto what was happening, I slapped code very similar to your FIsBusy all over the place (though I used .Tag)
Lazarus Trunk/FPC Trunk on Linux & Windows [7, 8.1]
  Have you tried searching this forum or the wiki?:   http://wiki.lazarus.freepascal.org/Alternative_Main_Page
  BOOKS! (Free and otherwise): http://wiki.lazarus.freepascal.org/Pascal_and_Lazarus_Books_and_Magazines

josh

  • Sr. Member
  • ****
  • Posts: 432
Re: Clear button pressed (and other GUI events) queue
« Reply #11 on: November 13, 2017, 10:43:38 pm »
It maybe beneficial to change the button caption to say 'Busy...' or 'Please wait...'; while the button action in progress..

Code: [Select]
procedure TForm1.Button1Click(Sender: TObject);
var
  b: TButton absolute Sender;
  oldCursor: TCursor;
  oldCaption:AnsiString='';
begin
  if Sender is TButton then
  begin
    oldCursor:=Screen.Cursor;
    oldCaption:=b.caption;
    b.Caption:='Working';
    b.Enabled:=False;
    Screen.Cursor:=crHourGlass;
    Application.ProcessMessages;
    try
      Sleep(5000);
      Inc(FCounter);
      Form1.Caption := IntToStr(FCounter);
        // some lengthy operation
    finally
      b.Enabled:=True;
      b.caption:=oldCaption;
      Screen.Cursor:=oldCursor;
      Application.Processmessages;
    end;
  end;
end;
Development Installation Lazarus 1.3, FPC 2.7.1,Windows 7/8 32/64, OSX, *nix

Test Environment Lazarus & FPC Trunk on Windows and OSX (Cocoa Mainly on OSX). Testing also Crosscompile windows to OSX.. 
Any posts made from 2015 will be based on Lazarus Trunk.

josh

  • Sr. Member
  • ****
  • Posts: 432
Re: Clear button pressed (and other GUI events) queue
« Reply #12 on: November 14, 2017, 12:02:12 am »
if user was wanting to enable/disable  a single control/ panel or form then I thought this little routine may help, I have not fully tested but so far so good.
Code: [Select]
procedure tForm1.Controls_Enabler(Control: TControl;aValue:Boolean);
var i: integer;
    WinControl: TWinControl;

begin
  with Control do
  begin
    Control.enabled:=aValue;
    if Control is TWinControl then
    begin
      WinControl := TWinControl(Control);
      If WinControl is TPanel Then
      begin
        for i := 0 to WinControl.ControlCount - 1 do Controls_Enabler(WinControl.Controls[i],AValue);
      end;
    end;
  end;
  application.ProcessMessages;
end;                   

Also a zip of test application showing its use.

usage Controls_Enabler(control,*required state*);
if control is a panel it goes through controls on panel to do set all controls to required state; same for a form.

Caution if used on a form all controls are effected ie, timers etc, so some tweeking for user case would be needed; Can't reaaly see use on form basis; but tested ok.

Maybe overkill



« Last Edit: November 14, 2017, 12:05:53 am by josh »
Development Installation Lazarus 1.3, FPC 2.7.1,Windows 7/8 32/64, OSX, *nix

Test Environment Lazarus & FPC Trunk on Windows and OSX (Cocoa Mainly on OSX). Testing also Crosscompile windows to OSX.. 
Any posts made from 2015 will be based on Lazarus Trunk.

engkin

  • Hero Member
  • *****
  • Posts: 1721
Re: Clear button pressed (and other GUI events) queue
« Reply #13 on: November 14, 2017, 02:57:57 am »
@GetMem
Even if you move a lengthy operation to a worker thread the question still remains, how you prevent the user to create more then one thread. In my opinion the OP's question is a valid one.

I really hate disabled controls, but here you go:
Code: Pascal  [Select]
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. var
  3.   Btn: TButton absolute Sender;
  4. begin
  5.   Btn.Enabled := False;
  6.  
  7.   Sleep(1000);
  8.   inc(FCounter);
  9.   Caption := IntToStr(FCounter);
  10.  
  11.   Application.QueueAsyncCall(@EnableBtn, PtrInt(Btn));
  12. end;
  13.  
  14. procedure TForm1.EnableBtn(Data: PtrInt);
  15. var
  16.   Btn: TButton absolute Data;
  17. begin
  18.   Btn.Enabled := True;
  19. end;

While when using threads where only one is allowed:
Code: Pascal  [Select]
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. begin
  3.   if Assigned(LT) then
  4.     exit;
  5.  
  6.   LT := TLengthyThread.Create...
« Last Edit: November 14, 2017, 02:59:40 am by engkin »

jamie

  • Full Member
  • ***
  • Posts: 154
Re: Clear button pressed (and other GUI events) queue
« Reply #14 on: November 19, 2017, 03:56:54 am »
I do things like this..
//Global space//
Var
 ExternalStopFlag :Boolean;

Procedure LongCode;
Const Busy :Boolean = false;
begin
 If Busy Then exit else Busy := true;
 
 While (Not ExternalSTopFlag do
  Begin
    Application.ProcessMessages;
    /// Do the Long code here.
   /// When Done with long Loop: ExternalStopFlag := true;
  End;
ExternalstopFlag := False;
Busy := False;
End;

Just an idea ...  :P

 

Recent

Get Lazarus at SourceForge.net. Fast, secure and Free Open Source software downloads Open Hub project report for Lazarus