Autosize / Layout

From Free Pascal wiki
Revision as of 20:55, 6 October 2008 by HowardPC (talk | contribs) (AutoSize)

Anchor Sides

See Anchor Sides.

AutoSize

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 was the simple version.

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. 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. Instead of sizing a control to Width=0 and/or Height=0, set Visible=false. 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:

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

This has no effect, because reading BoundsRect is a function. It creates a temporary TRect on the stack.


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

Sets fLastAlignedBounds, see above.


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