Lazarus

Programming => General => Topic started by: Paolo on June 25, 2022, 11:43:13 am

Title: Resize
Post by: Paolo on June 25, 2022, 11:43:13 am
Hello,

probably I am confused how resize works !
here what I see:
In one application of mine I have a lot of graphical elements (3D, chart, XY-plot, etc... all derived by TGrpahicControl) placed on different tabs of a TPageControl.
I noticed, but this seems not happening in the delphi version, that there is a large time-lag in updating the tab content when switching among them (well visible).
After some investigation I have understood that the "resize" is fired for all the graphical elements placed on the application (even regardeless the tab is visible or not and nothing is really "resized"), this causes a lot of repaint of them becaues they have their "resize" procedure implemented that fire the repaint.
Trying to isolate the problem I prepare the code here attached (Self standing compilable), I see the "TXY component" Resize is called not only if the main form is resized bue even if just click on the button (top left on the toolbar) that show the "resize" counter continuosly growing !
I was expecting for TXYcomponent "resize" was fired just if the component change Size, but it seems not the case.
Wath did I miss ?
This justify in LAZ+FPC the time-lag, but I do not understand the meaning of resize

when I run the attache code at first click on the button I see alerdy 5 call to resize ! then for any further click the counter increases by 1

win 10 pro, laz+FPC 2.2.0+3.2.2

Code: Pascal  [Select][+][-]
  1.   TXY = class(TGraphicControl)
  2.   public
  3.     procedure Resize; override;
  4.     procedure Paint; override;
  5.   end;
  6.  
  7.   TForm1 = class(TForm)
  8.     Edit3: TEdit;
  9.     ToolBar21: TToolBar;
  10.     ToolButton4: TToolButton;
  11.     procedure FormCreate(Sender: TObject);
  12.     procedure ToolButton4Click(Sender: TObject);
  13.   private
  14.  
  15.   public
  16.     pippo : TXY;
  17.   end;
  18.  
  19. var
  20.   Form1: TForm1;
  21.   drawcount : integer;
  22.  
  23. implementation
  24.  
  25. {$R *.lfm}
  26.  
  27. { TXY }
  28.  
  29. procedure TXY.Resize;
  30. begin
  31.   inherited Resize;
  32.   beep;  //for "feedback"
  33.   drawcount:=drawcount+1; //counter of resize call
  34. end;
  35.  
  36. procedure TXY.Paint;
  37. begin
  38.   inherited Paint;
  39.   canvas.Brush.Color:=clBlue;
  40.   Canvas.Rectangle(0, 0, width-1, height-1);
  41. end;
  42.  
  43. { TForm1 }
  44.  
  45. procedure TForm1.ToolButton4Click(Sender: TObject);
  46. begin
  47.   edit3.text:=drawcount.tostring;
  48. end;
  49.  
  50. procedure TForm1.FormCreate(Sender: TObject);
  51. begin
  52.   pippo:=TXY.Create(Self);
  53.   Pippo.Parent:=Self;
  54.   pippo.Top:=1;
  55.   pippo.Left:=1;
  56.   pippo.Width:=100;
  57.   pippo.height:=100;
  58.   pippo.Visible:=true;
  59. end;
  60.  
  61. initialization
  62.   drawcount:=0;  //it should be not necessary
  63. end.
  64.  
Title: Re: Resize
Post by: Paolo on June 25, 2022, 12:36:34 pm
just tested my application on
laz-fpc 2.0.12+3.2.0 it is faster, even if coming back on the tab with graphical elements it tooks a lot to be repainted even if it shouldn't repainted at all (nothing is changed), still resize is fired a lot of time!
Title: Re: Resize
Post by: wp on June 25, 2022, 12:58:21 pm
Putting a breakpoint in your Resize method and calling the stack trace leads me to TWinControl.RealSetText which contains an unconditional call to AdjustSize. In my understanding AdjustSize performs the AutoSize action. Of course, your Edit displaying the counter value has no AutoSize, and I wonder whether there should not be an "if AutoSize then" in front of the AdjustSize. Doing this, in fact, silences the counter.

I am not sure however, whether this is the correct solution since it might have many side-effects.

Since your application seems to be rather complex, I'd ask you to open file "wincontrol.inc" (in (lazarus)/lcl/include), find this procedure and replace the AdjustSize as follows
Code: Pascal  [Select][+][-]
  1. procedure TWinControl.RealSetText(const AValue: TCaption);
  2. ...
  3.     if AutoSize then  // <--- added
  4.       AdjustSize;  
  5. ...
Rebuild the IDE ("Tools" > "Build Lazarus with profile..."). Then test and check whether it solves the issue (it should) and - more importantly - has any sideeffects on toolbars, ChildSizing, anchoring, align etc.
Title: Re: Resize
Post by: Paolo on June 25, 2022, 01:52:13 pm
thanks wp, I'll try as soon as possible your suggestion.
But what is the link between "RealSetText" (of which control in my code ?) and my Resize of TXY class in my code ?
I just perss the toolbutton and I see fired the TXXY.Resize ???

thanks again. (later I'll report if the patch you suggest it is ok or not)
Title: Re: Resize
Post by: wp on June 25, 2022, 02:10:52 pm
The issue is not in the click, but in the change of the Edit.Text. When you set the Edit.Text to the new value of the resize counter RealSetText is called internally, and this calls AdjustSize which triggers a call to Resize.

As a test, activate a text console (in Windows uncheck "Win32 gui application" in the project options ("Config and Target"), and replace the "edit3.Text := drawcount.toString" by a "WriteLn(drawcount)". You will see that the displayed number in the console does NOT change after clicking.
Title: Re: Resize
Post by: Paolo on June 25, 2022, 06:37:08 pm
recompiled lazarus, ma no effects on the code, I did:

locate "wincontrol.inc" (in (lazarus)/lcl/include)
open with text editor,
applied the correction you suggest
Rebuild the IDE ("Tools" > "Build Lazarus with profile : Normal")

but it seems nothing is changed !
Title: Re: Resize
Post by: Paolo on June 25, 2022, 07:02:56 pm
@wp,

Quote
your Edit displaying the counter value has no AutoSize

edit has autosize !

I make Edit3.autosize:=false; and after clicking the button the counter does not inrement.

but in the attached code where I put a TPAgecontrol as per my application I still see the problem
Title: Re: Resize
Post by: wp on June 25, 2022, 07:54:41 pm
@wp,

Quote
your Edit displaying the counter value has no AutoSize

edit has autosize !
Sorry, I got confused because I did also other tests. Without the modification in the RealGetSize method, the counter incremented even when the form caption was used to display the value rather than the edit.

But that's not the problem...

I did some more tests: Added a TPanel (or a TPaintbox for a TCustomControl) and put your ResizeCounter into the OnResize event. Notice that here the counter does NOT increment! Having this in mind, I used the debugger to step into the inherited Resize instruction of your TXY component; it leads immediately to the inherited TControl.Resize:
Code: Pascal  [Select][+][-]
  1. procedure TControl.Resize;
  2. begin
  3.   if ([csLoading,csDestroying]*ComponentState<>[]) then exit;
  4.   if AutoSizeDelayed then exit;                    // <-------------- Here the procedure is exited.
  5.  
  6.   if (FLastResizeWidth<>Width) or (FLastResizeHeight<>Height)
  7.   or (FLastResizeClientWidth<>ClientWidth)
  8.   or (FLastResizeClientHeight<>ClientHeight) then begin
  9.     {if CompareText('SubPanel',Name)=0 then begin
  10.       DebugLn(['[TControl.Resize] ',Name,':',ClassName,
  11.       ' Last=',FLastResizeWidth,',',FLastResizeHeight,
  12.       ' LastClient=',FLastResizeClientWidth,',',FLastResizeClientHeight,
  13.       ' New=',Width,',',Height,
  14.       ' NewClient=',ClientWidth,',',ClientHeight]);
  15.       DumpStack;
  16.     end;}
  17.     FLastResizeWidth:=Width;
  18.     FLastResizeHeight:=Height;
  19.     FLastResizeClientWidth:=ClientWidth;
  20.     FLastResizeClientHeight:=ClientHeight;
  21.     DoOnResize;
  22.   end;
  23. end;

Stepping through the code line by line, it can be seen that the method is already exited in the 2nd line, "if AutoSizeDelayed then exit". So, the bulk of the method is not executed, it cannot be the cause of your performance issues.

Of course, if your own components implement their own Resize methods this code will be executed, and depending on what it is doing it can cause some noticeable delay. To prevent this you should do the same as TControl.Resize: Put an "if AutoSizedelayed then exit" before your code (AutoSizeDelayed is a public method of TControl and thus accessible to all controls):
Code: Pascal  [Select][+][-]
  1. procedure TXY.Resize;
  2. begin
  3.   inherited;
  4.   if AutoSizeDelayed then exit;
  5.   // here should be your code, e.g. drawcount := drawcount+1;
  6. end;
Title: Re: Resize
Post by: Paolo on June 25, 2022, 10:33:10 pm
thank wp,

tommorow I'll check your suggestion.

have I to remove the previously added line in wincontrol.inc

Code: Pascal  [Select][+][-]
  1. procedure TWinControl.RealSetText(const AValue: TCaption);
  2. ...
  3.     if AutoSize then  // <--- Have I to remove this line ?
  4.       AdjustSize;  
  5. ...
Title: Re: Resize
Post by: Martin_fr on June 25, 2022, 10:34:21 pm
Are we sure that
Code: Text  [Select][+][-]
  1. TWinControl.RealSetText
is never called for anything that changes it's size, regardless of the "AutoSize" property?

E.g. something like the text on a tab of a pagecontrol? Ok, that would have to lead to wrapping or similar, before it affects other components. And then it might trigger resized via other means.


Anyway, another case.

Put a (or several) label (label.autosize := false)
on a panel (panel.autosize = true)

Enable ChildSizing on the panel
LeftToRight
2 per line
no changes to Enlarge../Shrink...

Changing the label.caption will change the size of the label and panel.
Yes, weird... Should it? No idea... But it does. (Laz 2.3)



AutoSizeDelayed
Afaik that simply means that some outer caller is holding it back, but that when the execution returns to the outer caller it will be run.






Quote
I did some more tests: Added a TPanel (or a TPaintbox for a TCustomControl) and put your ResizeCounter into the OnResize event. Notice that here the counter does NOT increment

Yet, that does not mean that the AutoSize code doesn't run.
In
Code: Pascal  [Select][+][-]
  1. procedure TControl.Resize;
the OnResize event is only called, if the computation of the AutoSize code actually made changes.

Yet of course, you are right, such checks should be done by any inherited code too.
Especially if that inherited code will cause repainting of the control.

Code: Pascal  [Select][+][-]
  1. procedure TControl.Resize;
  2. begin
  3.   if ([csLoading,csDestroying]*ComponentState<>[]) then exit;
  4.   if AutoSizeDelayed then exit;
  5.  
  6.   if (FLastResizeWidth<>Width) or (FLastResizeHeight<>Height)
  7.   or (FLastResizeClientWidth<>ClientWidth)
  8.   or (FLastResizeClientHeight<>ClientHeight) then begin
  9. ..........
  10.     DoOnResize;
  11.   end;
  12. end;
  13.  
Title: Re: Resize
Post by: Paolo on June 25, 2022, 10:49:31 pm
added in the attached code the check on AutosizeDelayed

nothing chenge (click on tab1 and then tab2 and viceversa several times and the resize is fire everytime.

Anyway why "Resize" is fired if no resizing of TXY instance is happening ?

PS: for my issue I see 3 situation (with same code)

1- delphi 10.2, no delay among tbasheet (so very likle no resize is fired)
2- laz+FPC 2.0.12+3.2.0  I see the delay in repainting only moving from "empty" tab to the tab with heavy graphical components (because the "resize" is fired !)
3- laz+FPC 2.2.0+3.2.2 whatever is the tab all the repaint has significant delay (you can sse the content of frirst tab, then some component of nw tab draw on the old tab, finallly the final tab with its compnent, a sort of trasparency effect 1-2 seconds).

the code I have in my application is as per attached reduced program, where obviusly there are not heavy drawing involved for sake semplicity.

Title: Re: Resize
Post by: wp on June 25, 2022, 11:35:10 pm
The attached demo contains a pagecontrol with three tabs and 50 labels and 50 edits on page 1 and 2 (each) and 40 labels and 40 comboboxes on page 3. Switching between tabs occurs instantly. This demontrates that the LCL controls do not show this issue.

As I wrote I suspect the slow switching performance is due to your own components. The Resize method is an internal method of a component, and it is the responsibility of the component writer that it is behaving properly.

Looking again at the Resize code, I see other checks which must be passed - e.g. "if FLastResizeWidth <> Width" etc - this means the inner part of Resize is executed only when all these checks are passed. And what is done then? Resize simply calls another method: DoOnResize. This method is executed only when the size has changed. Therefore you should move all your resizing code in your components from Resize to DoOnResize, and leave Resize alone. DoOnResize is virtual, and when you override it you will replace the inherited code. Do call inherited, though, because it fires the event OnResize.
Code: Pascal  [Select][+][-]
  1. procedure TControl.DoOnResize;
  2. begin
  3.   if Assigned(FOnResize) then FOnResize(Self);
  4.   DoCallNotifyHandler(chtOnResize);
  5. end;
Title: Re: Resize
Post by: Paolo on June 26, 2022, 01:13:58 am
Quote
This demontrates that the LCL controls do not show this issue.
As I wrote I suspect the slow switching performance is due to your own components

sure, no doubt.

I'll do some test.

Title: Re: Resize
Post by: Paolo on June 26, 2022, 01:18:56 pm
@wp I did some change, now it works "quickly" after moved the logic in DoOnResize.

It avoids the continous hard-recomputing in the refresh (even if not real resizing is happening, this part for me it is still obscure).

Anyway now I need help in case of real resizing, since in this case I need hard-recomputing, I am looking for something that works "after" resizing is complete (eg when the user resize the form with mouse I would like to do a single Hard-refresh at the end of user interaction).

thank you in advance.
Title: Re: Resize
Post by: wp on June 26, 2022, 01:37:35 pm
Anyway now I need help in case of real resizing, since in this case I need hard-recomputing, I am looking for something that works "after" resizing is complete (eg when the user resize the form with mouse I would like to do a single Hard-refresh at the end of user interaction).
Sorry, I don't know in detail what you are talking about. It could also be that you are trying to reinvent the wheel because the LCL can handle a lot of sizing issues amazingly well (anchoring, auto-sizing, ... - that's why the sizing code is so complex). Please read https://wiki.freepascal.org/Autosize_/_Layout#Custom_Controls.
Title: Re: Resize
Post by: Martin_fr on June 26, 2022, 01:46:06 pm
Anyway now I need help in case of real resizing, since in this case I need hard-recomputing, I am looking for something that works "after" resizing is complete (eg when the user resize the form with mouse I would like to do a single Hard-refresh at the end of user interaction).
It's impossible to know. Example: The user grabs the edge of the form, and slowly resizes pixel by pixel. When is the resize finished? When the user takes more than 1 second, between two increases? Or half a second? or 2 seconds?...

You could go for on mouse up. I am not sure you can reliable catch that on all OS.
And then, resize could (on some OS) end by the escape key. Or "alt-tab" switching to another window. Or....


Quote
It avoids the continous hard-recomputing in the refresh (even if not real resizing is happening, this part for me it is still obscure)).

Earlier you said "repaint". Not sure of your implementation. But you should use "Invalidate". Though that is only part of what you may need.

Invalidate, will schedule a paint event. However, if there are more resize events, and there is no time to paint, then resize events should afaik be handled first. And one combined paint at the end.

However it is imperative, that paint events are fast. If you put big computation in there, the effects can be visible bad.

So you should split
- the work to compute what should be painted (which can potentially take a long time)
- painting the result (i.e. copy a bitmap, or output text at the pre-determined position, or....)

If you do that split, the the question is, where to put the computing so that no blocking happens.

Threads is one possibility.
- But needs careful syncing / and caching, so you have "data in progress" in the thread and "previous-finished-data" to paint.
- Need checking, if the size further changed => so they abort any no longer needed work.

Or you can
- compute in QueueAsync or OnIdle
- only compute small part in each call, and then schedule the next QueueAsync or OnIdle.
  (you need state keeping, what has been done, and where to continue)
- if the size further changed, discard the partial work, and start over
  (or keep the partial work, if it can be used for some partial updated paint)
Title: Re: Resize
Post by: jamie on June 26, 2022, 03:22:07 pm
@wp I did some change, now it works "quickly" after moved the logic in DoOnResize.

It avoids the continous hard-recomputing in the refresh (even if not real resizing is happening, this part for me it is still obscure).

Anyway now I need help in case of real resizing, since in this case I need hard-recomputing, I am looking for something that works "after" resizing is complete (eg when the user resize the form with mouse I would like to do a single Hard-refresh at the end of user interaction).

thank you in advance.

 There was a correction made to the OnReSize event in the trunk and I thought it made it in recent releases? The OnResize behaves like it should now, that is, when the user releases the mouse you then get the response.
   Before that, when I reported an issue of another, that Resize event would get called always while dragging your form around and most likely due to the reason Delphi does it because of a hack they did years ago back in the old windows 3.x days where there wasn't any proper messaging taking place for users dragging along.

  I believe it was in the years of 16 bit to 32 bit apps.

 So the WM_SIZE event would come in when the user got done dragging their window frame around at which point you could respond to that.

 Today we have extra messages like WM_WindowPosChanging and ....Changed. which can give you that smooth response. Not only that, you can adjust the window size on the fly before it gets applied. This makes for a very smooth translation of sizing with no drawing artifacts.

 You also have messages like WM_MOVING and WM_SIZING which for some reason LCL does not allow through the class but is doable via a nice little unit I have to make it easy to process that message, too.

 So you could trap that message for the time being and detect if the user still has the mouse down.
Title: Re: Resize
Post by: Paolo on June 26, 2022, 07:35:54 pm
First of all thank you all @wp, @Martin and @Jamie, for the suggestions.

I admit I was a little misliding in my components description, I wrote the code some years ago and I forgot the details  :-[

I'll came back here after re-assess the situation, here bleow just some comments.

Quote
So you should split
- the work to compute what should be painted (which can potentially take a long time)
- painting the result (i.e. copy a bitmap, or output text at the pre-determined position, or....)

I have already something similar. I wrote on a hidden bitmap and then display it. My "paint" logic (in the override paint procedure) is based on an internal flag that says if the bitmap has to be fully redraw (eg scales are changed, the plot itself is changed,...) or just show the bitmap as is.

So once the graphical elements are plotted the flag is set to "no-full-repaint". By user interaction there could be need of full-repaint but it is easily detected (ie change in data to display, change in scale,..) and a full-repaint (if needed) can be invoked by code.

But in case of "resize" (I initially wrote an override resize procedure) the flag is set set to full-repaint since the the size is changing!
this means that longer time is required (ie the bitmap is fully repainted due to "size" changes -> this was what I wrongly call "hard-recomputing", data are already there they need to transformed in graphical elements). And this causes slow repaint.

But as said before let me double check and better testing before adding other comments.


TinyPortal © 2005-2018