Difference between revisions of "Autosize / Layout/zh CN"

From Lazarus wiki
(DPI auto-adjustment and absolute layout auto-adjustment)
(More details)
Line 797: Line 797:
 
例如,当屏幕旋转时,LCL-CustomDrawn-Android将调用AutoAdjustLayout 。
 
例如,当屏幕旋转时,LCL-CustomDrawn-Android将调用AutoAdjustLayout 。
  
=More details=
+
=更多细节=
  
     <p>Many controls override ''TControl.DoAutoSize'' to perform the actual auto-sizing. </p>
+
     <p>很多控件重写(override) ''TControl.DoAutoSize''执行实际地自动大小。</p>
     <p>IMPORTANT: Many Delphi controls override this method and many call this method directly after setting some properties.</p>
+
     <p>重要: 很多Delphi控件重写(override)这个方法,很多控件在设置一些属性后会直接调用这个方法。</p>
     <p>During handle creation not all interfaces can create complete Device Contexts which are needed to calculate things like text size.</p>
+
     <p>在处理程序(handle)创建过程中,并非所有的接口都可以创建完整的设备上下文,这些上下文需要计算一些像文 本大小的事情。</p>
           <p>That's why you should always call ''AdjustSize'' instead of <var>DoAutoSize</var>.</p>
+
           <p>这就是为什么你应该总是调用''AdjustSize''而不是<var>DoAutoSize</var>的原因。</p>
 
   
 
   
  <p><var>TControl.AdjustSize</var> calls <var>DoAutoSize</var> in a smart fashion.</p>
+
  <p><var>TControl.AdjustSize</var>以一种智能流行的方式调用<var>DoAutoSize</var></p>
  <p>During loading and handle creation the calls are delayed.</p>
+
  <p>在加载和处理程序(handle)创建过程中,会延迟调用。</p>
           <p>This method initially does the same as ''TWinControl.DoAutoSize''.  But since <var>DoAutoSize</var> 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 <var>AdjustSize</var> instead.</p>
+
           <p>这种方法的最初的作用与''TWinControl.DoAutoSize''相同。但是,由于<var>DoAutoSize</var> 通常被衍生组件所重写,它并不是对执行所有的测试都有用,它会导致太多的资源开销。为减少这种情况,[[LCL]]调用<var>AdjustSize</var>来作为替换品。</p>
  
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:
+
当设置AutoSize = true时,[[LCL]] 在宽度和高度上自动大小控件。这是[[LCL]]最复杂的一部分,因为最终的结果取决于近一百个属性。让我们简单地开始:
  
The [[LCL]] will only autosize the ''Width'' (Height) if it is free to resize. In other words - the width is not autosized if:
+
[[LCL]]仅能自动调整''宽度(Width)'' (高度(Height)),如果它可以自由重新调整大小的话。换句话说-宽度不会自动调整大小,如果:
*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.
+
*左侧和右侧是被锚定的。你可以使用'''Anchors'''属性锚定一侧,或者通过设置''Align''属性为alTop,alBottom或alClient。
*the ''Width'' is bound by the ''Constraints'' properties. The Contraints can also be overriden by the widget set. For example the winapi does not allow resizing the height of a combobox. And the gtk widget set does not allow resizing the width of a vertical scrollbar.
+
*''宽度(Width)''''Constraints(约束式)''属性所约束。约束式也可以通过widget集重写。例如,winapi并不允许重新调整一个组合框的高度。并且gtk widget集也不允许重新调整一个垂直滚动条的宽度。
  
Same for ''Height''.
+
''高度(Height)''与''宽度(Width)''相同。
  
The new size is calculated by the protected method '''TControl.CalculatePreferredSize'''.
+
新的大小是通过受保护(protected)的方法'''TControl.CalculatePreferredSize'''所计算得出来的。这个方法会要求widget集的一个适当的宽度和高度。例如,一个TButton有首先的宽度和高 度。一个TComboBox仅有一个首选的高度。首选宽度被作为0返回,因此[[LCL]]并不会自动大小宽度- 它保持宽度不变。一个TMemo没有首先的宽度或高度。因此,自动大小对一个TMemo没有影响。
This method asks the widget set 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 widget set. They must calculate their preferred Width and Height themselves.
+
一些控件重写(override)这个方法。例如,TGraphicControlyan衍生物,像TLabel没有窗口处理程序,因此不能询问其widget。它们必需计算它们自己的首选宽度和高度。
  
The widget sets must override the GetPreferredSize method for each widget class that has a preferred size (Width or Height or both).
+
widget集必需为每个具有首选大小(宽度或高度或两者都有)的widget类重写(override)方法GetPreferredSize。
  
==Parent.AutoSize==
+
==Parent.AutoSize(父类.自动大小)==
  
The above described the simple explanation. The real algorithm provides far more possibilities and is therefore far more complex.
+
上面描述了简单的解释。真正的算法提供了很多的可能性因而变得更复杂。
  
==Properties / Methods==
+
==Properties / Methods(属性/方法)==
  
 
*Left
 
*Left
 
*Top
 
*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 widget sets define a minimum/maximum somewhere around 10.000 or more.
+
如果Parent<>nil,那么Left, Top是到父类客户端区域(非滚动的)的左侧像素,右侧像素的像素距离。记住客户端区域总是''不包含''父类的框 架和滚动条的。对于 Delphi用户来说:一些VCL控件,像TGroupbox,定义客户端区域为整个控件,包含框架,而另外一些控件则不是这样-[[LCL]]相比Delphi来说更加一致,因此其与Delphi不兼容。Left和Top可以是负数或者是大于客户端区域的数。一些widget集会定义一个最小值/最大值 ,在一些地方约有10.000或更多。
  
When the client area is scrolled the Left and Top are kept unchanged.
+
当客户端区域是滚动的时,Left和Top将保持不变。
  
During resizing/moving Left and Top are not always in sync with the coordinates of the Handle object.
+
在重新调整大小/移动期间,Left和Top并不总是与处理程序对的坐标同步。
  
If Parent=nil then Left, Top depend on the widget set 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.
+
如果Parent=nil ,那么Left, Top取决于widget集和窗口管理器。直到Lazarus 0.9.25 ,这通常是窗体的客户端区域的left,top的屏幕坐标。这是与Delphi不兼容的。计划将其更改为窗口的Left, Top
  
  
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:
+
你每次更改Left和Top时,[[LCL]]会立即移动并重新计算整个布局。如果你想更改Left和Top,请改用:
 
<syntaxhighlight lang="pascal">with Button1 do
 
<syntaxhighlight lang="pascal">with Button1 do
 
   SetBounds(NewLeft, NewTop, Width, Height);</syntaxhighlight>
 
   SetBounds(NewLeft, NewTop, Width, Height);</syntaxhighlight>
Line 849: Line 848:
 
*Height
 
*Height
  
The Size in pixels must not be negative, and most widget sets 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.
+
以像素形式的大小禁止为负数,并且大多数widget集不允许Width=0和/或Height=0 。在一些平台上的一些控件以Constraints.MinInterfaceWidth/Height的形式定义 一个较大的最小值约束。而不是重新大小一个控件为Width=0和/或Height=0 ,设置Visible=false或Parent=nil。在重新调整大小/移动期间,Width和Height并不总是与处理程序对的坐标同步。
  
  
 
*BoundsRect
 
*BoundsRect
  
Same as Bounds(Left, Top, Width, Height).
+
等同于Bounds(Left, Top, Width, Height).
  
Common newbie mistake:
+
新手常见错误:
 
<syntaxhighlight lang="pascal">BoundsRect.Left := 3; // WRONG: common newbie mistake</syntaxhighlight>
 
<syntaxhighlight lang="pascal">BoundsRect.Left := 3; // WRONG: common newbie mistake</syntaxhighlight>
This has no effect, because reading BoundsRect is a function. It creates a temporary TRect on the stack. The above is the same as
+
这没有效果, 因为读取BoundsRect的是一个函数。它在堆上创建一个临时的TRect 。上述等同于:
 
<syntaxhighlight lang="pascal">var
 
<syntaxhighlight lang="pascal">var
 
   r: TRect;
 
   r: TRect;
Line 868: Line 867:
 
*ClientRect
 
*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.
+
Left和Top总是0,0 。宽度和高度是客户端区域的可见像素大小。记住客户端区域不包含框架和滚动条。在一个可滚动的客户端区域中,逻辑客户端区域可能大于可见的客户端区域。
  
 
*ClientOrigin
 
*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.
+
返回客户端区域的左侧,顶部坐标0,0的屏幕坐标。注意,这个值是存储在接口(interface)中的位置。并不总是与[[LCL]]同步。当移动一个控件时,[[LCL]]设置边界到所需位置,并发送一条移动信息到接口(interface)。这取决于接口(interface)立即处理移动或排队处理移动。
  
 
*LCLIntf.GetClientBounds
 
*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.
+
返回一个控件的客户端边界。像ClientRect,但是Left和Top是到控件左侧,顶部的像素距离。例如在 一个TGroupBox上,Left, Top是左侧和右侧框架边界的宽度和高度。滚动在GetClientBounds上没有影响。
  
 
*LCLIntf.GetWindowRect
 
*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.
+
在调用后,ARect将是屏幕坐标中控件区域。这意味着,Left和Top将是处理程序对象的左侧和顶部像素的屏 幕坐标,Right和Bottom将是右侧和底部像素的屏幕坐标。
 
 
  
 
*FBaseBoundsLock: integer
 
*FBaseBoundsLock: integer
  
Increased/Decreased by LockBaseBounds/UnlockBaseBounds.
+
通过LockBaseBounds/UnlockBaseBounds来增加/减小。在SetBounds调用期 间,用于保持FBaseBounds。
Used to keep FBaseBounds during SetBounds calls.
 
 
 
  
 
*FBaseParentClientSize: TPoint
 
*FBaseParentClientSize: TPoint
  
The Parent.ClientRect size valid for the FBaseBounds.
+
Parent.ClientRect大小对FBaseBounds有效。FBaseBounds和 FBaseParentClientSize通常用于计算akRight (akBottom)的距离。当重新调整父类大小时,[[LCL]]知 道要保持什么距离。
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
 
*FBoundsRectForNewParent: TRect
  
When changing the Parent of a control the Handle is recreated and many
+
当更改一个控件的父类时,会重新创建处理重新并可能会发送很多事情。尤其是停靠窗体过程是太不可靠了。因此将会保 存BoundsRect。VCL使用一种类似的机制。
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
 
*fLastAlignedBounds: TRect
  
See TControl.SetAlignedBounds for an explanation.
+
查看TControl.SetAlignedBounds说明解释。简而言之:它停止在接口(interface)和[[LCL]]自动大小之间的一些循环。
In short: It stops some circles between interface and [[LCL]] autosizing.
 
 
 
  
 
*FLastChangebounds: TRect
 
*FLastChangebounds: TRect
  
Used to stop calling ChangeBounds with the same coordinates. This happens
+
用于停止调用具有相同坐标的ChangeBounds。这种情况经常发生。
very often.
 
 
 
  
 
*FLastDoChangeBounds: TRect
 
*FLastDoChangeBounds: TRect
  
Used to avoid calling OnChangeBounds with the same coordinates. This reduces
+
用于避免调用具有相同坐标的OnChangeBounds。这将减少用户定义的自动大小。
user defined autosizing.
 
  
  
Line 927: Line 913:
 
*FLastResizeWidth: integer
 
*FLastResizeWidth: integer
  
Used to avoid calling OnResize with the same coordinates. This reduces user
+
用于避免调用具有相同坐标的OnResize。这将减少用户定义的自动大小。
defined autosizing.
 
 
 
  
 
*FLoadedClientSize: TPoint
 
*FLoadedClientSize: TPoint
  
During loading many things are delayed and many things are set and worse: in
+
在加载期间,很多东西被迟延,很多东西被设置。并且最糟糕的是:以一种错误的顺序。这就是为什么在再次加载时,存 储和设置SetClientWidth/SetClientHeight调用的原因。使用这种方法,[[LCL]]可以恢复设计期间使用的距离(例如,akRight)
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
 
*FReadBounds: TRect
  
Same as FLoadedClientSize, but for SetLeft, SetTop, SetWidth, SetHeight.
+
类似于FLoadedClientSize,但是适用于 SetLeft,SetTop,SetWidth,SetHeight。
 
 
 
   
 
   
 
*procedure SetBoundsRectForNewParent(const AValue: TRect);
 
*procedure SetBoundsRectForNewParent(const AValue: TRect);
  
Used to set FBoundsRectForNewParent. See above.
+
用于设置FBoundsRectForNewParent。查看上文。
 
 
  
 
*procedure SetAlignedBounds(aLeft, aTop, aWidth, aHeight: integer); virtual;   
 
*procedure SetAlignedBounds(aLeft, aTop, aWidth, aHeight: integer); virtual;   
  
As SetBounds but without changing the default sizes.
+
像SetBounds,但是不更改默认大小。
 
 
  
 
*procedure SetInitialBounds(aLeft, aTop, aWidth, aHeight: integer); virtual;   
 
*procedure SetInitialBounds(aLeft, aTop, aWidth, aHeight: integer); virtual;   
  
A smart version of SetBounds, reducing overhead during creation and loading.
+
一个SetBounds的智能版本, r在创建和加载过程中减少资源开销(overhead)。
 
 
  
 
*procedure UpdateBaseBounds(StoreBounds, StoreParentClientSize, UseLoadedValues: boolean); virtual;
 
*procedure UpdateBaseBounds(StoreBounds, StoreParentClientSize, UseLoadedValues: boolean); virtual;
  
Commit current bounds to base bounds.
+
提交当前的边界为基础边界。
  
 
*procedure SetClientHeight(Value: Integer);
 
*procedure SetClientHeight(Value: Integer);
Line 968: Line 943:
 
*procedure SetClientWidth(Value: Integer);   
 
*procedure SetClientWidth(Value: Integer);   
  
Exists for Delphi compatibility too. Resizes the control, to get the wanted ClientRect size.
+
也是用于Delphi兼容。重新调整控件的大小,来获取想要的ClientRect大小。
 
 
  
 
*procedure ChangeBounds(ALeft, ATop, AWidth, AHeight: integer); virtual;
 
*procedure ChangeBounds(ALeft, ATop, AWidth, AHeight: integer); virtual;
  
This is the internal SetBounds.
+
这是内部的SetBounds。应用于约束式,更新基础边界,调用OnChangeBound, OnResize,锁定边界。
Applies constraints, updates base bounds, calls OnChangeBound, OnResize,
 
locks bounds.
 
 
 
  
 
*procedure DoSetBounds(ALeft, ATop, AWidth, AHeight: integer); virtual;
 
*procedure DoSetBounds(ALeft, ATop, AWidth, AHeight: integer); virtual;
  
This really sets the FLeft, FTop, FWidth, FHeight private variables.
+
这个实际上设置FLeft, FTop, FWidth, FHeight私有(private)变量。
  
 +
*procedure SetBounds(aLeft, aTop, aWidth, aHeight: integer); virtual;
  
*procedure SetBounds(aLeft, aTop, aWidth, aHeight: integer); virtual;
+
这是很多Delphi控件的标准的过程(procedure)重写(overriden)方法。 TWinControl也会重写它。
 +
** 当边界被锁定时忽略调用
 +
** 锁定FBoundsRealized来避免在自动调整大小期间的资源开销。
  
This is the standard procedure overriden by many Delphi controls.
+
ChangeBounds未锁定这种方法。
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;
 
*Function GetClientOrigin: TPoint; virtual;
  
Screen coordinate of Left, Top of client area.
+
客户端区域的Left, Top的屏幕左侧。
  
 
*Function GetClientRect: TRect; virtual;
 
*Function GetClientRect: TRect; virtual;
  
Size of client area. (always Left=0, Top=0)
+
客户端区域的大小。(总是Left=0, Top=0)
  
 
*Function GetScrolledClientRect: TRect; virtual;
 
*Function GetScrolledClientRect: TRect; virtual;
  
Visible client area in ClientRect.
+
在ClientRect中的可见客户端区域。
 
 
  
 
*function GetChildsRect(Scrolled: boolean): TRect; virtual;
 
*function GetChildsRect(Scrolled: boolean): TRect; virtual;
  
Returns the Client rectangle relative to the control's Left, Top.
+
返回相对于控件的左侧,顶部的客户端矩形。如果Scrolled是true,矩形将通过当前滚动值移动(查看示例 TScrollingWincontrol)
If Scrolled is true, the rectangle is moved by the current scrolling values
 
(for an example see TScrollingWincontrol).
 
  
 
*function GetClientScrollOffset: TPoint; virtual;
 
*function GetClientScrollOffset: TPoint; virtual;
  
Returns the scrolling offset of the client area.
+
返回客户端区域的滚动偏移量。
 
 
  
 
*function GetControlOrigin: TPoint; virtual;
 
*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)
+
返回控件区域的左侧,顶部坐标0,0的屏幕坐标。(在屏幕上的控件的左侧,顶部像素)。注意,这个值是存储在接口 (interface)中的位置。并不总是与[[LCL]]同步。当移动一个控件时,[[LCL]]设置边界到所需位置,并发送一条移动信息到接口(interface)。这屈居于接口(interface)立即处理移动或排队处理移动。
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=
 
=FAQ=

Revision as of 04:37, 10 March 2021

English (en) русский (ru) 中文(中国大陆)‎ (zh_CN) 中文(台灣)‎ (zh_TW)

Contents

概述

LCL可以自动修改一个控件的大小和位置,以便它适应字体,主题和文本或者其它内容的更改。如果你想在一些平台上运行你的 程序,或者你的标题可以使用一些语言,你的控件需要适应它们的当前环境。LCL不仅允许你做一次快速设计(使用鼠标在一个窗体上到处移动控件) ,并且后期设置一些将使控件随后自动地适应更改控件的关键属性等等。

  • 固定设计:这是在设计器中放置一个控件的默认设置。控件的位置相对其父类控件是固定的。控件的大小和位置(左,顶)可由程序员完全调整。你可以使用鼠标四处移动控件,和自由重新调整其大小。
  • 对齐:对齐的控件将填充剩余父类控件空间顶部、底部、左侧和右侧,或者填充整个剩余空间。
  • :你可以锚定一个控件的边侧(左,顶,右,底)到它的父类组件或到另一个组件。锚定的意思:LCL将尝试维持与锚定点相同的距离。
  • 布局:控件可以在行和列中自动地对齐 (例如, TRadioGroup)
  • 通过OnResize自定义:你可以在代码中使用OnResize和OnChangeBounds事件来对齐你自己的控件。
  • 自定义控件:写你自己的控件,你可以重写(override)几乎任何你想的LCL行为。

优先级规则

  1. 约束式(Constraints)
  2. 对齐(Align)
  3. 锚(Anchors)
  4. 子控件大小.布局(ChildSizing.Layout)
  5. 自动大小(AutoSize)
  6. OnResize, OnChangeBounds - 但是,如果你设置与上述规则冲突的边界,你将创建一个死循环。

公共属性

可以更改一些重要的属性来配置自动大小:

  • Left, Top, Width, Height(左,顶,宽,高)
  • 自动大小(AutoSize):autosize通知LCL自动地重新调整一个控件的宽度和高度。
  • 锚(Anchors):让你创建依赖关系/附属关系,例如,来锚一个ComboBox到一个Label的右侧。
  • 对齐(Align)
  • 约束式(Constraints):让你为宽度和高度设置一个最小值和最大值。
  • 边界空格(BorderSpacing): 让你设置锚定控件之间的空格。
  • ChildSizing:让你设置子控件的布局和空格。

内部算法在这里解释:LCL自动大小(AutoSizing)

固定设计

固定设计是默认的。控件的属性设置为[akLeft,akTop],这意味着左侧和顶部的值没有被LCL更改。如果AutoSize的值是false,LCL不改变宽度或高度。如果AutoSize的值是true,宽度 和高度将被更改以适应内容。例如,TLabel.AutoSize默认值为true,因此将更改它的标题来将重新调整 label 大小以容纳更改的表达方式。TButton.AutoSize默认值是false,因此更改一个按钮的标题不会重新调整按钮大小。当你设置Button.AutoSize为true时,每次更改按钮的标题或它的字体(或主体),按钮将收缩或放大。注意,这些更改不总是立即发生的。例如,在FormCreate期间,所有的自动大小是挂起的。无论何时,你都可以更改"Left", "Top", "Width",或"Height"属性。

自动大小(AutoSize)

AutoSize是一个在很多类中可用找到的布尔值属性;它允许自动地调整一个控件的大小以适应顾及所含文本或图形的差异,也允许最有效地使用可用空间。这是创建跨平台窗体的关键机制。 通常地, AutoSize=true 对一个可视控件做两件事:

  • 如果有可能,它将重新调整控件的大小为首选大小。例如,一个TButton的宽度和高度被重新调整大小以适应标题,在一个TEdit仅在高度上重新调整大小的同时。一个TEdit的宽度不被自动大小。你可以更改一个TEdit的宽度。(查看GetPreferredSize)。
  • 它以相同的数量移动固定位置的子控件,以便最左侧的子控件移动到 Left=0 (取决于边界空格(BorderSpacing)),最顶部的子控件移动到 Top=0 。

Lazarus vs Delphi

  • Delphi的自动大小(AutoSize)仅当某些属性更改时发生,例如,当一个TLabel的字体更改时。LCL的自动大小(AutoSize)总是激活的。Delphi允许更改一个有AutoSize=true的Lable的大小, 而Lazarus控件不允许。
  • Delphi的隐藏的控件不会被自动大小。
  • 使用Delphi,在加载期间,更改一个控件的大小不会重新调整大小/移动锚定子控件,但是一个构造函数通常被用来制作预期效果。当使用akRight,akBottom锚时,设置AnchorSides(锚定边)和BorderSpacing(边界空格)以保持距离正确。

自动大小(AutoSize)和重新调整大小控件

使用AutoSize=false,按钮将给予一个默认的固定大小。

Autosize1.png

当为每个按钮设置 AutoSize=true 时,按钮将放大(或收缩)以适应文本和主题框架。

Autosize on.png

正如你在OK按钮中所见,AutoSize不会收缩到最小可能的尺寸大小。它使用控件的 GetPreferredSize方法,该方法调用CalculatePreferredSize方法。默认的TWinControl实施询问 widget集合,这可能有一个首选的宽度或高度。每个控件都可以重写(override) CalculatePreferredSize 方法。例如,TImage 重写(override)它,并返回图片的大小。如果没有可用的首选的宽度(高度),返回的值是0,并且LCL将保持宽度(高度) (除非ControlStyle设置标示csAutoSize0x0,当前仅TPanel使用)。

一个TWinControl计算其所有子控件的大小,并使用它来计算首选大小。

当一个控件被锚定到左侧和右侧,并且它的 宽度 是固定的。例如,使用 Align=alTop ,控件被锚定到左侧和右侧,并且沿袭父类控件的宽度。如果 Parent.AutoSize 为 true,那么 Parent 将使用控件的首选宽度来计算它自身的首选宽度,以此方式,控件将重新调整大小为它的首选宽度。查看 对齐和自动大小(AutoSize. 如果没有 可用的首选宽度,那么最后设置的边界将被使用(BaseBounds或ReadBounds)。 如果没有边界被设置,GetControlClassDefaultSize方法将被使用。Same for对于高度和锚定到顶部和底部是一样的。

约束式总是被应用,并且有优先权。

自动大小 (AutoSize)和移动子控件

当 AutoSize=false 时,你可以自由地放置和移动控件:

Autosize on.png

当 AutoSize=true 时,有固定位置的子控件将移动以适应位置。

Autosize panel on.png

在面板上是所有按钮都将通过相同的数量来移动到顶部和左侧,以便左侧和顶部没有剩余的空间。如果按钮可能有 BorderSpacing>0 或 Panel.ChildSizing.LeftRightSpacing>0 ,按钮可能被移动,以便使用定 义的空格量

仅具有下面值的子控件将被移动:

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

子控件的移动取决于属性ChildSizing.Layout。 该布局被应用于方法 TWinControl.AlignControls ,这将完全重写或部分重写 (overridden)例如,TToolBar 重写 (overridden) ControlsAligned 到所有使用 Align=alCustom 的控件的位置,并定义一个流布局,在流布局中不适合的控件将被推放到后续的行中。

控件可以通过设置ControlStyle标示csAutoSizeKeepChildLeft 和 csAutoSizeKeepChildTop (自0.9.29版本可用)来禁用子控件的移动。

自动 大小(AutoSize)和窗体

没有父类控件的窗体将被窗口管理器控制,因而不能自由地放置和重新调整大小。设置大小仅是一个建议,并且可能会被 窗口管理器忽略。例如,你可以设置一个窗体的宽度为1000, 而 widget集可能会重新调整大小为800作为回应。如果你在OnResize中设置宽度,你可能会创建一个死循环。这就是为什么当widget集重新设 置窗体大小时,LCL TForm 禁用自动大小(AutoSize)的原因。

这意味着当用户重新调整窗体大小或假设窗口管理器不喜欢边界时,窗体的自动大小(AutoSize)将停止。例 如,一些Linux窗口管理器有参照关联的其它窗口重新调整该窗体的磁性边功能。

强制一个窗体的自动大小

你可以启动/执行一次新的自动大小(AutoSize),通过这样做:

Form1.AutoSize := False;
Form1.AutoSize := True;

预先计算一个自动大小的窗体的大小

当放置一个自动大小的窗体时,你可能需要在显示该窗体前知道它的大小。自动大小需要句柄(handle)。你可以 在显示一个窗体前计算其大小,使用:

Form1.HandleNeeded;
Form1.GetPreferredSize(PreferredWidth,PreferredHeight);

注意: 窗口管理器和窗体事件可能会更改窗体大小。首先大小不包含窗体的窗口边界。这是一个计划中的功能。

锚定边

控件有四个边: akLeft, akTop, akRight 和 akBottom。每个边都能被锚定到父类控件或另一个同类控件(具有相同父类控件的一个控件)的边。锚意味着保 存距离。默认是 Anchors=[akLeft,akTop] 。垂直锚与水平锚互不干涉。像 Align 和 Parent.AutoSize 之类的一些的属性有较高的优先级,并可以更改行为。

锚定到父类控件或锚定到空值

锚定到空值(默认)与锚定到父类控件有几乎相同的效果。都尝试与父类控件客户端区域的一边保持距离。锚定到空值使 用最后的SetBounds调用距离,然而锚定到父类控件使用BorderSpacing值。

这里有akLeft,akRight (akTop,akBottom)的四种组合:

  • 有akLeft,没有akRight:控件的左边是固定的,不由 LCL更改。右边没有锚定,因此它跟随左边。这意味着,控件的长度也是保存不变的
  • 有akLeft,有akRight: 控件的左边是固定的。不由LCL更改。右边被锚定到父类控件的右边。这意味着,如果父类控件被放大 100pixel ,那么控件的宽度也被放大 100pixel 。
  • 有akRight,没有akLeft:控件的左边不是固定的,因此它将跟随它的右边。右边是 锚定的。这意味着,如果父类控件被放大 100pixel ,那么控件也将被向右移动 100pixel 。
  • 没有akLeft.没有akRight:两边都没有锚定。控件的中心的位置与父类控件一起缩 放。例如,如果控件在父类控件的中间,并且父类控件被放大 100 pixel ,那么控件将被向右移动 50pixel 。

更改一个锚定的控件的父类控件的大小

当更改一个父类控件的大小时,除非自动大小是禁用的,否则所有锚定的子控件也将被连立即移动和/或重新调整大小。 例如,在一个窗体的加载期间和在一个窗体的创建期间,自动大小是被禁用的。

一个组框带有一个按钮: Anchors1.png

当组框被放大时,被锚定边的距离将保持不变:

使用akLeft,不使用akRight: Anchors akLeft.png

使用akLeft和akRight: Anchors akLeft akRight.png

使用akRight,不使用akLeft: Anchors akRight no akLeft.png

三个按钮在一个组框中: Anchors no akLeft no akRight small.png

不使用akLeft和akRight,它们的中心被缩放: Anchors no akLeft no akRight big.png


注释:

  • 加载一个窗体像设置一大组边界。在加载期间,使用lfm的值设置属性。记住,因为原型和控件,这里可能有多 个lfm文件。在加载LCL启用自动大小后。锚定的控件使用在加载结束时边界。在中间的一些 步骤被忽略。
  • 对于自定义控件,通常最好设置锚定侧而不是仅锚定。

更改一个锚定控件的大小

当更改一个锚定控件的宽度时,例如,通过对象查看器或通过代码 Button1.Width:=3, 你可以看到锚定到父类控件 和锚定到空值的不同点。锚定到父类控件将重新调整大小,并将移动Button1,然而锚定到空值将仅重新调整大小。例如:

锚定到空值

Anchored right nil resize1.png 一个Button1锚定[akTop,akRight], AnchorSide[akRight].Control=nil

Anchored right nil resize2.png 设置宽度到一个较小的值将收缩按钮,保持按钮的左边, 距离,减少按钮右边的距离。

Anchored right nil resize3.png 当组框被重新设置大小时,按钮也将保持新的距离。

img12:设置宽度相当于调用SetBounds(SetBounds(左侧,顶部,新宽度,高度)。这就是为什么左侧距离被保持。这是与Delphi兼容的。

锚定到父类控件

Anchored right parent resize1.png 一个Button1锚定[akTop,akRight],AnchorSide[akRight].Control=Button1.Parent

Anchored right parent resize2.png 设置宽度到一个较小的值将收缩按钮,保持按钮的右边距离,减少按钮左边的距离。

锚定到同级控件

你可以锚定到相邻的控件,下面的示例显示:

  • 你可以锚定一个labe的左侧到一个按钮的左侧
  • 锚定一个 label 的顶部到一个按钮的底部
  • 锚定一个 label 的中心到一个按钮的中心


Anchorsides example1.png

Anchorside example2.png

更多详细信息和如何设置锚,查看: 锚定边

Anchoreditor2.png

边界空格

边界空格(BorderSpacing)属性控制一个控件周围的最小空格量。属性是:

  • Around(四周):这个量以像素为单位被添加到 左侧,顶部,右侧,底部。
  • Left(左侧):空格以像素为单位在控件的左侧
  • Top(顶部):空格以像素为单位在控件的顶部
  • Right(右侧):空格以像素为单位在控件的右侧l
  • Bottom(底部):空格以像素为单位在控件的底部
  • InnerBorder(内部边界): 这个量以像素为单位被两次添加到首先的宽度和高度。一些控件重写(override)计算并忽略这个属性。 TButton在这里就是一个工作的示例。使用InnerBorder,你可能使一个按钮比需要的大。
  • CellAlignHorizontal(单元格水平对齐):这被使用在表格中,像ChildSizing.Layout=cclLeftToRightThenTopToBottom。如果控件小于表格单元格,这 个属性定义控件对齐的位置:到左侧的ccaLeftTop,到右侧的ccaRightBottom,或子中间的 ccaCenter。
  • CellAlignVertical(单元格垂直对齐):类似于CellAlignHorizontal,但是由于垂直对齐。

边界空格规则

  • Around被添加到左侧,顶部,右侧,底部边界空格
  • 如果控件有不允许扩展的限制,则空格可能更大。

锚定到另一侧

例如,A的右侧到B的左侧。

A和B两者的边界空格都使用。

  • 在两控件(在父类控件上的LeftControl, RightControl)之间的最大水平空格是
    • LeftControl.BorderSpacing.Right + LeftControl.BorderSpacing.Around
    • RightControl.BorderSpacing.Left + RightControl.BorderSpacing.Around
    • Parent.ChildSizing.HorizontalSpacing
  • 垂直空格工作类似:在两控件(在父类控件上的TopControl, BottomControl)之间的最大空格是
    • TopControl.BorderSpacing.Bottom + TopControl.BorderSpacing.Around
    • BottomControl.BorderSpacing.Top + BottomControl.BorderSpacing.Around
    • Parent.ChildSizing.VerticalSpacing

例如,如果LeftControl.BorderSpacing.Right = 3 和 LeftControl.BorderSpacing.Around = 4,那么在两控件之间至少有7 pixel空格。如果RightControl.BorderSpacing.Left = 4 和 RightControl.BorderSpacing.Around = 4 ,那么空格将至少8。如果Parent.ChildSizing.HorizontalSpacing = 10,那么空格将至少是10。

锚定到同一侧

例如,A的右侧到B的右侧。

  • 仅使用A的边界空格。
  • Parent.ChildSizing.Horizontal/VerticalSpacing未被使用。

锚定到中心

例如,A的中心到B的中心。

未使用边界空格,未使用Parent.ChildSizing.Horizontal/VerticalSpacing 。

示例

一个常见示例是锚定一个Label到一个Edit。

BorderSpacing Anchors.png

Label中心被垂直锚定到Edit。Edit的左边被锚定到Label的右边。两者都有 BorderSpacing.Around=6。这在Lable和Edit中间产生6个像素空格,Label的左侧 6个像素空格,Edit的右侧6个像素空格。也在Edit的上面和下面有6个像素空格。

  • 在一个控件和它的父类控件(Label1在GroupBox1上)的左侧空格最大值
    • Label1.BorderSpacing.Left + Label1.BorderSpacing.Around
    • GroupBox1.ChildSizing.LeftTopSpacing
  • 在一个控件和它的父类控件(Edit1在GroupBox1上)的右侧空格最大值
    • Edit1.BorderSpacing.Right + Edit1.BorderSpacing.Around
    • GroupBox1.ChildSizing.RightBottomSpacing
  • 当一个控件的中心被锚定到另一个控件时,例如,上述的Label于Edit1垂直居中,那么所有空格都被忽略。
  • 当一个控件的左侧被锚定到另一个控件的左侧时(例如,它们使用左侧对齐),那么两者左侧之间的距离是
    • Control1.BorderSpacing.Left + Control1.BorderSpacing.Around


这里有一个更复杂的示例:

Borderspacing anchors2.png

lfm代码的重要部分:

  object GroupBox1: TGroupBox
    AutoSize = True
    Caption = 'GroupBox1'
    TabOrder = 0
    object Label1: TLabel
      AnchorSideLeft.Control = GroupBox1
      AnchorSideTop.Control = Edit1
      AnchorSideTop.Side = asrCenter
      BorderSpacing.Around = 6
      Caption = 'Label1'
    end
    object Edit1: TEdit
      AnchorSideLeft.Control = Label1
      AnchorSideLeft.Side = asrBottom
      AnchorSideTop.Control = GroupBox1
      AnchorSideRight.Control = GroupBox1
      AnchorSideRight.Side = asrBottom
      Anchors = [akTop, akLeft, akRight]
      BorderSpacing.Around = 6
      TabOrder = 0
      Text = 'Edit1'
    end
    object Label2: TLabel
      AnchorSideLeft.Control = GroupBox1
      AnchorSideTop.Control = Edit1
      AnchorSideTop.Side = asrBottom
      BorderSpacing.Around = 6
      Caption = 'Label2'
    end
    object ComboBox1: TComboBox
      AnchorSideLeft.Control = Label2
      AnchorSideTop.Control = Label2
      AnchorSideTop.Side = asrBottom
      AnchorSideRight.Control = GroupBox1
      AnchorSideRight.Side = asrBottom
      Anchors = [akTop, akLeft, akRight]
      BorderSpacing.Right = 6
      TabOrder = 1
      Text = 'ComboBox1'
    end
    object CheckBox1: TCheckBox
      AnchorSideLeft.Control = GroupBox1
      AnchorSideTop.Control = ComboBox1
      AnchorSideTop.Side = asrBottom
      BorderSpacing.Around = 6
      Caption = 'CheckBox1'
      TabOrder = 2
    end
    object Label3: TLabel
      AnchorSideLeft.Control = GroupBox1
      AnchorSideTop.Control = Edit2
      AnchorSideTop.Side = asrCenter
      BorderSpacing.Around = 6
      Caption = 'Label3'
    end
    object Edit2: TEdit
      AnchorSideLeft.Control = Label3
      AnchorSideLeft.Side = asrBottom
      AnchorSideTop.Control = CheckBox1
      AnchorSideTop.Side = asrBottom
      BorderSpacing.Around = 6
      TabOrder = 3
      Text = 'Edit2'
    end
  end

边界空格和对齐

边界空格与对齐一起使用。在下面的示例中,这里有一个Memo1带有Align=alTop,和一个Memo2带有Align=alCLient。

  • 正常情况下,两个Memo将填充整个组框。
  • 但是Memo1有BorderSpacing.Around=10, 所以在Memo1四周有10 pixel空间。
  • Memo2有BorderSpacing.Top=20。在Memo1和Memo2之间的空间是最大的,是来自Memo2的2pixel空间。
  • Memo2也有BorderSpacing.Right=50,所以在Memo2右侧有50pixel空间。
  • 组框可以通过ChildSizing.LeftRightSpacing/VerticalSpacing /HorizontalSpacing为其所有的子控件定义默认空间。在这个示例中,它没有定义(全部是0)。

Borderspacing align1.png

对齐

Align属性的工作与Delphi非常相似,并且可用于快速填充一块区域。例如,来使一个TListBox填充 它的父类控件的整个区域,设置ListBox1.Align=alClient。如果有可能的话,align值 alTop, alBottom, alLeft 和 alRight 将不部分重叠地放置控件。这意味着,如果有足够的空间,所有的控件都将以alLeft,alTop,alBottom,alRight对齐而不会重叠。

运算法则的工作如下:

  • 首先,所有使用alTop的控件被放到客户端区域的顶部。如果这里有使用alTop的控件,最后添加/移除 的的控件将被放到最上面。运算法则将尝试避免重叠和保持控件的高度,直到扩展宽度到最大值。忽略左侧,顶部和右 侧的锚定到侧。底部锚定到侧工作正常。Borderspacing和Parent.ChildSizing空间 被考虑,因此你可以定义每个对齐控件周围的空间。
  • 然后,所有使用slBottom的控件被放置到客户端区域的底部。除此以外,它的工作方式类似于alTop。
  • 然后,所有使用alLeft的控件被放置到在alTop和alBottom控件之间的客户端区域的左侧。除此以外,它的工作方式类似于alTop。
  • 然后,所有使用alRight的控件被放置到在alTop和alBottom控件之间的客户端区域的右侧。除此以外,它的工作方式类似于alTop。
  • 如果这里有一个使用alClient的控件,它将填充剩余的客户端。
  • 如果这里有多个使用alClient的控件,它们将被放置到相同的位置,相互重叠。使用 Visibility属性来定义显示哪一个。

Autosize align.png

对齐和边界空格

边界距离和父级控件的子控件大小的空间被应用于对齐控件。下面的memo有Align=alClient。

Autosize align borderspacing.png

对齐和锚

一个对齐控件的自由侧(例如,Align=alLeft的右侧)遵循锚定规则。如果未设置锚定,那么控件将保持它的宽度。如果设置锚定,那么宽度将更改。

对齐和自动大小(AutoSize)

一个使用alLeft或alRight对齐的控件垂直扩展,将使用它的设计宽度。如果AutoSize被 设置为true,那么Width将是首先宽度。下面的按钮有Align=alRightAutoSize=true

Autosize align autosize.png

对齐和父级控件自动大小(AutoSize)

一个使用alClient对齐的控件将填充剩余的空间。一个使用AutoSize=true 的父类控件将扩展或收缩到精确的围住它的子控件。如果你组合两种属性将会发生什么?假设你放置一个使用Align=alClient的按钮到一个 AutoSize=true的组框中?

Autosize nested1.png

LCL使 用按钮的首先大小,相应地扩展或收缩组框:

Autosize nested2.png

alCustom(自定 义)

这个对齐值存在于自定义自动大小(AutoSize)算法中,并且由LCL处理,差不多很像alNone。使用alCustom的控件不被LCL移动,而是由你的自定义控件移动。你必需重写(override) CalculatePreferredSize和DoAutoSize。

使用相同对齐方式控件的顺序

使用相同对齐方式控件按下面顺序放置。关于alLeft,使用最低的Left的控件获取,对于alTop,最低的 Top,对于alRight,最大的Left+Width,对于alBottom,最大的Top+Height。如 果两个控件有相同的坐标,最后移动的一个(调用SetBounds或SetParent)取胜。控件可以重写 (override ) CreateControlAlignList来更改顺序,或者重写(override) DoAlignChildControls来处理整个对齐方式。

注意:在0.9.29之前,在同一坐标下的控件按Z顺序排列。在一些情况下,这导致了重新排序和不得不修复。这里 有一个不兼容的地方:如果你添加一些使用alTop或alLeft的控件,而没有具体指明限制范围,控件将被放置在 0,0处,因此最后添加的控件取胜,在这里前面的第一个控件取胜。

Delphi用户注意: Delphi用户注意:与VCL相反,LCL可以并且经常延迟重新计算布局。Delphi在每次属性更改后都会进 行布局。在VCL中设置Align=alTop调用具体指明的AlignControls,在其中控件有一个具体指 明的意义。这就是为什么在VCL中设置属性的顺序很重要的原因。LCL布局算法与设置属性的时间顺序无关。

例如,在FormCreate中的所有更改都会被延迟,并且布局是根据最终的属性值计算的。在另一方 面,ButtonClick没有延迟(也就是说,你必需自己添加Disable/EnableAutoSize), 因此插入alTop控件可能有不同的结果。

如果你想要一个具体指定的顺序,使用Disable/EnableAutoSize,并设置"Top"属性。例 如,调用TForm.OnCreate时禁用AutoSize,所以你可以使用:

procedure TForm1.FormCreate(Sender: TObject);
begin
  // making sure Panel2 is above Panel1, even though Panel1 is set first:
  Panel1.Align:=alTop;
  Panel1.Top:=1;
  Panel2.Align:=alTop;
  Panel2.Top:=0;
end;

布局

行,列和线

你可以使用ChildSizing属性来对齐行和列中的子控件,例如:

  • ChildSizing.Layout=cclLeftToRightThenTopToBottom
  • ChildSizing.ControlsPerLine=3
  • AutoSize=false(组框不能重新调整大小来适应子控件)

Autosize layout lefttoright.png

Layout(布局)属性默认是cclNone。如果你设置Layout为另一个值,每一个有正常锚定的控件都将被对齐。正常锚定意味着:

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

cclLeftToRightThenTopToBottom将在顶部,左侧放置第一个子项,在其右侧放置第二个子项,以此类推。这是一条线。属性ControlsPerLine定义何时开启一行新行。在上面的示例中,每一条线(行)有3个控件。这里有12个控件,所以这里有4条线(行),每 行有3个控件(列)。如果ControlsPerLine是0,意味着每行控件数量不受限制 - 这里将仅有带有全部子控件的一行。

你可以看到这些行有不同的大小,每一行都有最大控件的大小,并且这些被重新调整大小到列宽。在行之间没有空格。在 图像中的空格来自使用的主题,而不是来自LCL

固定行和列之间的空间

你可以在这些属性之间添加空格:

  • ChildSizing.VerticalSpacing - 行间距
  • ChildSizing.HorizontalSpacing - 列间距
  • ChildSizing.LeftRightSpacing - 所有列的左右间距
  • ChildSizing.TopBottomSpacing - 所有列的上下间距

上面的示例使用 ChildSizing.VerticalSpacing=6, ChildSizing.HorizontalSpacing=15, ChildSizing.LeftRightSpacing=30, ChildSizing.TopBottomSpacing=10, AutoSize=true

Autosize layout parentspacing.png

另外,你可以使用BorderSpacing属性为每个控件设置单独的空间。

放大

上面的示例重新调整组框的大小到需要的空间。如果你的组框有一个固定的大小,或者如果它不能自由地重新调整大小, 例如,如果组框应该填充整个窗体的宽度,那么子控件应该放大。这里有一些模式。默认模式ChildSizing.EnlargeHorizontal=crsAnchorAligning是不能放大任何东西的。在右边的空间将不被使用。

  • crsAnchorAligning - 不使用额外的空间
  • crsScaleChilds - 使用相同的因数成倍增加宽度/高度
  • crsHomogeneousChildResize - 向每个宽度/高度添加相同的量
  • crsHomogeneousSpaceResize - 在子控件之间每个空间中添加相同的量

crsScaleChilds(成倍增加)

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

Autosize layout scalechilds.png

例如,如果客户端宽度(ClientWidth)是所需宽度的两倍,那么每个子控件的宽度都将是所需宽度的两倍。

crsHomogeneousChildResize(单倍增加)

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

Autosize layout homogeneouschildresize.png

例如,如果客户端宽度(ClientWidth)比所需要宽度大30像素,那么每个子控件都将宽10像素。

crsHomogeneousSpaceResize(定量增加)

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

Autosize layout homogeneousspaceresize.png

例如,如果客户端宽度(ClientWidth)比所需要宽度大40 pixel,在左,右和每个子控件之间都将有10 pixel空间。

收缩

收缩工作类似于放大。如果没有足够的空间供控件使用,你可以设置不同的模式。ShrinkHorizontal(水平收缩), ShrinkVertical(垂直收缩)

单个单元格(Individual cells)

在上面的示例中,所有的控件都被同样地重新调整大小,每个控件都填充整个单元格(cell)单元格(cell)是在具体的行和列中的空间。通常一个控件会填充整个单元格空间。可以使用属性BorderSpacing.CellAlignHorizontalBorderSpacing.CellAlignVertical更改。

例如,设置Fifth按钮的BorderSpacing.CellAlignHorizontalcaCenter,你将得到这样:

Autosize layout cellhorizontal cacenter.png

CellAlignHorizontal/CellAlignVertical有四个可能的值:

  • caFill:子控件填充单元格的整个宽度(高度)
  • caCenter:子控件在单元格中使用它的首选宽度(高度)并将居中
  • caLeftTop:子控件在单元格中使用它的首选宽度(高度)并将左对齐
  • caRightBottom:子控件在单元格中使用它的首选宽度(高度)并将右对齐

Autosize layout cellalign.png

使用 OnResize / OnChangeBounds 自定义布局

有时,LCL布局是不够用的。下面的示例显示一个GroupBox1带有一个ListBox1和一个Memo1。ListBox1应该填充1/3的空间,Memo1占 据剩余的空间。

Autosize onresize.png

无论何时重新调整GroupBox1大小,ListBox1.Width都应该是1/3。为达到这个目的,设置 GroupBox1的OnResize事件为:

procedure TForm1.GroupBox1Resize(Sender: TObject);
begin
  ListBox1.Width := GroupBox1.ClientWidth div 3;
end;

常见错误:OnResize事件错误

不要将你所有的调整大小代码放入到窗体的OnResize事件中。窗体OnResize事件仅当窗 体被重新调整大小时被调用。子控件(例如,GroupBox1)将会稍后重新调整大小,因此GroupBox1.ClientWidth在FormResize事件期间仍然有旧值。你必需使用GroupBox1.OnResize事件来响应GroupBox1.ClientWidth的更改。

常见错误:Width(宽度)代替ClientWidth(客户端宽度), AdjustClientRect(调整客户端矩形)

Width(宽度)是包含框架在内的尺寸大小。ClientWidth是不包含框架的内部宽度。有一些框架(像 TPanel)会绘制一个附加的框架。那么你必需使用AdjustClientRect。相同的示例,但是在一个 Panel1中是一个ListBox1,而不是一个GroupBox1:

procedure TForm1.Panel1Resize(Sender: TObject);
var 
  r: TRect;
begin
  r := Panel1.ClientRect;
  Panel1.AdjustClientRect(r);
  ListBox1.Width := (r.Right - r.Left) div 3;
end;

自定义控件

当你写你自己的控件时,你可以重写(override)和微调LCL自动大小的很多部分。

SetBounds, ChangeBounds, DoSetBounds

当属性 Left, Top, Width, Height, BoundsRect被设置时,或者用户直接调用时,SetBounds将被调用。 SetBounds更新BaseBounds和BaseParentClientSize,这就用于锚定以保持距离。例如,加载一个带有TMemo的窗 体,并且lfm包含TMemo的Left和Width,接下来TMemo将会调用两次SetBounds。当用户最 大化一个窗口时,SetBounds是被窗体调用的,而不是被TMemo调用的,以保持TMemo的 BaseBounds。如果TMemo被锚定到右侧,那么TMemo的宽度基于BaseBounds和 BaseParentClientSize更改。记住,给定的aLeft, aTop, aWidth, aHeight可能是无效的,并且在应用前通过LCL更改。Delphi更经常的调用SetBounds。SetBounds使用KeepBase=false调用ChangeBounds。

每当通过属性或LCL的布局器设置控件的位置或大小时,都会调用ChangeBounds。SetBounds 使用KeepBase=false内部调用ChangeBounds , 而LCL布局器使用KeepBase=true来调用它。重新这些代码可能会更改首先大小或重新调整其它控件的大小。记住,给定的aLeft, aTop, aWidth, aHeight可能是无效的,并在应用前通过LCL更 改。你可以调用这个函数。

DoSetBounds是一个低级函数,用以设置私有变量FLeft, FTop, FWidth, FHeight。不要调用这个函数,仅LCL调用它。它也相应地更新FClientWidth和FClientHeight。重写这个函数以更新控件的内容布局, 例如滚动条。一如往常:在这里不绘制,但会调用Invalidate并在OnPaint或重新绘制(override Paint)中绘制。

DoAdjustClientRectChange通过LCLLCL调用,当ClientRect更改时,宽度和高度保持不变。 WMSize是为实现Delphi/VCL的兼容性而存在的。通过LCL interface调用,并在每次边界更改时更改。

AdjustClientRect(调整客户端矩形)

方法 AdjustClientRect 可以被你自定义的控件所重写(override),并且影响 Align, ChildSizing.Layout 和AnchorSides。它不影响Left, Top的意思,也不影响正常的锚定(例如设置)。

当你想绘制你自己的框架时,那么子控件应该在这些框架中对齐。例如,TPanel绘制一个框架,并通过重写方法AdjustClientRect来减少客户端区域:

  TCustomPanel = class(TCustomControl)
  ...
  protected
    ...
    procedure AdjustClientRect(var aRect: TRect); override;
  ...

procedure TCustomPanel.AdjustClientRect(var aRect: TRect);
var
  BevelSize: Integer;
begin
  inherited AdjustClientRect(aRect);

  BevelSize := BorderWidth;
  if (BevelOuter <> bvNone) then
    inc(BevelSize, BevelWidth);
  if (BevelInner <> bvNone) then
    inc(BevelSize, BevelWidth);

  InflateRect(aRect, -BevelSize, -BevelSize);
end;

AdjustClientRect(调整客户端矩形)和Align(对齐)

AdjustClientRect可以用于减少所有自动调整大小操作所使用的客户端区域。例如TPanel使用 AdjustClientRect来减少客户端区域的borderwidth(边界宽度):

Adjustclientrect align.png

在截图中的Button1是使用Align=alClient创建的。ChildSizing.Layout也会受到影响。

当通过AnchorSides(锚定侧)锚定到一个父类对象时,AdjustClientRect也会被使用:

  Button1.AnchorParallel(akTop,0,Button1.Parent);

Button1的顶部被锚定到父类客户端区域的顶部。如果AdjustClientRect添加3到顶部,那么Button1.Top将会是3 (3加上0)。

Own AutoSize

AutoSize被设置为true时,控件应该被重新调整为首选的大小,如果有可能的话。

首选大小

新的大小经由LCL通过'GetPreferredSize获取,它调用CalculatePreferredSize, 它可以被重写(overridden)。例如,让我们写一个TQuadrat, 它是一个TShape,但是它的高度应该等于它的宽度:

  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;

方法CalculatePreferredSize获取两个变量参数:PreferredWidthPreferredHeight 。它们默认为0,这意味着:这里没有首选大小,因此LCL将 不会更改大小。上面的函数设置PreferredHeight为当前的Width。布尔参数WithThemeSpace已经被弃用,并且始终是false。

重要: CalculatePreferredSize禁止更改一个控件的边界或任意其它值,因为 这样做将会创建一个循环。

计算PreferredWidth(首选宽度)/Height(高度)可能会代价很高。因此LCL会缓存结果,直到控件调用 InvalidatePreferredSize 。在我们的示例中,PreferredHeight(首选高度)依赖于Width,因此当Width更改时,我们必需使其无效:

  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;
  // Note: The AdjustSize can be omitted here, because the [[LCL]] does that after calling DoSetBounds.
end;

当边界更改时,LCL将自动地触发自动调整大小,因此示例是完整。

CalculatePreferredSize的默认T实施询问widget集,可能会返回 一个首选的宽度和/或高度。每一个控件都可以重写 (override)CalculatePreferredSize方法。例如,TImage将重写(override)它,并将返回图像的大小。如果没有可用的首选宽度(高度),返回值是0,LCL将保持当前是宽度(高度)。如果0是你的控件的有效大小,你必须设置ControlStyle标志 csAutoSize0x0 (ControlStyle:=ControlStyle+[csAutoSize0x0];)。LCL 控件面板是一个示例。

AdjustSize(调整大小)

当首选大小依赖于一个新的属性时,那么每次属性更改时,自动大小都必须被触发。例如:

procedure TQuadrat.SetSubTitle(const AValue: string);
begin
  if FSubTitle = AValue then exit;
  FSubTitle := AValue;
  InvalidatePreferredSize;
  AdjustSize;
end;

使用禁用自动大小(DisableAutoSizing),启用自动大小(EnableAutoSizing)来减少资源管理开销(overhead)

自从Lazarus 0.9.29 以来,lazarus有了一个新的自动调整大小的算法,它减少了资源管理开销并运行深度嵌套依赖关系。一直到Lazarus 0.9.28,DisableAlign/EnableAlign和Disable /EnableAutoSizing仅对一个控件和它的直系子控件有效。

你每次更改一个属性时,LCL都会触发一次布局的重新计算:

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

重新计算将会触发事件和发生信息。为减少资源管理开销,你可以暂停(suspend)自动大小:

DisableAutoSizing;
try
  Label1.Caption := 'A';  // no recompute
  Label2.Caption := 'B';  // no recompute
finally
  EnableAutoSizing; // one recompute
end;

Y你必需平衡调用禁用/启用自动大小(Disable/EnableAutoSizing)。仅在一次相关的 (早期) 禁用自动大小(DisableAutoSizing)后调用启用自动大小(EnableAutoSizing) 时,才会启动启用自动大小(EnableAutoSizing)

自0.9.29以来,禁用/启用自动大小Disable/EnableAutoSizing)适用于整个窗体。这意味着每一次调用DisableAutoSizing都会终止这个窗体上的所有控件自动调整大小。如果你编写你自己的控件,你现在可以使用下面的:

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

这基本上意味着:你不必在意。只需要调用Disable/EnableAutoSizing.

注意:这是错误的:

Button1.DisableAutoSizing;
Label1.EnableAutoSizing; // wrong: every control has its own autosizing reference counter

禁用自动大小(DisableAutoSizing)和窗体边界

例如在Linux系统中,在异步窗口管理器下,DisableAutoSizing有另外一个有用的效果。每次重新调整窗体大小或者移动边界都会发送非widget集(DoSendBoundsToInterface)。即使窗体未显示,处理程序也会重新调整大小。窗口管理器常常仅将这些边界作为建议处理。窗口管理器有它自己的逻辑,并且通常仅使用发送的第一步的边界。第二步、第三步或更进一步的移动将会被忽略。使用DisableAutoSizing,你可以确保将最终边界发送到widget集,从而使窗体边界更可靠。

示例:一个按钮面板

这个示例结合一些LCL布局机制来创建一个包含三个按钮的面板:一个Help按钮到左侧,OkCancel按钮到右侧。我们希望面板在窗体的底部填充整个宽度。按钮和面板都会自动调整大小来适合使用的字体和主题。

步骤 1: 创建面板并设置它的Align属性为alBottom。添加三个TBitBtns。

Autosize example buttonpanel1.png

设置BitBtns的Kind属性来显示符号(glyphs)。你可能需要设置GlyphShowMode为gsmAlways来在你的平台上查看它们。设置BitBtn的AutoSize属性为True,这将收缩/放大按钮以完美地适合符号(glyphs)和文本。取决于你的主题和平台,你可能会注意到按钮有不同的高度。

Autosize example buttonpanel2.png

设置help按钮的Align属性为alLeft,并设置其它两个按钮的Align属性为alRight 。这将垂直放大按钮并将移动它们到最左侧/右侧。alLeft/alRight对宽度没有影响,因此按钮使用它们的首选宽度。

Autosize example buttonpanel3.png

面板高度仍然是不变的。现在设置面板的AutoSize属性为True。面板现在垂直收缩来适合最高的按钮。

Autosize example buttonpanel4.png

Ok按钮有一个简短的标题(caption),因此按钮是非常短的。设置按钮的Constraints.MinWidth为75。按钮现在将稍微放大一些。

Autosize example buttonpanel5.png

现在在按钮的周围添加一些空格。设置面板的ChildSizing.LeftTopSpacing/RightBottomSpacing/HorizontalSpacing6.

Autosize example buttonpanel6.png

最后清除面板的标题(Caption),并设置它的BevelOuterbvNone

Autosize example buttonpanel7.png

滚动(Scrolling)

一些LCL控件(像TScrollBox, TForm,和TFrame)显示滚动条,如果其子控件太大,而无法适应滚动框,窗体或框架的话。它们从其祖先TScrollingWinControl继承这种行为。

一个滚动控件的逻辑客户区域(logical client area)可以大于可见客户区域(visible client area)可见客户区域(visible client area)ClientRect它总是从0,0开始,并且它的宽度和高度是内部区域。例如,在一个TGroupBox中,在框架内部中大小。因此下面总是true:

ClientWidth <= Width
ClientHeight <= Height

逻辑客户区域(logical client area)是通过方法GetLogicalClientRect.定义的。默认情况下,它与ClientRect相同。当一个子控件锚定到右侧时,它使用逻辑客户区域(logical client area)TScrollingWinControl 重写这个方法并返回滚动条的范围(Range),如果它们大于ClientRect的话。 范围(Range)可以使用AutoScroll=true来手动设置或自动设置。一个关于AutoScroll=true的示例:

Autoscroll all fit1.png

上面的按钮有一个固定是宽度200。下面的按钮被锚定到面板的右侧。因此,子控件有一个首先的宽度200。因为面 板是大于200的,逻辑客户区域(logical client area)也是更大的,因此下面的按钮扩展。

现在面板是收缩的,因此ClientWidth变得小于200

Autoscroll not fit1.png

首先的宽度仍然是200,因此逻辑客户区域(logical client area)现在是200,并大于可见客户区域(visible client area)。下面的按钮现在有一个200的宽度,面板显示一个水平滚动条。

滚动(Scroll)位置

更改一个滚动条的位置并不会方法任何子控件的左侧或顶部,也不会更改逻辑客户区域(logical client area),也不会影响自动大小。子控件仅会模拟移动。

滚动(Scrolling)和自动大小(AutoSize)

当AutoSize=true时,LCL将扩展控件以容纳其所有的子控件,并且不需要滚动条。如果控件不能被扩展,那么(仅)执行 AutoSize的第二个动作:移动子控件。

停靠(Docking)

停靠(Docking)使用这页中描述的方法和属性,查看 停靠.

分隔符

查看 分隔符.

TLabel.WordWrap

TLabel.WordWrap更改标签(label)的首选大小的行为。WordWrap=true 要求标签(label)的宽度是固定的,例如,通过锚定标签(label)的左、右侧的大小。标签(label)的 首选高度将通过打断标题(Caption)成多行的方式来重新计算。

DPI自动调整和绝对布局的自动调整

在历史上,LCL主要用于设计绝对布局,尽管Lazarus为灵活布局提供大量的选项,像:对齐(Align),锚定(Anchors),等等,像在这篇文章的其它部分所 描述的那样。除此之外,在历史上,它忽略了目标的DPI,而使用像素值来测量控件的左侧(left),顶部(top),宽度(width)和高度(height)属性。对于桌面平台和Windows CE来说,这已经工作地相当不错了,但是随着LCL支持Android的到来,这已经不能被忽视。从Lazarus 0.9.31开始,LCL可以将LCL的绝对布局以像素的形式重新解释为一个灵活的网格。这里有多种模式可供 选择,并且它们将允许重新解释像素值来作为绝对值,或者作为DPI调整,或者只是作为窗体尺寸的一部分考虑。

值得注意的是,这些新的规则仅影响没有对齐(Align)位置并且仅使用最标准锚定(Anchor)的控件.

在这种情况下,这里的值仅针对DPI调整,这里有一个新的属 性:TCustomForm.DesignTimeDPI,这将存储被设计窗体系统的DPI值。当目标的DPI大于 设计时的DPI,定位的值将被扩大,否则将会被减少。桌面DPI的常见值是96,并且这是给定的默认值。

属性DesignTimeDPI:整数读取FDesignTimeDPI然后写入FDesignTimeDPI;

这种布局方法使用属性TApplication.LayoutAdjustmentPolicy进行控制调整

  TLayoutAdjustmentPolicy = (
    lapDefault,     // widget set dependent
    lapFixedLayout, // A fixed absolute layout in all platforms
    lapAutoAdjustWithoutHorizontalScrolling, // Smartphone platforms use this one,
                                             // the x axis is stretched to fill the screen and
                                             // the y is scaled to fit the DPI
    lapAutoAdjustForDPI // For desktops using High DPI, scale x and y to fit the DPI
  );

并且,在下面的TControl的新方法允许强制进行一个特定控件和其所有子控件的一次布局自动调整,如何控制TControl特定衍生物,对此这样回应:

TControl = class
public
...
    procedure AutoAdjustLayout(AMode: TLayoutAdjustmentPolicy;
      const AFromDPI, AToDPI, AOldFormWidth, ANewFormWidth: Integer); virtual;
    function ShouldAutoAdjustLeftAndTop: Boolean; virtual;
    function ShouldAutoAdjustWidthAndHeight: Boolean; virtual;

例如,当屏幕旋转时,LCL-CustomDrawn-Android将调用AutoAdjustLayout 。

更多细节

很多控件重写(override) TControl.DoAutoSize执行实际地自动大小。

重要: 很多Delphi控件重写(override)这个方法,很多控件在设置一些属性后会直接调用这个方法。

在处理程序(handle)创建过程中,并非所有的接口都可以创建完整的设备上下文,这些上下文需要计算一些像文 本大小的事情。

这就是为什么你应该总是调用AdjustSize而不是DoAutoSize的原因。

TControl.AdjustSize以一种智能流行的方式调用DoAutoSize

在加载和处理程序(handle)创建过程中,会延迟调用。

这种方法的最初的作用与TWinControl.DoAutoSize相同。但是,由于DoAutoSize 通常被衍生组件所重写,它并不是对执行所有的测试都有用,它会导致太多的资源开销。为减少这种情况,LCL调用AdjustSize来作为替换品。

当设置AutoSize = true时,LCL 在宽度和高度上自动大小控件。这是LCL最复杂的一部分,因为最终的结果取决于近一百个属性。让我们简单地开始:

LCL仅能自动调整宽度(Width) (高度(Height)),如果它可以自由重新调整大小的话。换句话说-宽度不会自动调整大小,如果:

  • 左侧和右侧是被锚定的。你可以使用Anchors属性锚定一侧,或者通过设置Align属性为alTop,alBottom或alClient。
  • 宽度(Width)Constraints(约束式)属性所约束。约束式也可以通过widget集重写。例如,winapi并不允许重新调整一个组合框的高度。并且gtk widget集也不允许重新调整一个垂直滚动条的宽度。

高度(Height)宽度(Width)相同。

新的大小是通过受保护(protected)的方法TControl.CalculatePreferredSize所计算得出来的。这个方法会要求widget集的一个适当的宽度和高度。例如,一个TButton有首先的宽度和高 度。一个TComboBox仅有一个首选的高度。首选宽度被作为0返回,因此LCL并不会自动大小宽度- 它保持宽度不变。一个TMemo没有首先的宽度或高度。因此,自动大小对一个TMemo没有影响。

一些控件重写(override)这个方法。例如,TGraphicControlyan衍生物,像TLabel没有窗口处理程序,因此不能询问其widget。它们必需计算它们自己的首选宽度和高度。

widget集必需为每个具有首选大小(宽度或高度或两者都有)的widget类重写(override)方法GetPreferredSize。

Parent.AutoSize(父类.自动大小)

上面描述了简单的解释。真正的算法提供了很多的可能性因而变得更复杂。

Properties / Methods(属性/方法)

  • Left
  • Top

如果Parent<>nil,那么Left, Top是到父类客户端区域(非滚动的)的左侧像素,右侧像素的像素距离。记住客户端区域总是不包含父类的框 架和滚动条的。对于 Delphi用户来说:一些VCL控件,像TGroupbox,定义客户端区域为整个控件,包含框架,而另外一些控件则不是这样-LCL相比Delphi来说更加一致,因此其与Delphi不兼容。Left和Top可以是负数或者是大于客户端区域的数。一些widget集会定义一个最小值/最大值 ,在一些地方约有10.000或更多。

当客户端区域是滚动的时,Left和Top将保持不变。

在重新调整大小/移动期间,Left和Top并不总是与处理程序对的坐标同步。

如果Parent=nil ,那么Left, Top取决于widget集和窗口管理器。直到Lazarus 0.9.25 ,这通常是窗体的客户端区域的left,top的屏幕坐标。这是与Delphi不兼容的。计划将其更改为窗口的Left, Top 。


提示: 你每次更改Left和Top时,LCL会立即移动并重新计算整个布局。如果你想更改Left和Top,请改用:

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

以像素形式的大小禁止为负数,并且大多数widget集不允许Width=0和/或Height=0 。在一些平台上的一些控件以Constraints.MinInterfaceWidth/Height的形式定义 一个较大的最小值约束。而不是重新大小一个控件为Width=0和/或Height=0 ,设置Visible=false或Parent=nil。在重新调整大小/移动期间,Width和Height并不总是与处理程序对的坐标同步。


  • BoundsRect

等同于Bounds(Left, Top, Width, Height).

新手常见错误:

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

这没有效果, 因为读取BoundsRect的是一个函数。它在堆上创建一个临时的TRect 。上述等同于:

var
  r: TRect;
begin
  r := BoundsRect; // fetch the bounds
  r.Left := 3;  // change a value on the stack
end;  // no change
  • ClientRect

Left和Top总是0,0 。宽度和高度是客户端区域的可见像素大小。记住客户端区域不包含框架和滚动条。在一个可滚动的客户端区域中,逻辑客户端区域可能大于可见的客户端区域。

  • ClientOrigin

返回客户端区域的左侧,顶部坐标0,0的屏幕坐标。注意,这个值是存储在接口(interface)中的位置。并不总是与LCL同步。当移动一个控件时,LCL设置边界到所需位置,并发送一条移动信息到接口(interface)。这取决于接口(interface)立即处理移动或排队处理移动。

  • LCLIntf.GetClientBounds

返回一个控件的客户端边界。像ClientRect,但是Left和Top是到控件左侧,顶部的像素距离。例如在 一个TGroupBox上,Left, Top是左侧和右侧框架边界的宽度和高度。滚动在GetClientBounds上没有影响。

  • LCLIntf.GetWindowRect

在调用后,ARect将是屏幕坐标中控件区域。这意味着,Left和Top将是处理程序对象的左侧和顶部像素的屏 幕坐标,Right和Bottom将是右侧和底部像素的屏幕坐标。

  • FBaseBoundsLock: integer

通过LockBaseBounds/UnlockBaseBounds来增加/减小。在SetBounds调用期 间,用于保持FBaseBounds。

  • FBaseParentClientSize: TPoint

Parent.ClientRect大小对FBaseBounds有效。FBaseBounds和 FBaseParentClientSize通常用于计算akRight (akBottom)的距离。当重新调整父类大小时,LCL知 道要保持什么距离。


  • FBoundsRectForNewParent: TRect

当更改一个控件的父类时,会重新创建处理重新并可能会发送很多事情。尤其是停靠窗体过程是太不可靠了。因此将会保 存BoundsRect。VCL使用一种类似的机制。


  • fLastAlignedBounds: TRect

查看TControl.SetAlignedBounds说明解释。简而言之:它停止在接口(interface)和LCL自动大小之间的一些循环。

  • FLastChangebounds: TRect

用于停止调用具有相同坐标的ChangeBounds。这种情况经常发生。

  • FLastDoChangeBounds: TRect

用于避免调用具有相同坐标的OnChangeBounds。这将减少用户定义的自动大小。


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

用于避免调用具有相同坐标的OnResize。这将减少用户定义的自动大小。

  • FLoadedClientSize: TPoint

在加载期间,很多东西被迟延,很多东西被设置。并且最糟糕的是:以一种错误的顺序。这就是为什么在再次加载时,存 储和设置SetClientWidth/SetClientHeight调用的原因。使用这种方法,LCL可以恢复设计期间使用的距离(例如,akRight)。

  • FReadBounds: TRect

类似于FLoadedClientSize,但是适用于 SetLeft,SetTop,SetWidth,SetHeight。

  • procedure SetBoundsRectForNewParent(const AValue: TRect);

用于设置FBoundsRectForNewParent。查看上文。

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

像SetBounds,但是不更改默认大小。

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

一个SetBounds的智能版本, r在创建和加载过程中减少资源开销(overhead)。

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

提交当前的边界为基础边界。

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

也是用于Delphi兼容。重新调整控件的大小,来获取想要的ClientRect大小。

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

这是内部的SetBounds。应用于约束式,更新基础边界,调用OnChangeBound, OnResize,锁定边界。

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

这个实际上设置FLeft, FTop, FWidth, FHeight私有(private)变量。

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

这是很多Delphi控件的标准的过程(procedure)重写(overriden)方法。 TWinControl也会重写它。

    • 当边界被锁定时忽略调用
    • 锁定FBoundsRealized来避免在自动调整大小期间的资源开销。

ChangeBounds未锁定这种方法。


  • Function GetClientOrigin: TPoint; virtual;

客户端区域的Left, Top的屏幕左侧。

  • Function GetClientRect: TRect; virtual;

客户端区域的大小。(总是Left=0, Top=0)。

  • Function GetScrolledClientRect: TRect; virtual;

在ClientRect中的可见客户端区域。

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

返回相对于控件的左侧,顶部的客户端矩形。如果Scrolled是true,矩形将通过当前滚动值移动(查看示例 TScrollingWincontrol)。

  • function GetClientScrollOffset: TPoint; virtual;

返回客户端区域的滚动偏移量。

  • function GetControlOrigin: TPoint; virtual;

返回控件区域的左侧,顶部坐标0,0的屏幕坐标。(在屏幕上的控件的左侧,顶部像素)。注意,这个值是存储在接口 (interface)中的位置。并不总是与LCL同步。当移动一个控件时,LCL设置边界到所需位置,并发送一条移动信息到接口(interface)。这屈居于接口(interface)立即处理移动或排队处理移动。

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?

See AutoSize and Forms

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:

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;

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 destroy the ability to change properties independently.

Resizing stringgrid columns in form's OnResize event does not work

The Form's OnResize is triggered when the Form's Width, Height, ClientWidth or ClientHeight changes. This is per se independent of TStringGrid. Of course it often happens that both the Form and the TStringGrid are resized. This means using forms OnResize will often work, but not always. A prominent example where it always fails is when the theme is changed and you have a TStringGrid in a TGroupBox in a TForm. When the theme changed the form size is kept, so no Forms OnResize is triggered. But the TGroupBox changed, so the TStringGrid should be resized.

Solution: Use the TStringGrid's OnResize.

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.

There is a new compiler define LCLRealFormBounds In Lazarus trunk 1.7 that enables real size for a form. To use it just compile your application with LCLRealFormBounds ON. For now, only win32 widget set is supported.

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

I get an infinite loop / How to debug autosizing?

Here are some notes, what other users made wrong and how to find out:

Differences to Delphi

For Delphi users: Please read: Lazarus_vs_Delphi

Overriding a LCL method and triggering a recompute

You override a method and tell the LCL to compute again, even if nothing changed. Check for AdjustSize, Realign, AlignControls, InvalidatePreferredSize. For example:

procedure TMainForm.SetBounds(ALeft, ATop, AWidth, AHeight: integer);
begin
  // This will create an endless loop
  InvalidatePreferredSize;

  // This will create an endless loop too:
  OtherComponent.Left:=10;
  OtherComponent.Left:=20;

  inherited SetBounds(ALeft, ATop, AWidth, AHeight);
end;

Explanation: The SetBounds method is called often, even without changing anything. For example a "Left:=30;" will do so.

Remedy: Check for changes:

procedure TMainForm.SetBounds(ALeft, ATop, AWidth, AHeight: integer);
begin
  if (Left <> ALeft) or (Top <> ATop) or (Width <> AWidth) or (Height <> AHeight) then
    InvalidatePreferredSize;
  inherited SetBounds(ALeft, ATop, AWidth, AHeight);
end;

TWinControl.AlignControls

procedure AlignControls(aControl: TControl; var aRect: TRect); override;

AlignControls moves and sizes all child controls. The LCL implementation ignores controls with Align=alCustom.

The parameter aControl: TControl is kept for VCL compatibility. The LCL always passes nil. It gives aControl precedence when applying the Align property. When you have for example two controls A,B with Align=alLeft then the one with the lower Left is put left most. If both have the same Left then creation order wins. Now imagine you want to switch both controls A,B in the designer. You drag B to the left. This results in setting B.Left to 0. Now AlignControls starts and find both A.Left=0 and B.Left=0. Normally A would win. To let B win the VCL calls AlignControls(B,r). So aControl is the last moved. Contrary to the VCL the LCL allows to combine multiple layout changes without recomputing on every step. The LCL keeps track of the last moved controls in TWinControl.fAlignControls and applies the order in TWinControl.CreateControlAlignList. The aControl parameter is always nil.

See TWinControl.CreateControlAlignList.

OnResize/OnChangeBounds conflict with LCL properties

You set bounds that bite the LCL properties. For example a TLabel.AutoSize is true by default. If you set the Label1.Width in an OnResize event, the LCL will recompute, resize the Label1 and call the OnResize again. Start your application in the debugger and reproduce the bug. When it enters the loop, pause the application and see the call stack. If you see one of your events or your methods start searching there. For example:

procedure TMainForm.FormResize(Sender: TObject);
begin
  // if Button1 is anchored or AutoSize=true then the following might create an endless loop:
  Button1.Width:=100;
end;

LCL interface bug, custom widget

Sometimes the LCL interface or your custom control has a bug and undoes the LCL bounds. Compile the LCL with -dVerboseIntfSizing. This will write the communication between LCL and the LCL interface. If a widget does not allow free resizing, it must tell the LCL via the Constraints. Search for SetInterfaceConstraints in the various LCL interfaces and TWSControlClass.ConstraintWidth/Height.

alCustom

You use alCustom. This was only half implemented in earlier Lazarus versions, but some clever programmers used it to create some special effects. Now it is implemented and your program does not work anymore. Please see here what alCustom does: alCustom.

See also

贡献者和更改

  • 简体中文版本由 robsean 于 2020-02-18 创建。