@rturas 0 Posted February 12 Hello, I want to disable all the controls that are on the specific panel (buttons, edits, etc.) while i read data from server. For that reason i use: TPanel.Enabled := False And reenable it after reading is finished. If i click on any disabled button while panel is disabled, its event is fired after the panel is reanabled. Is it normal behavior, or am i doing something wrong? My code looks like that: procedure TfrmPanel.FormShow(Sender: TObject); begin fTCPClient := TIdTCPClient.Create; fTCPClient.Host := IP; fTCPClient.Port := Port; try fTCPClient.Connect; except on E: Exception do //Handle exception end; end; procedure TfrmPanel.btnWriteASCII_ShrtClick(Sender: TObject); var myStr: string; i: Integer; begin pnl3.Enabled := False; myStr := ''; try if fTCPClient.Connected then begin fTCPClient.IOHandler.WriteLn('set OutputType=ASCII_SHORT'); if fTCPClient.IOHandler.InputBufferIsEmpty then begin for i := 0 to 1 do myStr := myStr + fTCPClient.IOHandler.WaitFor(Char($0A), True, False, IndyTextEncoding_ASCII, 5000); end; end; finally begin mmo1.Lines.Add(myStr); pnl3.Enabled := True; end; end; end; Share this post Link to post
Anders Melander 1892 Posted February 12 1 hour ago, @rturas said: If i click on any disabled button while panel is disabled, its event is fired after the panel is reanabled. Is it normal behavior, or am i doing something wrong? It's because you aren't processing any windows messages while the panel is disabled, so when you enable it again the mouse click message is still waiting in the message queue. 1 Share this post Link to post
@rturas 0 Posted February 12 Just now, Anders Melander said: It's because you aren't processing any windows messages while the panel is disabled, so when you enable it again the mouse click message is still waiting in the message queue. I see, thank you! Share this post Link to post
Anders Melander 1892 Posted February 12 Just now, Anders Melander said: you aren't processing any windows messages while the panel is disabled, ...not because the panel is disabled but because the stuff in between doesn't process messages - in case you wondered. Share this post Link to post
Angus Robertson 606 Posted February 12 I use this function to disable and reanable all controls in a container: procedure EnableOrDisableChildren (Container: TWinControl; Enabled: Boolean) ; var index: integer; aControl: TControl; isContainer: boolean; begin for index := 0 to -1 + Container.ControlCount do begin aControl := Container.Controls [index] ; isContainer := (csAcceptsControls in aControl.ControlStyle) ; if NOT isContainer then aControl.Enabled := Enabled; //recursive for child controls if (isContainer) AND (aControl is TWinControl) then begin EnableOrDisableChildren (TWinControl (aControl), Enabled) ; end; end; end; Angus Share this post Link to post
Anders Melander 1892 Posted February 12 1 hour ago, Angus Robertson said: I use this function to disable and reanable all controls in a container That doesn't solve the problem. Read the prior replies. Why test for csAcceptsControls? It's a design-time option. Share this post Link to post
Cristian Peța 108 Posted February 12 (edited) 11 minutes ago, Anders Melander said: That doesn't solve the problem. Read the prior replies.. The OP is disabling only the panel and that will not disable the button. Angus solution will solve the problem because the function will disable the button that will not pump the windows messages. Edited February 12 by Cristian Peța Share this post Link to post
PeaShooter_OMO 18 Posted February 12 (edited) The OP is doing fTCPClient.IOHandler.WaitFor which will cause the execution of the main thread to wait on that call which will cause windows messages to queue and as soon as btnWriteASCII_ShrtClick returns the queued messages will be processed. To Disable all controls inside the panel is required if you want the visual aspect to correlate to the state Edited February 12 by PeaShooter_OMO Share this post Link to post
Anders Melander 1892 Posted February 12 3 minutes ago, Cristian Peța said: The OP is disabling only the panel and that will not disable the button. Yes it will. 3 minutes ago, Cristian Peța said: Angus solution will solve the problem because the function will disable the button that will not pump the windows messages. The reason it will not pump messages is that it's not being given an opportunity to do so. This is the equivalent: Button.Enabled := False; Sleep(1000); Button.Enabled := True; 1 1 Share this post Link to post
Anders Melander 1892 Posted February 12 Just now, PeaShooter_OMO said: as soon as btnWriteASCII_ShrtClick returns the queued messages will be processed. Exactly Share this post Link to post
@rturas 0 Posted February 12 Based on the answer of the Anders Melander i have added Application.ProcessMessages; right before pnl3.Enabled := True; Not sure if it is a good idea. Share this post Link to post
PeaShooter_OMO 18 Posted February 12 (edited) No, try and stay away from Application.ProcessMessages. Its a "poor-programmer's" response to an issue which can be fixed in a better way. In anyway, the Waitfor will negate Application.ProcessMessages where you put it. Common-rule-of-thumb is to put lengthy operations inside a thread to not adversely affect the main thread. The long Timeout on the WaitFor call makes it a very lengthy operation. (Changing the timeout length is not the solution either). Use a thread. Edited February 12 by PeaShooter_OMO Share this post Link to post
Anders Melander 1892 Posted February 12 I agree wrt avoiding Application.ProcessMessages but... If the form is modal then you can disable the form instead of the panel and then it should be (relatively) safe to use Application.ProcessMessages. Place it right before you enable the form again. Share this post Link to post
Anders Melander 1892 Posted February 12 3 minutes ago, PeaShooter_OMO said: In anyway, the Waitfor will negate Application.ProcessMessages where you put it. I can't see why. Please explain. Share this post Link to post
PeaShooter_OMO 18 Posted February 12 (edited) Just now, Anders Melander said: I can't see why. Please explain. If he puts the Application.ProcessMessages right before pnl3.Enabled := True; then it basically is the same as leaving it out because the two WaitFor calls have already taken place which means the lengthy operation is finished and btnWriteASCII_ShrtClick is already on its way out. As such the application will continue processing in anyway immediately after. Edited February 12 by PeaShooter_OMO Share this post Link to post
Anders Melander 1892 Posted February 12 The purpose of Application.ProcessMessages would be to have any pending messages in the queue processed. If there is a mouse-click message in the queue then it will be processed but will do nothing because the control being clicked is disabled. Without Application.ProcessMessages then the mouse-click message will still be in the queue after the control has been enabled and will therefore be handled as a click when the queue is pumped. Share this post Link to post
@rturas 0 Posted February 12 4 minutes ago, PeaShooter_OMO said: No, try and stay away from Application.ProcessMessages. Its a "poor-programmer's" response to an issue which can be fixed in a better way. In anyway, the Waitfor will negate Application.ProcessMessages where you put it. Well, in my case it did work, WaitFor did not neglet Application.ProcessMessages. procedure TfrmPanel.btnWriteASCII_ShrtClick(Sender: TObject); var myStr: string; i: Integer; begin pnl3.Enabled := False; myStr := ''; try if fTCPClient.Connected then begin fTCPClient.IOHandler.WriteLn('set OutputType=ASCII_SHORT'); if fTCPClient.IOHandler.InputBufferIsEmpty then begin for i := 0 to 1 do myStr := myStr + fTCPClient.IOHandler.WaitFor(Char($0A), True, False, IndyTextEncoding_ASCII, 5000); end; end; finally begin mmo1.Lines.Add(myStr); Application. ProcessMessages; pnl3.Enabled := True; end; end; end; Anyway, i do not state, that it is a good solution. 9 minutes ago, PeaShooter_OMO said: Common-rule-of-thumb is to put lengthy operations inside a thread to not adversely affect the main thread. The long Timeout on the WaitFor call makes it a very lengthy operation. (Changing the timeout length is not the solution either). I use 5000 ms TimeOut for WaitFor, because i do not know exactly, when the second answer from the server comes (it should be within couple of seconds). First answer is only a feedback to my request, second one - confirmation that request is proccessed with/without errors. Share this post Link to post
Cristian Peța 108 Posted February 12 (edited) The panel should be enabled after the message is received and not in TfrmPanel.btnWriteASCII_ShrtClick() And disabling the panel should be done right before fTCPClient.IOHandler.WaitFor() when you know that WaitFor() is executed. Edited February 12 by Cristian Peța Share this post Link to post
PeaShooter_OMO 18 Posted February 12 1 minute ago, Anders Melander said: The purpose of Application.ProcessMessages would be to have any pending messages in the queue processed. If there is a mouse-click message in the queue then it will be processed but will do nothing because the control being clicked is disabled. Without Application.ProcessMessages then the mouse-click message will still be in the queue after the control has been enabled and will therefore be handled as a click when the queue is pumped. You have a good point. It basically disposes of the queued messages related to the controls which at that stage would be disabled. Share this post Link to post
Anders Melander 1892 Posted February 12 I can't believe I just made a case for Application.ProcessMessages after 25 years of trying to get people to stop using it 🤦♂️ 2 Share this post Link to post
PeaShooter_OMO 18 Posted February 12 Just now, Anders Melander said: I can't believe I just made a case for Application.ProcessMessages after 25 years of trying to get people to stop using it Indeed. The Application.ProcessMessages at his chosen position does resolve his issue with queued messages though but because of my "buddy-buddy" feelings I have for threads I would have put that operation in a thread aspecially for the instances where he does not get a quick response from the TCP peer. Share this post Link to post
Anders Melander 1892 Posted February 12 Yeah well, threads do come with their own challenges and I have a feeling that the OP "just want it work" and get on with business. I agree that blocking the main thread for 5 seconds is too long - and I'm pretty sure Windows isn't too happy about it either. Quote If a top-level window stops responding to messages for more than several seconds, the system considers the window to be not responding. In this case, the system hides the window and replaces it with a ghost window that has the same Z order, location, size, and visual attributes. Share this post Link to post
Remy Lebeau 1494 Posted February 12 Indy has a TIdAntiFreeze component that processes pending UI messages during blocking socket operations. This would allow the WaitFor() call to process messages while waiting for the data to arrive. Though, that wouldn't address the case where the button click occurs after the last WaitFor() call had already exited but the panel hasn't been enabled yet. 1 Share this post Link to post
@rturas 0 Posted February 13 21 hours ago, PeaShooter_OMO said: No, try and stay away from Application.ProcessMessages. Its a "poor-programmer's" response to an issue which can be fixed in a better way. In anyway, the Waitfor will negate Application.ProcessMessages where you put it. Common-rule-of-thumb is to put lengthy operations inside a thread to not adversely affect the main thread. The long Timeout on the WaitFor call makes it a very lengthy operation. (Changing the timeout length is not the solution either). Use a thread. As my mother language is not english i might misunderstood your post. Please correct me if i am wrong: if i will put WaitFor into the thread, then i will not be in need to use Application.ProcessMessage? Share this post Link to post
PeaShooter_OMO 18 Posted February 13 (edited) 30 minutes ago, @rturas said: if i will put WaitFor into the thread, then i will not be in need to use Application.ProcessMessage? That is correct. In your current situation Application.ProcessMessages serves one main purpose; To fix your queued windows messages issue. Rightfully so @Anders Melander mentioned that it is a way to fix that because it disposes of the queued windows messages that were destined for the disabled controls. But your code has the ability to produce another "artifact" and that is an unresponsive GUI while the lengthy operation is taking place (when the result of WaitFor takes a a little longer to arrive). Application.ProcessMessages where you placed it will not fix that because the possible occurance of a longer-than-usual WaitFor happens before Application.ProcessMessages. Your GUI will be unresponsive wile the WaitFors take place. As @Remy Lebeau mentioned; you can use Indy's TIdAntiFreeze to fix the unresponsive GUI issue but it only works while the WaitFor is being processed. So you can place the following into a thread (obviously with modifications to handle thread-safety and other things as required) : if fTCPClient.Connected then begin fTCPClient.IOHandler.WriteLn('set OutputType=ASCII_SHORT'); if fTCPClient.IOHandler.InputBufferIsEmpty then begin for i := 0 to 1 do myStr := myStr + fTCPClient.IOHandler.WaitFor(Char($0A), True, False, IndyTextEncoding_ASCII, 5000); end; end; You have to remember that that thread will now leave your main thread and GUI free to continue and this will be evident in any Buttons, Menus, "interactions" being available for the user to click on unless you specifically disable them. You also have to have some result for the thread which means as soon as the WaitFors finish and you have your answer from the TCP then you should obviously react by passing the resulting String to the main thread, enable all that needs to be enabled again, continue with whatever you still wanted to do, etc. Edited February 13 by PeaShooter_OMO 1 1 Share this post Link to post