Clear button pressed (and other GUI events) queue lazarus / 62-General ---------------------------------------------------------------------- richardcgiroux · 2017-11-13 16:31 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. ---------------------------------------------------------------------- balazsszekely · 2017-11-13 16:39 Try this: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i  TForm1 = class(TForm)    Button1: TButton;    procedure Button1Click(Sender: TObject);  private    FIsBusy: Boolean;  public   end; //.... procedure TForm1.Button1Click(Sender: TObject);begin  if FIsBusy then    Exit;   FIsBusy := True;  try          //lengthy operation  finally    FIsBusy := False;  end;end;               You can also disable the button as a visual feedback. ---------------------------------------------------------------------- Thaddy · 2017-11-13 16:57 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. ---------------------------------------------------------------------- mse · 2017-11-13 17:10 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? ---------------------------------------------------------------------- howardpc · 2017-11-13 17:13 Putting thaddy's suggestion into code gives Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.Button1Click(Sender: TObject);var  b: TButton absolute Sender;  oldCursor: TCursor;begin  if Sender is TButton then begin    WriteLn('TForm1.Button1Click');    oldCursor:=Screen.Cursor;    b.Enabled:=False;    Screen.Cursor:=crHourGlass;    try      // some lengthy operation    finally      b.Enabled:=True;      Screen.Cursor:=oldCursor;    end;  end;end; ---------------------------------------------------------------------- balazsszekely · 2017-11-13 17:50 @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][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.Button1Click(Sender: TObject);var  b: TButton absolute Sender;  oldCursor: TCursor;begin  if Sender is TButton then begin    oldCursor:=Screen.Cursor;    b.Enabled:=False;    Screen.Cursor:=crHourGlass;    try      Sleep(5000);      Inc(FCounter);      Form1.Caption := IntToStr(FCounter);        // some lengthy operation    finally      b.Enabled:=True;      Screen.Cursor:=oldCursor;    end;  end;end; ---------------------------------------------------------------------- Thaddy · 2017-11-13 18:09 Yup, because of messaging (enable / disable are messages). Better to eat the message queue: Application.processmessages just after disabled. Not ideal. ---------------------------------------------------------------------- engkin · 2017-11-13 18:22 As Thaddy had pointed out earlier: Quote from: Thaddy on November 13, 2017, 05:57:06 pmcheck 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 · 2017-11-13 19:23 @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. ---------------------------------------------------------------------- balazsszekely · 2017-11-13 20:11 @engkin QuoteThe 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 QuoteI enjoyed the idea of code "byting" back ... very Pascalish (as opposed to biting back)! My limited English knowledge bites back.  Thank you for pointing me out the mistake. ---------------------------------------------------------------------- Mike.Cornflake · 2017-11-13 21:18 Quote from: GetMem on November 13, 2017, 09:11:36 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) ---------------------------------------------------------------------- Josh · 2017-11-13 21:43 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; ---------------------------------------------------------------------- Josh · 2017-11-13 23:02 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 ---------------------------------------------------------------------- engkin · 2017-11-14 01:57 @GetMem Quote from: GetMem on November 13, 2017, 09:11:36 pmEven 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][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.Button1Click(Sender: TObject);var  Btn: TButton absolute Sender;begin  Btn.Enabled := False;   Sleep(1000);  inc(FCounter);  Caption := IntToStr(FCounter);   Application.QueueAsyncCall(@EnableBtn, PtrInt(Btn));end; procedure TForm1.EnableBtn(Data: PtrInt);var  Btn: TButton absolute Data;begin  Btn.Enabled := True;end; While when using threads where only one is allowed: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.Button1Click(Sender: TObject);begin  if Assigned(LT) then    exit;   LT := TLengthyThread.Create... ---------------------------------------------------------------------- jamie · 2017-11-19 02:56 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 ...