Jump to content
@rturas

Disable all controls on panel

Recommended Posts

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
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.

 

  • Thanks 1

Share this post


Link to post
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
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

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
1 hour ago, Angus Robertson said:

I use this function to disable and reanable all controls in a container

  1. That doesn't solve the problem. Read the prior replies.
  2. Why test for csAcceptsControls? It's a design-time option.

Share this post


Link to post
11 minutes ago, Anders Melander said:
  1. 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 by Cristian Peța

Share this post


Link to post

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 by PeaShooter_OMO

Share this post


Link to post
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;

 

  • Like 1
  • Thanks 1

Share this post


Link to post
Just now, PeaShooter_OMO said:

as soon as btnWriteASCII_ShrtClick returns the queued messages will be processed.

Exactly

Share this post


Link to post

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

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 by PeaShooter_OMO

Share this post


Link to post

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
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
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 by PeaShooter_OMO

Share this post


Link to post

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
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

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 by Cristian Peța

Share this post


Link to post
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

I can't believe I just made a case for Application.ProcessMessages after 25 years of trying to get people to stop using it 🤦‍♂️

  • Haha 2

Share this post


Link to post
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

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

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. 

  • Like 1

Share this post


Link to post
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
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 by PeaShooter_OMO
  • Like 1
  • Thanks 1

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×