Autosize / Layout

From Free Pascal wiki
Jump to navigationJump to search

Overview

The LCL can adapt the size and positions of controls automatically to adapt to fonts, themes and content. If you want to run your program on several platforms or your captions are translated to various languages then your controls need to adapt to that. The LCL allows both: first design a form quickly and allow to move the controls with the mouse around. And later you can change some properties to make the automatically adapt.

  • Fixed design: this is the default when putting a control in the designer. The position of the control is fixed relative to its parent. The position (Left,Top) of the control is under your control. You can move the control around with the mouse or resize it freely.
  • Aligned: Controls can fill up remaining space on the top, bottom, left, right or fill the remaining space.
  • Anchored: You can anchor control sides (left, top, right, bottom) to its parent or another control. Anchoring means: LCL will try to keep the distance.
  • Layout: Controls can be automatically aligned in rows and columns (e.g. TRadioGroup)
  • Custom via OnResize: you can align controls yourself by using the OnResize and OnChangeBounds events.
  • Custom controls: writing your own controls you can override nearly every LCL behavior you want.

Precedence rules

  1. Constraints
  2. Align
  3. Anchors
  4. ChildSizing.Layout
  5. AutoSize
  6. OnResize, OnChangeBounds - if you set bounds that conflict with above rules, you create an endless loop

Common Properties

And there are various properties to configure autosizing:

  • Left, Top, Width, Height
  • AutoSize: enable to let the LCL automatically resize the Width, Height of the control
  • Anchors: create dependencies, for example to anchor a TComboBox right of a Label.
  • Align
  • Constraints: set minimum and maximum for Width and Height
  • BorderSpacing: set the space between anchored controls
  • ChildSizing: set the layout and spacing of child controls

The internals of the algorithms are explained here: LCL AutoSizing.

Fixed design

This is the default setup. The Anchors property is set to [akLeft,akTop] which means the Left, Top are not changed by the LCL. If AutoSize is false the Width and Height are kept too. If AutoSize is true then the Width and Height are changed to fit the content. For example TLabel.AutoSize defaults to true and thus changing the Caption will resize the label. TButton.AutoSize defaults to false so changing the Caption does not resize the button. When you set Button.AutoSize to true the Button will shrink or enlarge every time the Caption or the font or the theme changes. Note that this change is not always done immediately. For example during FormCreate all autosizing is suspended. You can at any time change the Left, Top, Width, Height properties.

Anchor Sides

Controls have four sides: akLeft, akTop, akRight and akBottom. Each side can be anchored to the parent or the side of another sibling (a control with the same parent). Anchoring means keeping the distance. The default is Anchors=[akLeft,akTop]. Vertical anchors are independent of horizontal anchors. Some properties like Align and Parent.AutoSize have higher precedence and can change the behavior.

Anchored to Parent

There are four combinations of akLeft,akRight (akTop,akBottom):

  • akLeft, no akRight: the control's Left is fixed and not changed by the LCL. The right side is not anchored, so it follows the left side. That means the Width is kept too.
  • akLeft and akRight: the control's Left is fixed and not changed by the LCL. The right side is anchored to the Parent's right side. That means if the Parent is enlarged by 100pixel then the control's Width is enlarged by 100pixel too.
  • akRight, no akLeft: the control's left side is not anchored, so it will follow its right side. The right side is anchored. That means if the parent is enlarged by 100pixel then the control is moved to the right by 100pixel.
  • no akLeft and no akRight: neither sides are anchored. The position of the center of the control scales with the parent. For example if the control is in the middle of the parent and the parent is enlarged by 100 pixel, then the control is moved 50pixel to the right.

A GroupBox with a Button: Anchors1.png

With akLeft, no akRight: Anchors akLeft.png

With akLeft and akRight: Anchors akLeft akRight.png

With akRight, no akLeft: Anchors akRight no akLeft.png

Three buttons in a groupbox: Anchors no akLeft no akRight small.png

With no akLeft and no akRight their centers are scaled: Anchors no akLeft no akRight big.png

Anchored to sibling

You can anchor to neighbor controls. The following example shows:

  • you can anchor a label's left to the left of a button
  • anchor a label's top to the bottom of a button
  • anchor the center of a label to the center of a button

Anchorsides example1.png

Anchorside example2.png

For more details and how to setup anchors see: Anchor Sides.

Anchoreditor.png

Align

The Align property works pretty much like in Delphi and can be used to quickly fill an area. For example to let a TListBox fill the entire area of its Parent, set ListBox1.Align=alClient. The align values alTop, alBottom, alLeft and alRight will place controls without overlapping if possible. That means all controls with Align in alLeft,alTop,alBottom,alRight will not overlap if there is enough space.

The algorithm works as follows:

  • First all controls with alTop are put to the top of the client area. The algorithm will try to avoid overlapping and keep the height of the control, while expanding the width to maximum. AnchorSides of the left, top and right sides are ignored. The bottom AnchorSide works normal. Borderspacing and Parent.ChildSizing spaces are considered, so you can define space around each aligned control.
  • Then all controls with alBottom are put to the bottom of the client area. Otherwise it works analog to alTop.
  • Then all controls with alLeft are put to the left of the client area between the alTop and alBottom controls. Otherwise it works analog to alTop.
  • Then all controls with alRight are put to the right of the client area between the alTop and alBottom controls. Otherwise it works analog to alTop.
  • If there is a control with alClient it will fill the remaining client.
  • If there is more than one control with alClient they will be placed at the same position, overlapping each other. Use the Visibility property to define which one is shown.

Autosize align.png

Align and BorderSpacing

The space of BorderSpacing and of the parent's ChildSizing is applied to aligned controls. The memo below has Align=alClient.

Autosize align borderspacing.png

Align and Anchoring

The free side of an aligned control (e.g. the right side of a Align=alLeft) follows the anchoring rules. If the anchor is not set, then the control will keep its Width. If the anchor is set, then the Width will change.

Align and AutoSize

A control aligned with alLeft or alRight expands vertically, and will use its designed width. If AutoSize is set to true, then the Width will be the preferred width. The button below has Align=alRight and AutoSize=true.

Autosize align autosize.png

Align and parent AutoSize

A control aligned with alClient fills the remaining space. A parent control with AutoSize=true will enlarge/shrink to fit its childs. What happens if you combine that, that means put a button with Align=alClient into a groupbox with AutoSize=true?

Autosize nested1.png

The LCL will use the preferred size of the buttons and enlarge/shrink the groupbox accordingly:

Autosize nested2.png

alCustom

This Align value exists for custom AutoSize algorithm and is treated almost like alNone. The exception is: When the ChildSizing.Layout property is set, all Controls with alNone and Anchors akLeft,akTop are moved.

Layout

Rows, columns and lines

You can align child controls in rows and columns using the ChildSizing properties. For example:

  • ChildSizing.Layout=cclLeftToRightThenTopToBottom
  • ChildSizing.ControlsPerLine=3
  • AutoSize=false (the groupbox does not fit the childs)

Autosize layout lefttoright.png

The Layout property defaults to cclNone. If you set Layout to another value, every child, that has normal anchors, will be aligned. Normal anchors means:

  • Anchors=[akLeft,akRight]
  • AnchorSide[akLeft].Control=nil
  • AnchorSide[akTop].Control=nil
  • Align=alNone

The value cclLeftToRightThenTopToBottom will put the first child at top, left, the second to the right, and so forth. This is a line. The property ControlsPerLine defines when a new line is started. In the above example each line (row) has 3 controls. There are 12 controls, so there are 4 lines (rows) each with 3 controls (columns). If ControlsPerLine is 0 it means unlimited controls per line - there would be only one row with all childs.

You can see that the rows have different sizes, each row has the size of the biggest control and that the controls are resized to column width. There is no space between the rows. The space in the image comes from the used theme, not from the LCL.

Fixed space between rows and columns

You can add space between with these properties:

  • ChildSizing.VerticalSpacing - Space between rows
  • ChildSizing.HorizontalSpacing - Space between columns
  • ChildSizing.LeftRightSpacing - Space on the left and right of all columns
  • ChildSizing.TopBottomSpacing - Space above and below of all columns

The above example with ChildSizing.VerticalSpacing=6, ChildSizing.HorizontalSpacing=15, ChildSizing.LeftRightSpacing=30, ChildSizing.TopBottomSpacing=10, AutoSize=true

Autosize layout parentspacing.png

Additionally you can add individual space for each control with its BorderSpacing properties.

Enlarge

The above example resized the GroupBox to the needed space. If your GroupBox has a fixed size or if it is not freely resizable, for instance if the GroupBox should fill the whole width of the form, then the childs should enlarge. There are several modes. The default mode ChildSizing.EnlargeHorizontal=crsAnchorAligning is to not enlarge anything. The space on the right side will be unused.

  • crsAnchorAligning - do not use the extra space
  • crsScaleChilds - multiply the width/height with the same factor
  • crsHomogeneousChildResize - add to each width/height the same amount
  • crsHomogeneousSpaceResize - add to each space between the childs the same amount

crsScaleChilds

ChildSizing.EnlargeHorizontal=crsScaleChilds, ChildSizing.EnlargeVertical=crsScaleChilds, AutoSize=false

Autosize layout scalechilds.png

For example if the ClientWidth is twice as big as needed, then every child will be twice as big.

crsHomogeneousChildResize

ChildSizing.EnlargeHorizontal=crsHomogeneousChildResize, ChildSizing.EnlargeVertical=crsHomogeneousChildResize, AutoSize=false

Autosize layout homogeneouschildresize.png

For example if the ClientWidth is 30 pixel bigger than needed, then every child will be 10 pixel broader.

crsHomogeneousSpaceResize

ChildSizing.EnlargeHorizontal=crsHomogeneousSpaceResize, ChildSizing.EnlargeVertical=crsHomogeneousSpaceResize, AutoSize=false

Autosize layout homogeneousspaceresize.png

For example if the ClientWidth is 40 pixel bigger than needed, there will be 10 pixel space on the left, right and between each child.

Shrink

Shrinking works similar to enlarging. You can set different modes if there is not enough space for controls. ShrinkHorizontal, ShrinkVertical.

Individual cells

In the above examples all controls were resized the same, each filled the whole cell. A cell' is the space in a specific row and column. Normally a control fills the whole cell space. This can be changed with the properties BorderSpacing.CellAlignHorizontal and BorderSpacing.CellAlignVertical.

For example set the BorderSpacing.CellAlignHorizontal of the fifth button to caCenter you will get this:

Autosize layout cellhorizontal cacenter.png

There are four possible values for CellAlignHorizontal/CellAlignVertical:

  • caFill: the child control fills the whole width (height) of the cell
  • caCenter: the child control uses its preferred width (height) and will be centered in the cell
  • caLeftTop: the child control uses its preferred width (height) and will be leftaligned in the cell
  • caRightBottom: the child control uses its preferred width (height) and will be rightaligned in the cell

Autosize layout cellalign.png

Custom layout with OnResize / OnChangeBounds

Sometimes the LCL layout is not sufficient. The below example shows a GroupBox1 with a ListBox1 and a Memo1. The ListBox1 should fill one third of the space, the Memo1 takes the rest.

Autosize onresize.png

Whenever the GroupBox is resized the ListBox1.Width should be one third. To achieve this set the OnResize event of the GroupBox1 to:

<Delphi> procedure TForm1.GroupBox1Resize(Sender: TObject); begin

 ListBox1.Width:=GroupBox1.ClientWidth div 3;

end; </Delphi>

Common mistake: Wrong OnResize event

Do not put all your resizing code into the Form OnResize event. The Form OnResize event is only called when the Form was resized. The child controls (e.g. a GroupBox1) is resized later, so the GroupBox1.ClientWidth has still the old value during the FormResize event. You must use the GroupBox1.OnResize event to react to changes of GroupBox1.ClientWidth.

Common mistake: Width instead of ClientWidth, AdjustClientRect

The Width is the size including the frame. The ClientWidth is the inner Width without the frame. Some controls like the TPanel paints a further frame. Then you have to use AdjustClientRect. The same example, but instead of a GroupBox1 the ListBox1 is in a Panel1:

<Delphi> procedure TForm1.Panel1Resize(Sender: TObject); var r: TRect; begin

 r:=Panel1.ClientRect;
 Panel1.AdjustClientRect(r);
 ListBox1.Width:=(r.Right-r.Left) div 3;

end; </Delphi>

Custom Controls

When you write your own control, you can override and fine tune many parts of the LCL autosizing.

Own AutoSize

When AutoSize is set to true the control should be resized to the preferred size if possible.

Preferred Size

The new size is fetched by the LCL via GetPreferredSize which calls CalculatePreferredSize, which can be overridden. For example let's write a TQuadrat, which is a TShape, but its height should equal its width:

<Delphi>

 TQuadrat = class(TShape)
 protected
   procedure CalculatePreferredSize(var PreferredWidth,
         PreferredHeight: integer; WithThemeSpace: Boolean); override;
 end;

... procedure TQuadrat.CalculatePreferredSize(var PreferredWidth,

 PreferredHeight: integer; WithThemeSpace: Boolean);

begin

 PreferredHeight:=Width;

end; </Delphi>

The method CalculatePreferredSize gets two var parameters: PreferredWidth and PreferredHeight. They default to 0 which means: there is no preferred size, so the LCL will not change the size. The above function sets PreferredHeight to the current Width. The boolean parameter WithThemeSpace is deprecated and always false.

Computing the PreferredWidth/Height can be expensive. Therefore the LCL caches the result until InvalidatePreferredSize is called for the control. In our example the PreferredHeight depends on the Width, so we must invalidate when the Width changes:

<Delphi>

 TQuadrat = class(TShape)
 protected
   ...
   procedure DoSetBounds(ALeft, ATop, AWidth, AHeight: integer); override;
 end;

... procedure TQuadrat.DoSetBounds(ALeft, ATop, AWidth, AHeight: integer); begin

 inherited DoSetBounds(ALeft, ATop, AWidth, AHeight);
 InvalidatePreferredSize;

end; </Delphi>

The LCL will automatically trigger the autosizing when the bounds have changed, so the example is complete.

AdjustSize

When the preferred size depends on a new property, then every time the property changes the auto sizing must be triggered. For example:

<Delphi> procedure TQuadrat.SetSubTitle(const AValue: string); begin

 if FSubTitle=AValue then exit;
 FSubTitle:=AValue;
 InvalidatePreferredSize;
 AdjustSize;

end; </Delphi>

Reduce overhead with DisableAutoSizing, EnableAutoSizing

Since Lazarus 0.9.29 there is a new autosizing algorithm, that reduces the overhead and allows deep nested dependencies. Up to 0.9.28 DisableAlign/EnableAlign and Disable/EnableAutoSizing worked only for one control and its direct childs.

Each time you change a property the LCL triggers a recompute of the layout:

<Delphi> Label1.Caption:='A'; // first recompute Label2.Caption:='B'; // second recompute </Delphi>

The recompute will trigger events and send messages. To reduce the overhead you can suspend the autosizing:

<Delphi> DisableAutoSizing; try

 Label1.Caption:='A';  // no recompute
 Label2.Caption:='B';  // no recompute

finally

 EnableAutoSizing; // one recompute

end; </Delphi>

The calls of Disable/EnableAutoSizing must be balanced. Only when EnableAutoSizing was called the same time as DisableAutoSizing the autosizing will start.

Since 0.9.29 the Disable/EnableAutoSizing work for the whole form. This means every call of DisableAutoSizing suspends the autosizing for all controls on the same form. If you write your own control you can now use the following:

<Delphi> procedure TMyRadioGroup.DoSomething; begin

 DisableAutoSizing;  // disables not only TMyRadioGroup, but the whole form
 try
   // delete items ...
   // add, reorder items ...
   // change item captions ...
 finally
   EnableAutoSizing; // recompute
 end;

end; </Delphi>

Which basically means: you do not have to care. Just call Disable/EnableAutoSizing.

Note: this is wrong:

<Delphi> Button1.DisableAutoSizing; Label1.EnableAutoSizing; // wrong: every control has its own counter </Delphi>

Example: a button panel

This example combines several of the LCL layout mechanism to create a panel with three buttons: A Help button to the left and a Ok and Cancel button to the right. The panel is on the bottom of the form and fills the whole width. The buttons and the panel are autosized to fit all fonts and themes.

Step 1: Create the panel and set its Align propert to alBottom. Add three TBitBtns.

Autosize example buttonpanel1.png

Set the Kind properties of the BitBtns to show the glyphs. You might need to GlyphShowMode to gsmAlways to see them on your platform. Set the BitBtn's AutoSize property to true, which will shrink/enlarge the buttons to fit perfectly around the glyphs and text. Depending on your theme and platform you might notice that the buttons have different heights.

Autosize example buttonpanel2.png

Set the Align property of the help button to alLeft and for the other two buttons to alRight. This will enlarge the buttons vertically and move to the leftmost/rightmost. alLeft/alRight has no effect for the width, so the buttons uses their preferred width.

Autosize example buttonpanel3.png

The Panel height is still fixed. Now set the panel AutoSize property to true. The panel now shrinks vertically to fit the highest button.

Autosize example buttonpanel4.png

The Ok button has a short caption, so the button is very small. Set the buttons Constraints.MinWidth to 75. The buttons will now enlarge somewhat.

Autosize example buttonpanel5.png

Now add some space around the buttons. Set the panel's ChildSizing.LeftTopSpacing/RightBottomSpacing/HorizontalSpacing to 6.

Autosize example buttonpanel6.png

Finally clear the Caption of the panel and set its BevelOuter to bvNone.

Autosize example buttonpanel7.png

AutoSize

AutoSize is a boolean property found in many classes; it permits the size of a control to be adjusted automatically to accommodate differences in the text or graphic contained therein, and allows most efficient use of available space

Many controls override TControl.DoAutoSize to perform the actual auto-sizing.

IMPORTANT: Many Delphi controls override this method and many call this method directly after setting some properties.

During handle creation not all interfaces can create complete Device Contexts which are needed to calculate things like text size.

That's why you should always call AdjustSize instead of DoAutoSize.

TControl.AdjustSize calls DoAutoSize in a smart fashion.

During loading and handle creation the calls are delayed.

This method initially does the same as TWinControl.DoAutoSize. But since DoAutoSize is commonly overriden by descendant components, it is not useful to perform all tests, which can result in too much overhead. To reduce this the LCL calls AdjustSize instead.

When setting AutoSize = true the LCL autosizes the control in width and height. This is one of the most complex parts of the LCL, because the result depends on nearly a hundred properties. Let's start simple:

The LCL will only autosize the Width (Height) if it is free to resize. In other words - the width is not autosized if:

  • the left and right side is anchored. You can anchor the sides with the Anchors property or by setting the Align property to alTop, alBottom or alClient.
  • the Width is bound by the Constraints properties. The Contraints can also be overriden by the widgetset. For example the winapi does not allow resizing the height of a combobox. And the gtk widgetset does not allow resizing the width of a vertical scrollbar.

Same for Height.

The new size is calculated by the protected method TControl.CalculatePreferredSize. This method asks the widgetset for an appropriate Width and Height. For example a TButton has preferred Width and Height. A TComboBox has only a preferred Height. The preferred Width is returned as 0 and so the LCL does not autosize the Width - it keeps the width unaltered. Finally a TMemo has no preferred Width or Height. Therefore AutoSize has no effect on a TMemo.

Some controls override this method. For example the TGraphicControl descendants like TLabel have no window handle and so cannot query the widgetset. They must calculate their preferred Width and Height themselves.

The widgetsets must override the GetPreferredSize method for each widget class that has a preferred size (Width or Height or both).

Parent.AutoSize

The above described the simple explanation. The real algorithm provides far more possibilities and is therefore far more complex.

Properties / Methods

  • Left
  • Top

If Parent<>nil then Left, Top are the pixel distance to the top, left pixel of the parent's client area (not scrolled). Remember the client area is always without the frame and scrollbars of the parent. For Delphi users: Some VCL controls like TGroupbox define the client area as the whole control including the frame and some not - the LCL is more consistent, and therefore Delphi incompatible. Left and Top can be negative or bigger than the client area. Some widgetsets define a minimum/maximum somewhere around 10.000 or more.

When the client area is scrolled the Left and Top are kept unchanged.

During resizing/moving Left and Top are not always in sync with the coordinates of the Handle object.

If Parent=nil then Left, Top depend on the widgetset and the window manager. Till Lazarus 0.9.25 this is typically the screen coordinate of the left,top of the client area of the form. This is Delphi incompatible. It is planned to change this to the Left, Top of the window.


Hint: Each time you change Left and Top the LCL moves instantly and recomputes the whole layout. If you want to change Left and Top use instead:

 with Button1 do
   SetBounds(NewLeft,NewTop,Width,Height);
  • Width
  • Height

The Size in pixels must not be negative, and most widgetsets do not allow Width=0 and/or Height=0. Some controls on some platforms define a bigger minimum constraint in Constraints.MinInterfaceWidth/Height. Instead of sizing a control to Width=0 and/or Height=0, set Visible=false or Parent=nil. During resizing/moving Width and Height are not always in sync with the size of the Handle object.


  • BoundsRect

Same as Bounds(Left,Top,Width,Height).

Common newbie mistake: <Delphi>

 BoundsRect.Left:=3; // WRONG: common newbie mistake

</Delphi> This has no effect, because reading BoundsRect is a function. It creates a temporary TRect on the stack. The above is the same as <Delphi> var

 r: TRect;

begin

 r:=BoundsRect; // fetch the bounds
 r.Left:=3;  // change a value on the stack

end; // no change </Delphi>

  • ClientRect

Left and Top are always 0,0. Width and Height are the visible size in pixels of the client area. Remember the client area is without the frame and without scrollbars. In a scrollable client area the logical client area can be bigger than the visible.

  • ClientOrigin

Returns the screen coordinate of the topleft coordinate 0,0 of the client area. Note that this value is the position as stored in the interface and is not always in sync with the LCL. When a control is moved, the LCL sets the bounds to the desired position and sends a move message to the interface. It is up to the interface to handle moves instantly or queued.

  • LCLIntf.GetClientBounds

Returns the client bounds of a control. Like ClientRect, but Left and Top are the pixel distances to the control's left, top. For example on a TGroupBox the Left, Top are the width and height of the left and top frame border. Scrolling has no effect on GetClientBounds.

  • LCLIntf.GetWindowRect

After the call, ARect will be the control area in screen coordinates. That means, Left and Top will be the screen coordinate of the TopLeft pixel of the Handle object and Right and Bottom will be the screen coordinate of the BottomRight pixel.


  • FBaseBoundsLock: integer

Increased/Decreased by LockBaseBounds/UnlockBaseBounds. Used to keep FBaseBounds during SetBounds calls.


  • FBaseParentClientSize: TPoint

The Parent.ClientRect size valid for the FBaseBounds. FBaseBounds and FBaseParentClientSize are used to calculate the distance for akRight (akBottom). When the parent is resized, the LCL knows what distance to keep.


  • FBoundsRectForNewParent: TRect

When changing the Parent of a control the Handle is recreated and many things can happen. Especially for docking forms the process is too unreliable. Therefore the BoundsRect is saved. The VCL uses a similar mechanism.


  • fLastAlignedBounds: TRect

See TControl.SetAlignedBounds for an explanation. In short: It stops some circles between interface and LCL autosizing.


  • FLastChangebounds: TRect

Used to stop calling ChangeBounds with the same coordinates. This happens very often.


  • FLastDoChangeBounds: TRect

Used to avoid calling OnChangeBounds with the same coordinates. This reduces user defined autosizing.


  • FLastResizeClientHeight: integer
  • FLastResizeClientWidth: integer
  • FLastResizeHeight: integer
  • FLastResizeWidth: integer

Used to avoid calling OnResize with the same coordinates. This reduces user defined autosizing.


  • FLoadedClientSize: TPoint

During loading many things are delayed and many things are set and worse: in the wrong order. That's why SetClientWidth/SetClientHeight calls are stored and set at end of loading again. This way the LCL can restore the distances (e.g. akRight) used during designing.


  • FReadBounds: TRect

Same as FLoadedClientSize, but for SetLeft, SetTop, SetWidth, SetHeight.


  • procedure SetBoundsRectForNewParent(const AValue: TRect);

Used to set FBoundsRectForNewParent. See above.


  • procedure SetAlignedBounds(aLeft, aTop, aWidth, aHeight: integer); virtual;

As SetBounds but without changing the default sizes.


  • procedure SetInitialBounds(aLeft, aTop, aWidth, aHeight: integer); virtual;

A smart version of SetBounds, reducing overhead during creation and loading.


  • procedure UpdateBaseBounds(StoreBounds, StoreParentClientSize, UseLoadedValues: boolean); virtual;

Commit current bounds to base bounds.

  • procedure SetClientHeight(Value: Integer);
  • procedure SetClientSize(Value: TPoint);
  • procedure SetClientWidth(Value: Integer);

Exists for Delphi compatibility too. Resizes the control, to get the wanted ClientRect size.


  • procedure ChangeBounds(ALeft, ATop, AWidth, AHeight: integer); virtual;

This is the internal SetBounds. Applies constraints, updates base bounds, calls OnChangeBound, OnResize, locks bounds.


  • procedure DoSetBounds(ALeft, ATop, AWidth, AHeight: integer); virtual;

This really sets the FLeft, FTop, FWidth, FHeight private variables.


  • procedure SetBounds(aLeft, aTop, aWidth, aHeight: integer); virtual;

This is the standard procedure overriden by many Delphi controls. TWinControl overrides it too.

    • ignores calls when bounds are locked
    • lock the FBoundsRealized to avoid overhead to the interface during auto sizing.

ChangeBounds is not locked this way.


  • Function GetClientOrigin: TPoint; virtual;

Screen coordinate of Left, Top of client area.

  • Function GetClientRect: TRect; virtual;

Size of client area. (always Left=0, Top=0)

  • Function GetScrolledClientRect: TRect; virtual;

Visible client area in ClientRect.


  • function GetChildsRect(Scrolled: boolean): TRect; virtual;

Returns the Client rectangle relative to the control's Left, Top. If Scrolled is true, the rectangle is moved by the current scrolling values (for an example see TScrollingWincontrol).

  • function GetClientScrollOffset: TPoint; virtual;

Returns the scrolling offset of the client area.


  • function GetControlOrigin: TPoint; virtual;

Returns the screen coordinate of the topleft coordinate 0,0 of the control area. (The topleft pixel of the control on the screen) Note that this value is the position as stored in the interface and is not always in sync with the LCL. When a control is moved, the LCL sets the bounds to the wanted position and sends a move message to the interface. It is up to the interface to handle moves instantly or queued.

FAQ

Why does AutoSize not work in the designer properly?

In the designer controls can be dragged around and properties can be set in almost any order. To allow this and avoid possible conflicts, the AutoSizing is not updated on every change at design time.

Why does TForm.AutoSize not work when something changes?

TForm.AutoSize only works once at creation time. After that the size is up to user and the windowmanager. The application can force a resize with the following:

 AutoSize:=false; // first reset the counter
 AutoSize:=true;  // then do one AutoSize

The reason for this is, that the size of the window is controlled by the window manager. Some window managers do not allow free resizes. This would result in an endless loop between the LCL and the window manager. That's why AutoSize works only once on controls with Parent=nil.

Do I need to call Application.ProcessMessages when creating lots of controls?

Application.ProcessMessages is called by the LCL automatically after every message (e.g. after every event like OnClick). Calling it yourself is only needed if the changes should become visible to the user immediately. For example:

<DELPHI> procedure TFrom.Button1Click(Sender: TObject); begin

 // change width of a control
 Button1.Width := Button1.Width + 10;
 // apply any needed changes and repaint the button
 Application.ProcessMessages;
 // do a lot of things that takes a long time
 ...
 // after leaving the OnClick the LCL automatically processes messages

end; </DELPHI>

When enabling Anchors at runtime the control resizes, does not use the current values. Why?

akBottom means: keep a distance to the bottom side of the parent. The distance to keep is defined by the base bounds. They are set at designtime or by runtime calls of SetBounds or UpdateBaseBounds.

For example: A TListBox (Anchors=[akLeft,aTop]) at designtime has a bottom distance of 100 pixel. And a button to enable/disable the akBottom of the TListBox. Now start the application and press the button to enable akBottom. The 100 pixel distance will be activated, because this was the last time the programmer defined the base bounds of the TListBox. All other resizes were done by the LCL and are irrelevant. The programmers base bounds rules. You can resize the form and the 100 pixel will be kept. In order to use the current bounds as base bounds use:

   ListBox1.UpdateBaseBounds(true,true,false);
   ListBox1.Anchors:=ListBox1.Anchors+[akBottom];

Setting Anchors does not automatically call UpdateBaseBounds, because this would make layouts like child-parent AutoSizing very difficult.

Why is TForm.Width equal to TForm.ClientWidth?

Mattias notes:

"There are historical and technical reasons.

For forms without parent the Clientwidth equals the Width, because the real Width including the frame was not available on Linux ten years ago (at least not reliable on various window managers). I didn't test, but I heard it is now possible with gtk2. The main problem is the autosizing, because this needs the frame sizes before the form is mapped to the screen. It might be, that this is only available after an event, which means that you have to wait for it, which means trouble for ShowModal. Changing this breaks compatibility with a lot of existing LCL code, but for this we added the LCLVersion in the lfm files.

For all other controls the rules is ClientWidth<=Width. The Width is the ClientWidth plus the widget frame. The question is if the scrollbars belong to the frame. I would say yes and it was implemented that way some time ago. Apparently this has changed. See the current cursor problem on synedit."