Sign in Join
Wiki copytemporary

Autosize / Layout/zh CN: what changed

Revision 1 to 2. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
11# 概述
22
33[[LCL]]可以自动修改一个控件的大小和位置,以便它适应字体,主题和文本或者其它内容的更改。如果你想在一些平台上运行你的 程序,或者你的标题可以使用一些语言,你的控件需要适应它们的当前环境。[[LCL]]不仅允许你做一次快速设计(使用鼠标在一个窗体上到处移动控件) ,并且后期设置一些将使控件随后自动地适应更改控件的关键属性等等。
44
55- 固定设计:这是在设计器中放置一个控件的默认设置。控件的位置相对其父类控件是固定的。控件的大小和位置(左,顶)可由程序员完全调整。你可以使用鼠标四处移动控件,和自由重新调整其大小。
66- 对齐:对齐的控件将填充剩余父类控件空间顶部、底部、左侧和右侧,或者填充整个剩余空间。
77- 锚:你可以锚定一个控件的边侧(左,顶,右,底)到它的父类组件或到另一个组件。锚定的意思:[[LCL]]将尝试维持与锚定点相同的距离。
88- 布局:控件可以在行和列中自动地对齐 (例如, TRadioGroup)
99- 通过OnResize自定义:你可以在代码中使用OnResize和OnChangeBounds事件来对齐你自己的控件。
1010- 自定义控件:写你自己的控件,你可以重写(override)几乎任何你想的[[LCL]]行为。
1111
1212## 优先级规则
1313
14141. 约束式(Constraints)
15152. 对齐(Align)
16163. 锚(Anchors)
17174. 子控件大小.布局(ChildSizing.Layout)
18185. 自动大小(AutoSize)
19196. OnResize, OnChangeBounds - 但是,如果你设置与上述规则冲突的边界,你将创建一个死循环。
2020
2121## 公共属性
2222
2323可以更改一些重要的属性来配置自动大小:
2424
2525- Left, Top, Width, Height(左,顶,宽,高)
2626- 自动大小(AutoSize):autosize通知[[LCL]]自动地重新调整一个控件的宽度和高度。
2727- 锚(Anchors):让你创建依赖关系/附属关系,例如,来锚一个ComboBox到一个Label的右侧。
2828- 对齐(Align)
2929- 约束式(Constraints):让你为宽度和高度设置一个最小值和最大值。
3030- 边界空格(BorderSpacing): 让你设置锚定控件之间的空格。
3131- ChildSizing:让你设置子控件的布局和空格。
3232
3333内部算法在这里解释:[[LCL自动大小(AutoSizing)]]。
3434
3535# 固定设计
3636
3737固定设计是默认的。控件的属性设置为\[akLeft,akTop\],这意味着左侧和顶部的值没有被[[LCL]]更改。如果AutoSize的值是false,[[LCL]]不改变宽度或高度。如果AutoSize的值是true,宽度 和高度将被更改以适应内容。例如,TLabel.AutoSize默认值为true,因此将更改它的标题来将重新调整 label 大小以容纳更改的表达方式。TButton.AutoSize默认值是false,因此更改一个按钮的标题不会重新调整按钮大小。当你设置Button.AutoSize为true时,每次更改按钮的标题或它的字体(或主体),按钮将收缩或放大。注意,这些更改不总是立即发生的。例如,在FormCreate期间,所有的自动大小是挂起的。无论何时,你都可以更改"Left", "Top", "Width",或"Height"属性。
3838
3939# 自动大小(AutoSize)
4040
4141`AutoSize`是一个在很多类中可用找到的布尔值属性;它允许自动地调整一个控件的大小以适应顾及所含文本或图形的差异,也允许最有效地使用可用空间。这是创建跨平台窗体的关键机制。 通常地, AutoSize=true 对一个可视控件做两件事:
4242
4343- 如果有可能,它将重新调整控件的大小为首选大小。例如,一个TButton的宽度和高度被重新调整大小以适应标题,在一个TEdit仅在高度上重新调整大小的同时。一个TEdit的宽度不被自动大小。你可以更改一个TEdit的宽度。(查看GetPreferredSize)。
4444- 它以相同的数量移动固定位置的子控件,以便最左侧的子控件移动到 Left=0 (取决于边界空格(BorderSpacing)),最顶部的子控件移动到 Top=0 。
4545
4646## Lazarus vs Delphi
4747
4848- Delphi的自动大小(AutoSize)仅当某些属性更改时发生,例如,当一个TLabel的字体更改时。[[LCL]]的自动大小(AutoSize)总是激活的。Delphi允许更改一个有AutoSize=true的Lable的大小, 而Lazarus控件不允许。
4949- Delphi的隐藏的控件不会被自动大小。
5050- 使用Delphi,在加载期间,更改一个控件的大小不会重新调整大小/移动锚定子控件,但是一个构造函数通常被用来制作预期效果。当使用akRight,akBottom锚时,设置AnchorSides(锚定边)和BorderSpacing(边界空格)以保持距离正确。
5151
5252## 自动大小(AutoSize)和重新调整大小控件
5353
5454使用AutoSize=false,按钮将给予一个默认的固定大小。
5555
56-<figure>
57-<img src="Autosize1.png" title="Autosize1.png" />
58-<figcaption>Autosize1.png</figcaption>
59-</figure>
56+![](/wiki-images/1/1f/Autosize1.png)
6057
6158当为每个按钮设置 AutoSize=true 时,按钮将放大(或收缩)以适应文本和主题框架。
6259
63-<figure>
64-<img src="Autosize_on.png" title="Autosize_on.png" />
65-<figcaption>Autosize_on.png</figcaption>
66-</figure>
60+![](/wiki-images/7/75/Autosize_on.png)
6761
6862正如你在OK按钮中所见,AutoSize不会收缩到最小可能的尺寸大小。它使用控件的 GetPreferredSize方法,该方法调用CalculatePreferredSize方法。默认的TWinControl实施询问 widget集合,这可能有一个首选的宽度或高度。每个控件都可以重写(override) CalculatePreferredSize 方法。例如,TImage 重写(override)它,并返回图片的大小。如果没有可用的首选的宽度(高度),返回的值是0,并且[[LCL]]将保持宽度(高度) (除非ControlStyle设置标示csAutoSize0x0,当前仅TPanel使用)。
6963
7064一个TWinControl计算其所有子控件的大小,并使用它来计算首选大小。
7165
7266当一个控件被锚定到左侧和右侧,并且它的 宽度 是固定的。例如,使用 Align=alTop ,控件被锚定到左侧和右侧,并且沿袭父类控件的宽度。如果 Parent.AutoSize 为 true,那么 Parent 将使用控件的首选宽度来计算它自身的首选宽度,以此方式,控件将重新调整大小为它的首选宽度。查看 [[Autosize / Layout/zh CN|对齐和自动大小(AutoSize]]. 如果没有 可用的首选宽度,那么最后设置的边界将被使用(BaseBounds或ReadBounds)。 如果没有边界被设置,GetControlClassDefaultSize方法将被使用。Same for对于高度和锚定到顶部和底部是一样的。
7367
7468约束式总是被应用,并且有优先权。
7569
7670## 自动大小 (AutoSize)和移动子控件
7771
7872当 AutoSize=false 时,你可以自由地放置和移动控件:
7973
80-<figure>
81-<img src="Autosize_on.png" title="Autosize_on.png" />
82-<figcaption>Autosize_on.png</figcaption>
83-</figure>
74+![](/wiki-images/7/75/Autosize_on.png)
8475
8576当 AutoSize=true 时,有固定位置的子控件将移动以适应位置。
8677
87-<figure>
88-<img src="Autosize_panel_on.png" title="Autosize_panel_on.png" />
89-<figcaption>Autosize_panel_on.png</figcaption>
90-</figure>
78+![](/wiki-images/d/d9/Autosize_panel_on.png)
9179
9280在面板上是所有按钮都将通过相同的数量来移动到顶部和左侧,以便左侧和顶部没有剩余的空间。如果按钮可能有 BorderSpacing\>0 或 Panel.ChildSizing.LeftRightSpacing\>0 ,按钮可能被移动,以便使用定 义的空格量
9381
9482仅具有下面值的子控件将被移动:
9583
9684- Anchors=\[akLeft,akRight\]
9785- AnchorSide\[akLeft\].Control=nil
9886- AnchorSide\[akTop\].Control=nil
9987- Align=alNone
10088
10189子控件的移动取决于属性[[Autosize / Layout|ChildSizing.Layout]]。 该布局被应用于方法 TWinControl.AlignControls ,这将完全重写或部分重写 (overridden)例如,TToolBar 重写 (overridden) ControlsAligned 到所有使用 Align=alCustom 的控件的位置,并定义一个流布局,在流布局中不适合的控件将被推放到后续的行中。
10290
10391控件可以通过设置ControlStyle标示csAutoSizeKeepChildLeft 和 csAutoSizeKeepChildTop (自0.9.29版本可用)来禁用子控件的移动。
10492
10593## 自动 大小(AutoSize)和窗体
10694
10795没有父类控件的窗体将被窗口管理器控制,因而不能自由地放置和重新调整大小。设置大小仅是一个建议,并且可能会被 窗口管理器忽略。例如,你可以设置一个窗体的宽度为1000, 而 widget集可能会重新调整大小为800作为回应。如果你在OnResize中设置宽度,你可能会创建一个死循环。这就是为什么当widget集重新设 置窗体大小时,[[LCL]] TForm 禁用自动大小(AutoSize)的原因。
10896
10997这意味着当用户重新调整窗体大小或假设窗口管理器不喜欢边界时,窗体的自动大小(AutoSize)将停止。例 如,一些Linux窗口管理器有参照关联的其它窗口重新调整该窗体的磁性边功能。
11098
11199### 强制一个窗体的自动大小
112100
113101你可以启动/执行一次新的自动大小(AutoSize),通过这样做:
114102
115103``` pascal
116104Form1.AutoSize := False;
117105Form1.AutoSize := True;
118106```
119107
120108### 预先计算一个自动大小的窗体的大小
121109
122110当放置一个自动大小的窗体时,你可能需要在显示该窗体前知道它的大小。自动大小需要句柄(handle)。你可以 在显示一个窗体前计算其大小,使用:
123111
124112``` pascal
125113Form1.HandleNeeded;
126114Form1.GetPreferredSize(PreferredWidth,PreferredHeight);
127115```
128116
129117**注意**: 窗口管理器和窗体事件可能会更改窗体大小。首先大小不包含窗体的窗口边界。这是一个计划中的功能。
130118
131119# 锚定边
132120
133121控件有四个边: akLeft, akTop, akRight 和 akBottom。每个边都能被锚定到父类控件或另一个同类控件(具有相同父类控件的一个控件)的边。锚意味着**保 存距离**。默认是 Anchors=\[akLeft,akTop\] 。垂直锚与水平锚互不干涉。像 Align 和 Parent.AutoSize 之类的一些的属性有较高的优先级,并可以更改行为。
134122
135123## 锚定到父类控件或锚定到空值
136124
137125锚定到空值(默认)与锚定到父类控件有几乎相同的效果。都尝试与父类控件客户端区域的一边保持距离。锚定到空值使 用最后的SetBounds调用距离,然而锚定到父类控件使用BorderSpacing值。
138126
139127这里有akLeft,akRight (akTop,akBottom)的四种组合:
140128
141129- **有akLeft,没有akRight**:控件的**左边**是固定的,不由 [[LCL]]更改。右边没有锚定,因此它跟随左边。这意味着,控件的**长度**也是保存不变的
142130- **有akLeft,有akRight**: 控件的**左边**是固定的。不由[[LCL]]更改。右边被锚定到父类控件的右边。这意味着,如果父类控件被放大 100pixel ,那么控件的**宽度**也被放大 100pixel 。
143131- **有akRight,没有akLeft**:控件的左边不是固定的,因此它将跟随它的右边。右边是 锚定的。这意味着,如果父类控件被放大 100pixel ,那么控件也将被向右移动 100pixel 。
144132- **没有akLeft.没有akRight**:两边都没有锚定。控件的中心的位置与父类控件一起缩 放。例如,如果控件在父类控件的中间,并且父类控件被放大 100 pixel ,那么控件将被向右移动 50pixel 。
145133
146134### 更改一个锚定的控件的父类控件的大小
147135
148136当更改一个父类控件的大小时,除非自动大小是禁用的,否则所有锚定的子控件也将被连立即移动和/或重新调整大小。 例如,在一个窗体的加载期间和在一个窗体的创建期间,自动大小是被禁用的。
149137
150138一个组框带有一个按钮: ![](/wiki-images/6/60/Anchors1.png)
151139
152140当组框被放大时,被锚定边的距离将保持不变:
153141
154142使用akLeft,不使用akRight: ![](/wiki-images/0/05/Anchors_akLeft.png)
155143
156144使用akLeft和akRight: ![](/wiki-images/6/66/Anchors_akLeft_akRight.png)
157145
158146使用akRight,不使用akLeft: ![](/wiki-images/d/d3/Anchors_akRight_no_akLeft.png)
159147
160148三个按钮在一个组框中: ![](/wiki-images/4/48/Anchors_no_akLeft_no_akRight_small.png)
161149
162150不使用akLeft和akRight,它们的中心被缩放: ![](/wiki-images/7/7b/Anchors_no_akLeft_no_akRight_big.png)
163151
164152**注释:**
165153
166154- 加载一个窗体像设置一大组边界。在加载期间,使用lfm的值设置属性。记住,因为原型和控件,这里可能有多 个lfm文件。在加载[[LCL]]启用自动大小后。锚定的控件使用在加载结束时边界。在中间的一些 步骤被忽略。
167155- 对于自定义控件,通常最好设置锚定侧而不是仅锚定。
168156
169157### 更改一个锚定控件的大小
170158
171159当更改一个锚定控件的宽度时,例如,通过对象查看器或通过代码 *Button1.Width:=3*, 你可以看到锚定到*父类控件* 和锚定到*空值*的不同点。锚定到父类控件将重新调整大小,并将移动Button1,然而锚定到空值将仅重新调整大小。例如:
172160
173161#### 锚定到空值
174162
175163![](/wiki-images/b/bd/Anchored_right_nil_resize1.png) 一个Button1锚定\[akTop,akRight\], AnchorSide\[akRight\].Control=nil
176164
177165![](/wiki-images/a/a0/Anchored_right_nil_resize2.png) 设置宽度到一个较小的值将收缩按钮,保持按钮的*左边*, 距离,减少按钮右边的距离。
178166
179167![](/wiki-images/1/16/Anchored_right_nil_resize3.png) 当组框被重新设置大小时,按钮也将保持新的距离。
180168
181169**img12:**设置**宽度**相当于调用SetBounds(SetBounds(**左侧**,顶部,**新宽度**,高度)。这就是为什么*左侧*距离被保持。这是与Delphi兼容的。
182170
183171#### 锚定到父类控件
184172
185173![](/wiki-images/4/45/Anchored_right_parent_resize1.png) 一个Button1锚定\[akTop,akRight\],AnchorSide\[akRight\].Control=Button1.Parent
186174
187175![](/wiki-images/0/04/Anchored_right_parent_resize2.png) 设置宽度到一个较小的值将收缩按钮,保持按钮的*右边*距离,减少按钮左边的距离。
188176
189177## 锚定到同级控件
190178
191179你可以锚定到相邻的控件,下面的示例显示:
192180
193181- 你可以锚定一个labe的左侧到一个按钮的左侧
194182- 锚定一个 label 的顶部到一个按钮的底部
195183- 锚定一个 label 的中心到一个按钮的中心
196184
197-<figure>
198-<img src="Anchorsides_example1.png" title="Anchorsides_example1.png" />
199-<figcaption>Anchorsides_example1.png</figcaption>
200-</figure>
185+![](/wiki-images/e/ee/Anchorsides_example1.png)
201186
202-<figure>
203-<img src="Anchorside_example2.png" title="Anchorside_example2.png" />
204-<figcaption>Anchorside_example2.png</figcaption>
205-</figure>
187+![](/wiki-images/b/ba/Anchorside_example2.png)
206188
207189更多详细信息和如何设置锚,查看: [[锚定边]]。
208190
209-<figure>
210-<img src="Anchoreditor2.png" title="Anchoreditor2.png" />
211-<figcaption>Anchoreditor2.png</figcaption>
212-</figure>
191+![](/wiki-images/4/4d/Anchoreditor2.png)
213192
214193## 边界空格
215194
216195边界空格(BorderSpacing)属性控制一个控件周围的最小空格量。属性是:
217196
218197- **Around(四周)**:这个量以像素为单位被添加到 左侧,顶部,右侧,底部。
219198- **Left(左侧)**:空格以像素为单位在控件的左侧
220199- **Top(顶部)**:空格以像素为单位在控件的顶部
221200- **Right(右侧)**:空格以像素为单位在控件的右侧l
222201- **Bottom(底部)**:空格以像素为单位在控件的底部
223202- **InnerBorder(内部边界)**: 这个量以像素为单位被两次添加到首先的宽度和高度。一些控件重写(override)计算并忽略这个属性。 TButton在这里就是一个工作的示例。使用InnerBorder,你可能使一个按钮比需要的大。
224203- **CellAlignHorizontal(单元格水平对齐)**:这被使用在表格中,像ChildSizing.Layout=cclLeftToRightThenTopToBottom。如果控件小于表格单元格,这 个属性定义控件对齐的位置:到左侧的ccaLeftTop,到右侧的ccaRightBottom,或子中间的 ccaCenter。
225204- **CellAlignVertical(单元格垂直对齐)**:类似于CellAlignHorizontal,但是由于垂直对齐。
226205
227206### 边界空格规则
228207
229208- Around被添加到左侧,顶部,右侧,底部边界空格
230209- 如果控件有不允许扩展的限制,则空格可能更大。
231210
232211#### 锚定到另一侧
233212
234213例如,A的右侧到B的左侧。
235214
236215A和B两者的边界空格都使用。
237216
238217- 在两控件(在父类控件上的LeftControl, RightControl)之间的最大水平空格是
239218 - LeftControl.BorderSpacing.Right + LeftControl.BorderSpacing.Around
240219 - RightControl.BorderSpacing.Left + RightControl.BorderSpacing.Around
241220 - Parent.ChildSizing.HorizontalSpacing
242221- 垂直空格工作类似:在两控件(在父类控件上的TopControl, BottomControl)之间的最大空格是
243222 - TopControl.BorderSpacing.Bottom + TopControl.BorderSpacing.Around
244223 - BottomControl.BorderSpacing.Top + BottomControl.BorderSpacing.Around
245224 - Parent.ChildSizing.VerticalSpacing
246225
247226例如,如果LeftControl.BorderSpacing.Right = 3 和 LeftControl.BorderSpacing.Around = 4,那么在两控件之间至少有7 pixel空格。如果RightControl.BorderSpacing.Left = 4 和 RightControl.BorderSpacing.Around = 4 ,那么空格将至少8。如果Parent.ChildSizing.HorizontalSpacing = 10,那么空格将至少是10。
248227
249228#### 锚定到同一侧
250229
251230例如,A的右侧到B的右侧。
252231
253232- 仅使用A的边界空格。
254233- Parent.ChildSizing.Horizontal/VerticalSpacing未被使用。
255234
256235#### 锚定到中心
257236
258237例如,A的中心到B的中心。
259238
260239未使用边界空格,未使用Parent.ChildSizing.Horizontal/VerticalSpacing 。
261240
262241#### 示例
263242
264243一个常见示例是锚定一个Label到一个Edit。
265244
266-<figure>
267-<img src="BorderSpacing_Anchors.png" title="BorderSpacing_Anchors.png" />
268-<figcaption>BorderSpacing_Anchors.png</figcaption>
269-</figure>
245+![](/wiki-images/1/1e/BorderSpacing_Anchors.png)
270246
271247Label中心被垂直锚定到Edit。Edit的左边被锚定到Label的右边。两者都有 BorderSpacing.Around=6。这在Lable和Edit中间产生6个像素空格,Label的左侧 6个像素空格,Edit的右侧6个像素空格。也在Edit的上面和下面有6个像素空格。
272248
273249- 在一个控件和它的父类控件(Label1在GroupBox1上)的左侧空格最大值
274250 - Label1.BorderSpacing.Left + Label1.BorderSpacing.Around
275251 - GroupBox1.ChildSizing.LeftTopSpacing
276252
277253- 在一个控件和它的父类控件(Edit1在GroupBox1上)的右侧空格最大值
278254 - Edit1.BorderSpacing.Right + Edit1.BorderSpacing.Around
279255 - GroupBox1.ChildSizing.RightBottomSpacing
280256
281257- 当一个控件的中心被锚定到另一个控件时,例如,上述的Label于Edit1垂直居中,那么所有空格都被忽略。
282258
283259- 当一个控件的左侧被锚定到另一个控件的左侧时(例如,它们使用左侧对齐),那么两者左侧之间的距离是
284260 - Control1.BorderSpacing.Left + Control1.BorderSpacing.Around
285261
286262这里有一个更复杂的示例:
287263
288-<figure>
289-<img src="Borderspacing_anchors2.png" title="Borderspacing_anchors2.png" />
290-<figcaption>Borderspacing_anchors2.png</figcaption>
291-</figure>
264+![](/wiki-images/9/9c/Borderspacing_anchors2.png)
292265
293266lfm代码的重要部分:
294267
295268``` pascal
296269 object GroupBox1: TGroupBox
297270 AutoSize = True
298271 Caption = 'GroupBox1'
299272 TabOrder = 0
300273 object Label1: TLabel
301274 AnchorSideLeft.Control = GroupBox1
302275 AnchorSideTop.Control = Edit1
303276 AnchorSideTop.Side = asrCenter
304277 BorderSpacing.Around = 6
305278 Caption = 'Label1'
306279 end
307280 object Edit1: TEdit
308281 AnchorSideLeft.Control = Label1
309282 AnchorSideLeft.Side = asrBottom
310283 AnchorSideTop.Control = GroupBox1
311284 AnchorSideRight.Control = GroupBox1
312285 AnchorSideRight.Side = asrBottom
313286 Anchors = [akTop, akLeft, akRight]
314287 BorderSpacing.Around = 6
315288 TabOrder = 0
316289 Text = 'Edit1'
317290 end
318291 object Label2: TLabel
319292 AnchorSideLeft.Control = GroupBox1
320293 AnchorSideTop.Control = Edit1
321294 AnchorSideTop.Side = asrBottom
322295 BorderSpacing.Around = 6
323296 Caption = 'Label2'
324297 end
325298 object ComboBox1: TComboBox
326299 AnchorSideLeft.Control = Label2
327300 AnchorSideTop.Control = Label2
328301 AnchorSideTop.Side = asrBottom
329302 AnchorSideRight.Control = GroupBox1
330303 AnchorSideRight.Side = asrBottom
331304 Anchors = [akTop, akLeft, akRight]
332305 BorderSpacing.Right = 6
333306 TabOrder = 1
334307 Text = 'ComboBox1'
335308 end
336309 object CheckBox1: TCheckBox
337310 AnchorSideLeft.Control = GroupBox1
338311 AnchorSideTop.Control = ComboBox1
339312 AnchorSideTop.Side = asrBottom
340313 BorderSpacing.Around = 6
341314 Caption = 'CheckBox1'
342315 TabOrder = 2
343316 end
344317 object Label3: TLabel
345318 AnchorSideLeft.Control = GroupBox1
346319 AnchorSideTop.Control = Edit2
347320 AnchorSideTop.Side = asrCenter
348321 BorderSpacing.Around = 6
349322 Caption = 'Label3'
350323 end
351324 object Edit2: TEdit
352325 AnchorSideLeft.Control = Label3
353326 AnchorSideLeft.Side = asrBottom
354327 AnchorSideTop.Control = CheckBox1
355328 AnchorSideTop.Side = asrBottom
356329 BorderSpacing.Around = 6
357330 TabOrder = 3
358331 Text = 'Edit2'
359332 end
360333 end
361334```
362335
363336### 边界空格和对齐
364337
365338边界空格与对齐一起使用。在下面的示例中,这里有一个Memo1带有Align=alTop,和一个Memo2带有Align=alCLient。
366339
367340- 正常情况下,两个Memo将填充整个组框。
368341- 但是Memo1有BorderSpacing.Around=10, 所以在Memo1四周有10 pixel空间。
369342- Memo2有BorderSpacing.Top=20。在Memo1和Memo2之间的空间是最大的,是来自Memo2的2pixel空间。
370343- Memo2也有BorderSpacing.Right=50,所以在Memo2右侧有50pixel空间。
371344- 组框可以通过ChildSizing.LeftRightSpacing/VerticalSpacing /HorizontalSpacing为其所有的子控件定义默认空间。在这个示例中,它没有定义(全部是0)。
372345
373-<figure>
374-<img src="Borderspacing_align1.png" title="Borderspacing_align1.png" />
375-<figcaption>Borderspacing_align1.png</figcaption>
376-</figure>
346+![](/wiki-images/1/13/Borderspacing_align1.png)
377347
378348# 对齐
379349
380350Align属性的工作与Delphi非常相似,并且可用于快速填充一块区域。例如,来使一个TListBox填充 它的父类控件的整个区域,设置ListBox1.Align=alClient。如果有可能的话,align值 alTop, alBottom, alLeft 和 alRight 将不部分重叠地放置控件。这意味着,如果有足够的空间,所有的控件都将以alLeft,alTop,alBottom,alRight对齐而不会重叠。
381351
382352运算法则的工作如下:
383353
384354- 首先,所有使用alTop的控件被放到客户端区域的顶部。如果这里有使用alTop的控件,最后添加/移除 的的控件将被放到最上面。运算法则将尝试避免重叠和保持控件的高度,直到扩展宽度到最大值。忽略左侧,顶部和右 侧的锚定到侧。底部锚定到侧工作正常。Borderspacing和Parent.ChildSizing空间 被考虑,因此你可以定义每个对齐控件周围的空间。
385355- 然后,所有使用slBottom的控件被放置到客户端区域的底部。除此以外,它的工作方式类似于alTop。
386356- 然后,所有使用alLeft的控件被放置到在alTop和alBottom控件之间的客户端区域的左侧。除此以外,它的工作方式类似于alTop。
387357- 然后,所有使用alRight的控件被放置到在alTop和alBottom控件之间的客户端区域的右侧。除此以外,它的工作方式类似于alTop。
388358- 如果这里有一个使用alClient的控件,它将填充剩余的客户端。
389359- 如果这里有多个使用alClient的控件,它们将被放置到相同的位置,相互重叠。使用 Visibility属性来定义显示哪一个。
390360
391-<figure>
392-<img src="Autosize_align.png" title="Autosize_align.png" />
393-<figcaption>Autosize_align.png</figcaption>
394-</figure>
361+![](/wiki-images/e/e6/Autosize_align.png)
395362
396363## 对齐和边界空格
397364
398365边界距离和父级控件的子控件大小的空间被应用于对齐控件。下面的memo有Align=alClient。
399366
400-<figure>
401-<img src="Autosize_align_borderspacing.png" title="Autosize_align_borderspacing.png" />
402-<figcaption>Autosize_align_borderspacing.png</figcaption>
403-</figure>
367+![](/wiki-images/b/b4/Autosize_align_borderspacing.png)
404368
405369## 对齐和锚
406370
407371一个对齐控件的自由侧(例如,**Align=alLeft**的右侧)遵循锚定规则。如果未设置锚定,那么控件将保持它的**宽度**。如果设置锚定,那么宽度将更改。
408372
409373## 对齐和自动大小(AutoSize)
410374
411375一个使用alLeft或alRight对齐的控件垂直扩展,将使用它的设计宽度。如果*AutoSize*被 设置为*true*,那么*Width*将是首先宽度。下面的按钮有*Align=alRight*和*AutoSize=true*。
412376
413-<figure>
414-<img src="Autosize_align_autosize.png" title="Autosize_align_autosize.png" />
415-<figcaption>Autosize_align_autosize.png</figcaption>
416-</figure>
377+![](/wiki-images/3/35/Autosize_align_autosize.png)
417378
418379## 对齐和父级控件自动大小(AutoSize)
419380
420381一个使用alClient对齐的控件将填充剩余的空间。一个使用*AutoSize=true* 的父类控件将扩展或收缩到精确的围住它的子控件。如果你组合两种属性将会发生什么?假设你放置一个使用Align=alClient的按钮到一个 AutoSize=true的组框中?
421382
422-<figure>
423-<img src="Autosize_nested1.png" title="Autosize_nested1.png" />
424-<figcaption>Autosize_nested1.png</figcaption>
425-</figure>
383+![](/wiki-images/b/b1/Autosize_nested1.png)
426384
427385[[LCL]]使 用按钮的*首先大小*,相应地扩展或收缩组框:
428386
429-<figure>
430-<img src="Autosize_nested2.png" title="Autosize_nested2.png" />
431-<figcaption>Autosize_nested2.png</figcaption>
432-</figure>
387+![](/wiki-images/2/28/Autosize_nested2.png)
433388
434389## alCustom(自定 义)
435390
436391这个对齐值存在于自定义自动大小(AutoSize)算法中,并且由[[LCL]]处理,差不多很像alNone。使用alCustom的控件不被[[LCL]]移动,而是由你的自定义控件移动。你必需重写(override) CalculatePreferredSize和DoAutoSize。
437392
438393## 使用相同对齐方式控件的顺序
439394
440395使用相同对齐方式控件按下面顺序放置。关于alLeft,使用最低的Left的控件获取,对于alTop,最低的 Top,对于alRight,最大的Left+Width,对于alBottom,最大的Top+Height。如 果两个控件有相同的坐标,最后移动的一个(调用SetBounds或SetParent)取胜。控件可以重写 (override ) CreateControlAlignList来更改顺序,或者重写(override) DoAlignChildControls来处理整个对齐方式。
441396
442397注意:在0.9.29之前,在同一坐标下的控件按Z顺序排列。在一些情况下,这导致了重新排序和不得不修复。这里 有一个不兼容的地方:如果你添加一些使用alTop或alLeft的控件,而没有具体指明限制范围,控件将被放置在 0,0处,因此最后添加的控件取胜,在这里前面的第一个控件取胜。
443398
444399Delphi用户注意: Delphi用户注意:与VCL相反,LCL可以并且经常延迟重新计算布局。Delphi在每次属性更改后都会进 行布局。在VCL中设置Align=alTop调用具体指明的AlignControls,在其中控件有一个具体指 明的意义。这就是为什么在VCL中设置属性的顺序很重要的原因。LCL布局算法与设置属性的时间顺序无关。
445400
446401例如,在FormCreate中的所有更改都会被延迟,并且布局是根据最终的属性值计算的。在另一方 面,ButtonClick没有延迟(也就是说,你必需自己添加Disable/EnableAutoSize), 因此插入alTop控件可能有不同的结果。
447402
448403如果你想要一个具体指定的顺序,使用Disable/EnableAutoSize,并设置"Top"属性。例 如,调用TForm.OnCreate时禁用AutoSize,所以你可以使用:
449404
450405``` pascal
451406procedure TForm1.FormCreate(Sender: TObject);
452407begin
453408 // making sure Panel2 is above Panel1, even though Panel1 is set first:
454409 Panel1.Align:=alTop;
455410 Panel1.Top:=1;
456411 Panel2.Align:=alTop;
457412 Panel2.Top:=0;
458413end;
459414```
460415
461416# 布局
462417
463418## 行,列和线
464419
465420你可以使用ChildSizing属性来对齐行和列中的子控件,例如:
466421
467422- ChildSizing.Layout=cclLeftToRightThenTopToBottom
468423- ChildSizing.ControlsPerLine=3
469424- AutoSize=false(组框不能重新调整大小来适应子控件)
470425
471-<figure>
472-<img src="Autosize_layout_lefttoright.png" title="Autosize_layout_lefttoright.png" />
473-<figcaption>Autosize_layout_lefttoright.png</figcaption>
474-</figure>
426+![](/wiki-images/1/10/Autosize_layout_lefttoright.png)
475427
476428**Layout(布局)**属性默认是cclNone。如果你设置**Layout**为另一个值,每一个有*正常*锚定的控件都将被对齐。*正常*锚定意味着:
477429
478430- Anchors=\[akLeft,akTop\]
479431- AnchorSide\[akLeft\].Control=nil
480432- AnchorSide\[akTop\].Control=nil
481433- Align=alNone
482434
483435值**cclLeftToRightThenTopToBottom**将在顶部,左侧放置第一个子项,在其右侧放置第二个子项,以此类推。这是一条**线**。属性**ControlsPerLine**定义何时开启一行新行。在上面的示例中,每一条线(行)有3个控件。这里有12个控件,所以这里有4条线(行),每 行有3个控件(列)。如果*ControlsPerLine*是0,意味着每行控件数量不受限制 - 这里将仅有带有全部子控件的一行。
484436
485437你可以看到这些行有不同的大小,每一行都有最大控件的大小,并且这些被重新调整大小到列宽。在行之间没有空格。在 图像中的空格来自使用的主题,而不是来自[[LCL]]。
486438
487439## 固定行和列之间的空间
488440
489441你可以在这些属性之间添加空格:
490442
491443- ChildSizing.VerticalSpacing - 行间距
492444- ChildSizing.HorizontalSpacing - 列间距
493445- ChildSizing.LeftRightSpacing - *所有*列的左右间距
494446- ChildSizing.TopBottomSpacing - *所有*列的上下间距
495447
496448上面的示例使用 ChildSizing.VerticalSpacing=6, ChildSizing.HorizontalSpacing=15, ChildSizing.LeftRightSpacing=30, ChildSizing.TopBottomSpacing=10, AutoSize=true
497449
498-<figure>
499-<img src="Autosize_layout_parentspacing.png" title="Autosize_layout_parentspacing.png" />
500-<figcaption>Autosize_layout_parentspacing.png</figcaption>
501-</figure>
450+![](/wiki-images/a/a9/Autosize_layout_parentspacing.png)
502451
503452另外,你可以使用BorderSpacing属性为每个控件设置单独的空间。
504453
505454## 放大
506455
507456上面的示例重新调整组框的大小到需要的空间。如果你的组框有一个固定的大小,或者如果它不能自由地重新调整大小, 例如,如果组框应该填充整个窗体的宽度,那么子控件应该放大。这里有一些模式。默认模式*ChildSizing.EnlargeHorizontal=crsAnchorAligning*是不能放大任何东西的。在右边的空间将不被使用。
508457
509458- crsAnchorAligning - 不使用额外的空间
510459- crsScaleChilds - 使用相同的因数成倍增加宽度/高度
511460- crsHomogeneousChildResize - 向每个宽度/高度添加相同的量
512461- crsHomogeneousSpaceResize - 在子控件之间每个空间中添加相同的量
513462
514463### crsScaleChilds(成倍增加)
515464
516465ChildSizing.EnlargeHorizontal=crsScaleChilds, ChildSizing.EnlargeVertical=crsScaleChilds, AutoSize=false
517466
518-<figure>
519-<img src="Autosize_layout_scalechilds.png" title="Autosize_layout_scalechilds.png" />
520-<figcaption>Autosize_layout_scalechilds.png</figcaption>
521-</figure>
467+![](/wiki-images/b/b0/Autosize_layout_scalechilds.png)
522468
523469例如,如果客户端宽度(ClientWidth)是所需宽度的两倍,那么每个子控件的宽度都将是所需宽度的两倍。
524470
525471### crsHomogeneousChildResize(单倍增加)
526472
527473ChildSizing.EnlargeHorizontal=crsHomogeneousChildResize, ChildSizing.EnlargeVertical=crsHomogeneousChildResize, AutoSize=false
528474
529-<figure>
530-<img src="Autosize_layout_homogeneouschildresize.png" title="Autosize_layout_homogeneouschildresize.png" />
531-<figcaption>Autosize_layout_homogeneouschildresize.png</figcaption>
532-</figure>
475+![](/wiki-images/3/39/Autosize_layout_homogeneouschildresize.png)
533476
534477例如,如果客户端宽度(ClientWidth)比所需要宽度大30像素,那么每个子控件都将宽10像素。
535478
536479### crsHomogeneousSpaceResize(定量增加)
537480
538481ChildSizing.EnlargeHorizontal=crsHomogeneousSpaceResize, ChildSizing.EnlargeVertical=crsHomogeneousSpaceResize, AutoSize=false
539482
540-<figure>
541-<img src="Autosize_layout_homogeneousspaceresize.png" title="Autosize_layout_homogeneousspaceresize.png" />
542-<figcaption>Autosize_layout_homogeneousspaceresize.png</figcaption>
543-</figure>
483+![](/wiki-images/9/96/Autosize_layout_homogeneousspaceresize.png)
544484
545485例如,如果客户端宽度(ClientWidth)比所需要宽度大40 pixel,在左,右和每个子控件之间都将有10 pixel空间。
546486
547487## 收缩
548488
549489收缩工作类似于放大。如果没有足够的空间供控件使用,你可以设置不同的模式。**ShrinkHorizontal(水平收缩)**, **ShrinkVertical(垂直收缩)**。
550490
551491## 单个单元格(Individual cells)
552492
553493在上面的示例中,所有的控件都被同样地重新调整大小,每个控件都填充整个*单元格(cell)*。*单元格(cell)*是在具体的行和列中的空间。通常一个控件会填充整个单元格空间。可以使用属性**BorderSpacing.CellAlignHorizontal** 和**BorderSpacing.CellAlignVertical**更改。
554494
555495例如,设置Fifth按钮的*BorderSpacing.CellAlignHorizontal*为**caCenter**,你将得到这样:
556496
557-<figure>
558-<img src="Autosize_layout_cellhorizontal_cacenter.png" title="Autosize_layout_cellhorizontal_cacenter.png" />
559-<figcaption>Autosize_layout_cellhorizontal_cacenter.png</figcaption>
560-</figure>
497+![](/wiki-images/d/d3/Autosize_layout_cellhorizontal_cacenter.png)
561498
562499CellAlignHorizontal/CellAlignVertical有四个可能的值:
563500
564501- caFill:子控件填充单元格的整个宽度(高度)
565502- caCenter:子控件在单元格中使用它的首选宽度(高度)并将居中
566503- caLeftTop:子控件在单元格中使用它的首选宽度(高度)并将左对齐
567504- caRightBottom:子控件在单元格中使用它的首选宽度(高度)并将右对齐
568505
569-<figure>
570-<img src="Autosize_layout_cellalign.png" title="Autosize_layout_cellalign.png" />
571-<figcaption>Autosize_layout_cellalign.png</figcaption>
572-</figure>
506+![](/wiki-images/c/c0/Autosize_layout_cellalign.png)
573507
574508# 使用 OnResize / OnChangeBounds 自定义布局
575509
576510有时,[[LCL]]布局是不够用的。下面的示例显示一个GroupBox1带有一个ListBox1和一个Memo1。ListBox1应该填充1/3的空间,Memo1占 据剩余的空间。
577511
578-<figure>
579-<img src="Autosize_onresize.png" title="Autosize_onresize.png" />
580-<figcaption>Autosize_onresize.png</figcaption>
581-</figure>
512+![](/wiki-images/4/4b/Autosize_onresize.png)
582513
583514无论何时重新调整GroupBox1大小,ListBox1.Width都应该是1/3。为达到这个目的,设置 GroupBox1的OnResize事件为:
584515
585516``` pascal
586517procedure TForm1.GroupBox1Resize(Sender: TObject);
587518begin
588519 ListBox1.Width := GroupBox1.ClientWidth div 3;
589520end;
590521```
591522
592523### 常见错误:OnResize事件错误
593524
594525不要将你*所有的*调整大小代码放入到窗体的OnResize事件中。窗体OnResize事件仅当窗 体被重新调整大小时被调用。子控件(例如,GroupBox1)将会稍后重新调整大小,因此GroupBox1.ClientWidth在FormResize事件期间仍然有旧值。你必需使用GroupBox1.OnResize事件来响应GroupBox1.ClientWidth的更改。
595526
596527### 常见错误:Width(宽度)代替ClientWidth(客户端宽度), AdjustClientRect(调整客户端矩形)
597528
598529Width(宽度)是包含框架在内的尺寸大小。ClientWidth是不包含框架的内部宽度。有一些框架(像 TPanel)会绘制一个附加的框架。那么你必需使用AdjustClientRect。相同的示例,但是在一个 Panel1中是一个ListBox1,而不是一个GroupBox1:
599530
600531``` pascal
601532procedure TForm1.Panel1Resize(Sender: TObject);
602533var
603534 r: TRect;
604535begin
605536 r := Panel1.ClientRect;
606537 Panel1.AdjustClientRect(r);
607538 ListBox1.Width := (r.Right - r.Left) div 3;
608539end;
609540```
610541
611542# 自定义控件
612543
613544当你写你自己的控件时,你可以重写(override)和微调[[LCL]]自动大小的很多部分。
614545
615546## SetBounds, ChangeBounds, DoSetBounds
616547
617548当属性 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。
618549
619550每当通过属性或[[LCL]]的布局器设置控件的位置或大小时,都会调用**ChangeBounds**。SetBounds 使用KeepBase=false内部调用ChangeBounds , 而[[LCL]]布局器使用KeepBase=true来调用它。重新这些代码可能会更改首先大小或重新调整其它控件的大小。记住,给定的aLeft, aTop, aWidth, aHeight可能是无效的,并在应用前通过[[LCL]]更 改。你可以调用这个函数。
620551
621552**DoSetBounds**是一个低级函数,用以设置私有变量FLeft, FTop, FWidth, FHeight。不要调用这个函数,仅[[LCL]]调用它。它也相应地更新FClientWidth和FClientHeight。重写这个函数以更新控件的内容布局, 例如滚动条。一如往常:在这里不绘制,但会调用Invalidate并在OnPaint或重新绘制(override Paint)中绘制。
622553
623554**DoAdjustClientRectChange**通过[[LCL]]或[[LCL]]调用,当ClientRect更改时,宽度和高度保持不变。 **WMSize**是为实现Delphi/VCL的兼容性而存在的。通过[[LCL]] interface调用,并在每次边界更改时更改。
624555
625556## AdjustClientRect(调整客户端矩形)
626557
627558方法 AdjustClientRect 可以被你自定义的控件所重写(override),并且影响 Align, ChildSizing.Layout 和AnchorSides。它不影响Left, Top的意思,也不影响正常的锚定(例如设置)。
628559
629560当你想绘制你自己的框架时,那么子控件应该在这些框架中对齐。例如,TPanel绘制一个框架,并通过重写方法**AdjustClientRect**来减少客户端区域:
630561
631562``` pascal
632563 TCustomPanel = class(TCustomControl)
633564 ...
634565 protected
635566 ...
636567 procedure AdjustClientRect(var aRect: TRect); override;
637568 ...
638569
639570procedure TCustomPanel.AdjustClientRect(var aRect: TRect);
640571var
641572 BevelSize: Integer;
642573begin
643574 inherited AdjustClientRect(aRect);
644575
645576 BevelSize := BorderWidth;
646577 if (BevelOuter <> bvNone) then
647578 inc(BevelSize, BevelWidth);
648579 if (BevelInner <> bvNone) then
649580 inc(BevelSize, BevelWidth);
650581
651582 InflateRect(aRect, -BevelSize, -BevelSize);
652583end;
653584```
654585
655586### AdjustClientRect(调整客户端矩形)和Align(对齐)
656587
657588AdjustClientRect可以用于减少所有自动调整大小操作所使用的客户端区域。例如TPanel使用 AdjustClientRect来减少客户端区域的borderwidth(边界宽度):
658589
659-<figure>
660-<img src="Adjustclientrect_align.png" title="Adjustclientrect_align.png" />
661-<figcaption>Adjustclientrect_align.png</figcaption>
662-</figure>
590+![](/wiki-images/c/ca/Adjustclientrect_align.png)
663591
664592在截图中的Button1是使用Align=alClient创建的。ChildSizing.Layout也会受到影响。
665593
666594当通过AnchorSides(锚定侧)锚定到一个父类对象时,AdjustClientRect也会被使用:
667595
668596``` pascal
669597 Button1.AnchorParallel(akTop,0,Button1.Parent);
670598```
671599
672600Button1的顶部被锚定到父类客户端区域的顶部。如果AdjustClientRect添加3到顶部,那么Button1.Top将会是3 (3加上0)。
673601
674602## Own AutoSize
675603
676604当*AutoSize*被设置为true时,控件应该被重新调整为首选的大小,如果有可能的话。
677605
678606### 首选大小
679607
680608新的大小经由[[LCL]]通过**GetPreferredSize''获取,它调用**CalculatePreferredSize**, 它可以被重写(overridden)。例如,让我们写一个**TQuadrat''', 它是一个TShape,但是它的高度应该等于它的宽度:
681609
682610``` pascal
683611 TQuadrat = class(TShape)
684612 protected
685613 procedure CalculatePreferredSize(var PreferredWidth,
686614 PreferredHeight: integer; WithThemeSpace: Boolean); override;
687615 end;
688616...
689617procedure TQuadrat.CalculatePreferredSize(var PreferredWidth,
690618 PreferredHeight: integer; WithThemeSpace: Boolean);
691619begin
692620 PreferredHeight:=Width;
693621end;
694622```
695623
696624方法CalculatePreferredSize获取两个变量参数:*PreferredWidth*和*PreferredHeight* 。它们默认为0,这意味着:这里没有首选大小,因此[[LCL]]将 不会更改大小。上面的函数设置*PreferredHeight*为当前的*Width*。布尔参数*WithThemeSpace*已经被弃用,并且始终是false。
697625
698626**重要**: CalculatePreferredSize禁止更改一个控件的边界或任意其它值,因为 这样做将会创建一个循环。
699627
700628计算*PreferredWidth(首选宽度)/Height(高度)*可能会代价很高。因此[[LCL]]会缓存结果,直到控件调用 *InvalidatePreferredSize* 。在我们的示例中,*PreferredHeight(首选高度)*依赖于*Width*,因此当*Width*更改时,我们必需使其无效:
701629
702630``` pascal
703631 TQuadrat = class(TShape)
704632 protected
705633 ...
706634 procedure DoSetBounds(ALeft, ATop, AWidth, AHeight: integer); override;
707635 end;
708636...
709637procedure TQuadrat.DoSetBounds(ALeft, ATop, AWidth, AHeight: integer);
710638begin
711639 inherited DoSetBounds(ALeft, ATop, AWidth, AHeight);
712640 InvalidatePreferredSize;
713641 // Note: The AdjustSize can be omitted here, because the [[LCL]] does that after calling DoSetBounds.
714642end;
715643```
716644
717645当边界更改时,[[LCL]]将自动地触发自动调整大小,因此示例是完整。
718646
719647CalculatePreferredSize的默认T实施询问widget集,可能会返回 一个首选的宽度和/或高度。每一个控件都可以重写 (override)CalculatePreferredSize方法。例如,TImage将重写(override)它,并将返回图像的大小。如果没有可用的首选宽度(高度),返回值是0,[[LCL]]将保持当前是宽度(高度)。如果0是你的控件的有效大小,你必须设置ControlStyle标志 csAutoSize0x0 (ControlStyle:=ControlStyle+\[csAutoSize0x0\];)。[[LCL]] 控件面板是一个示例。
720648
721649### AdjustSize(调整大小)
722650
723651当首选大小依赖于一个新的属性时,那么每次属性更改时,自动大小都必须被触发。例如:
724652
725653``` pascal
726654procedure TQuadrat.SetSubTitle(const AValue: string);
727655begin
728656 if FSubTitle = AValue then exit;
729657 FSubTitle := AValue;
730658 InvalidatePreferredSize;
731659 AdjustSize;
732660end;
733661```
734662
735663# 使用禁用自动大小(DisableAutoSizing),启用自动大小(EnableAutoSizing)来减少资源管理开销(overhead)
736664
737665自从Lazarus 0.9.29 以来,lazarus有了一个新的自动调整大小的算法,它减少了资源管理开销并运行深度嵌套依赖关系。一直到Lazarus 0.9.28,DisableAlign/EnableAlign和Disable /EnableAutoSizing仅对一个控件和它的直系子控件有效。
738666
739667你每次更改一个属性时,[[LCL]]都会触发一次布局的重新计算:
740668
741669``` pascal
742670Label1.Caption := 'A'; // first recompute
743671Label2.Caption := 'B'; // second recompute
744672```
745673
746674重新计算将会触发事件和发生信息。为减少资源管理开销,你可以暂停(suspend)自动大小:
747675
748676``` pascal
749677DisableAutoSizing;
750678try
751679 Label1.Caption := 'A'; // no recompute
752680 Label2.Caption := 'B'; // no recompute
753681finally
754682 EnableAutoSizing; // one recompute
755683end;
756684```
757685
758686Y你必需平衡调用禁用/启用自动大小(Disable/EnableAutoSizing)。仅在一次相关的 (早期) 禁用自动大小(DisableAutoSizing)后调用启用自动大小(EnableAutoSizing) 时,才会启动启用自动大小(EnableAutoSizing)
759687
760688自0.9.29以来,禁用/启用自动大小Disable/EnableAutoSizing)适用于整个窗体。这意味着每一次调用DisableAutoSizing都会终止这个窗体上的**所有**控件自动调整大小。如果你编写你自己的控件,你现在可以使用下面的:
761689
762690``` pascal
763691procedure TMyRadioGroup.DoSomething;
764692begin
765693 DisableAutoSizing; // disables not only TMyRadioGroup, but the whole form's autosizing
766694 try
767695 // delete items ...
768696 // add, reorder items ...
769697 // change item captions ...
770698 finally
771699 EnableAutoSizing; // recompute
772700 end;
773701end;
774702```
775703
776704这基本上意味着:你不必在意。只需要调用Disable/EnableAutoSizing.
777705
778706注意:这是错误的:
779707
780708``` pascal
781709Button1.DisableAutoSizing;
782710Label1.EnableAutoSizing; // wrong: every control has its own autosizing reference counter
783711```
784712
785713## 禁用自动大小(DisableAutoSizing)和窗体边界
786714
787715例如在Linux系统中,在异步窗口管理器下,DisableAutoSizing有另外一个有用的效果。每次重新调整窗体大小或者移动边界都会发送非widget集(DoSendBoundsToInterface)。即使窗体未显示,处理程序也会重新调整大小。窗口管理器常常仅将这些边界作为建议处理。窗口管理器有它自己的逻辑,并且通常仅使用发送的第一步的边界。第二步、第三步或更进一步的移动将会被忽略。使用DisableAutoSizing,你可以确保将最终边界发送到widget集,从而使窗体边界更可靠。
788716
789717# 示例:一个按钮面板
790718
791719这个示例结合一些[[LCL]]布局机制来创建一个包含三个按钮的面板:一个*Help*按钮到左侧,*Ok*和*Cancel*按钮到右侧。我们希望面板在窗体的底部填充整个宽度。按钮和面板都会自动调整大小来适合使用的字体和主题。
792720
793721步骤 1: 创建面板并设置它的Align属性为alBottom。添加三个TBitBtns。
794722
795-<figure>
796-<img src="Autosize_example_buttonpanel1.png" title="Autosize_example_buttonpanel1.png" />
797-<figcaption>Autosize_example_buttonpanel1.png</figcaption>
798-</figure>
723+![](/wiki-images/7/7e/Autosize_example_buttonpanel1.png)
799724
800725设置BitBtns的*Kind*属性来显示符号(glyphs)。你可能需要设置GlyphShowMode为gsmAlways来在你的平台上查看它们。设置BitBtn的*AutoSize*属性为*True*,这将收缩/放大按钮以完美地适合符号(glyphs)和文本。取决于你的主题和平台,你可能会注意到按钮有不同的高度。
801726
802-<figure>
803-<img src="Autosize_example_buttonpanel2.png" title="Autosize_example_buttonpanel2.png" />
804-<figcaption>Autosize_example_buttonpanel2.png</figcaption>
805-</figure>
727+![](/wiki-images/4/49/Autosize_example_buttonpanel2.png)
806728
807729设置help按钮的*Align*属性为*alLeft*,并设置其它两个按钮的*Align*属性为*alRight* 。这将垂直放大按钮并将移动它们到最左侧/右侧。alLeft/alRight对宽度没有影响,因此按钮使用它们的首选宽度。
808730
809-<figure>
810-<img src="Autosize_example_buttonpanel3.png" title="Autosize_example_buttonpanel3.png" />
811-<figcaption>Autosize_example_buttonpanel3.png</figcaption>
812-</figure>
731+![](/wiki-images/b/b6/Autosize_example_buttonpanel3.png)
813732
814733面板高度仍然是不变的。现在设置面板的*AutoSize*属性为*True*。面板现在垂直收缩来适合最高的按钮。
815734
816-<figure>
817-<img src="Autosize_example_buttonpanel4.png" title="Autosize_example_buttonpanel4.png" />
818-<figcaption>Autosize_example_buttonpanel4.png</figcaption>
819-</figure>
735+![](/wiki-images/3/36/Autosize_example_buttonpanel4.png)
820736
821737*Ok*按钮有一个简短的标题(caption),因此按钮是非常短的。设置按钮的*Constraints.MinWidth*为75。按钮现在将稍微放大一些。
822738
823-<figure>
824-<img src="Autosize_example_buttonpanel5.png" title="Autosize_example_buttonpanel5.png" />
825-<figcaption>Autosize_example_buttonpanel5.png</figcaption>
826-</figure>
739+![](/wiki-images/3/32/Autosize_example_buttonpanel5.png)
827740
828741现在在按钮的周围添加一些空格。设置面板的*ChildSizing.LeftTopSpacing/RightBottomSpacing/HorizontalSpacing*为*6*.
829742
830-<figure>
831-<img src="Autosize_example_buttonpanel6.png" title="Autosize_example_buttonpanel6.png" />
832-<figcaption>Autosize_example_buttonpanel6.png</figcaption>
833-</figure>
743+![](/wiki-images/4/46/Autosize_example_buttonpanel6.png)
834744
835745最后清除面板的*标题(Caption)*,并设置它的*BevelOuter*为*bvNone*。
836746
837-<figure>
838-<img src="Autosize_example_buttonpanel7.png" title="Autosize_example_buttonpanel7.png" />
839-<figcaption>Autosize_example_buttonpanel7.png</figcaption>
840-</figure>
747+![](/wiki-images/4/4e/Autosize_example_buttonpanel7.png)
841748
842749# 滚动(Scrolling)
843750
844751一些[[LCL]]控件(像*TScrollBox*, *TForm*,和*TFrame*)显示滚动条,如果其子控件太大,而无法适应滚动框,窗体或框架的话。它们从其祖先**TScrollingWinControl**继承这种行为。
845752
846753一个滚动控件的**逻辑客户区域(logical client area)**可以大于**可见客户区域(visible client area)**。*可见客户区域(visible client area)*是**ClientRect**它总是从0,0开始,并且它的宽度和高度是内部区域。例如,在一个TGroupBox中,在框架内部中大小。因此下面总是true:
847754
848755``` pascal
849756ClientWidth <= Width
850757ClientHeight <= Height
851758```
852759
853760**逻辑客户区域(logical client area)**是通过方法**GetLogicalClientRect**.定义的。默认情况下,它与*ClientRect*相同。当一个子控件锚定到右侧时,它使用*逻辑客户区域(logical client area)*。 *TScrollingWinControl* 重写这个方法并返回滚动条的*范围(Range)*,如果它们大于*ClientRect*的话。 **范围(Range)**可以使用*AutoScroll=true*来手动设置或自动设置。一个关于*AutoScroll=true*的示例:
854761
855-<figure>
856-<img src="Autoscroll_all_fit1.png" title="Autoscroll_all_fit1.png" />
857-<figcaption>Autoscroll_all_fit1.png</figcaption>
858-</figure>
762+![](/wiki-images/d/db/Autoscroll_all_fit1.png)
859763
860764上面的按钮有一个固定是宽度200。下面的按钮被锚定到面板的右侧。因此,子控件有一个首先的宽度200。因为面 板是大于200的,逻辑客户区域(logical client area)也是更大的,因此下面的按钮扩展。
861765
862766现在面板是收缩的,因此ClientWidth变得小于200
863767
864-<figure>
865-<img src="Autoscroll_not_fit1.png" title="Autoscroll_not_fit1.png" />
866-<figcaption>Autoscroll_not_fit1.png</figcaption>
867-</figure>
768+![](/wiki-images/2/27/Autoscroll_not_fit1.png)
868769
869770首先的宽度仍然是200,因此逻辑客户区域(logical client area)现在是200,并大于可见客户区域(visible client area)。下面的按钮现在有一个200的宽度,面板显示一个水平滚动条。
870771
871772## 滚动(Scroll)位置
872773
873774更改一个滚动条的位置并不会方法任何子控件的左侧或顶部,也不会更改逻辑客户区域(logical client area),也不会影响自动大小。子控件仅会模拟移动。
874775
875776## 滚动(Scrolling)和自动大小(AutoSize)
876777
877778当AutoSize=true时,[[LCL]]将扩展控件以容纳其所有的子控件,并且不需要滚动条。如果控件不能被扩展,那么(仅)执行 AutoSize的第二个动作:移动子控件。
878779
879780# 停靠(Docking)
880781
881782停靠(Docking)使用这页中描述的方法和属性,查看[[Docking|停靠]].
882783
883784# 分隔符
884785
885786查看[[TSplitter|分隔符]].
886787
887788# TLabel.WordWrap
888789
889790**TLabel.WordWrap**更改标签(label)的首选大小的行为。**WordWrap=true** 要求标签(label)的宽度是固定的,例如,通过锚定标签(label)的左、右侧的大小。标签(label)的 首选高度将通过打断标题(Caption)成多行的方式来重新计算。
890791
891792# DPI自动调整和绝对布局的自动调整
892793
893794在历史上,[[LCL]]主要用于设计绝对布局,尽管Lazarus为灵活布局提供大量的选项,像:对齐(Align),锚定(Anchors),等等,像在这篇文章的其它部分所 描述的那样。除此之外,在历史上,它忽略了目标的DPI,而使用像素值来测量控件的左侧(left),顶部(top),宽度(width)和高度(height)属性。对于桌面平台和Windows CE来说,这已经工作地相当不错了,但是随着[[LCL]]支持Android的到来,这已经不能被忽视。从Lazarus 0.9.31开始,[[LCL]]可以将[[LCL]]的绝对布局以像素的形式重新解释为一个灵活的网格。这里有多种模式可供 选择,并且它们将允许重新解释像素值来作为绝对值,或者作为DPI调整,或者只是作为窗体尺寸的一部分考虑。
894795
895796值得注意的是,这些新的规则仅影响没有对齐(Align)位置并且仅使用最标准锚定(Anchor)的控件.
896797
897798在这种情况下,这里的值仅针对DPI调整,这里有一个新的属 性:TCustomForm.DesignTimeDPI,这将存储被设计窗体系统的DPI值。当目标的DPI大于 设计时的DPI,定位的值将被扩大,否则将会被减少。桌面DPI的常见值是96,并且这是给定的默认值。
898799
899800属性DesignTimeDPI:整数读取FDesignTimeDPI然后写入FDesignTimeDPI;
900801
901802这种布局方法使用属性TApplication.LayoutAdjustmentPolicy进行控制调整
902803
903804``` pascal
904805 TLayoutAdjustmentPolicy = (
905806 lapDefault, // widget set dependent
906807 lapFixedLayout, // A fixed absolute layout in all platforms
907808 lapAutoAdjustWithoutHorizontalScrolling, // Smartphone platforms use this one,
908809 // the x axis is stretched to fill the screen and
909810 // the y is scaled to fit the DPI
910811 lapAutoAdjustForDPI // For desktops using High DPI, scale x and y to fit the DPI
911812 );
912813```
913814
914815并且,在下面的TControl的新方法允许强制进行一个特定控件和其所有子控件的一次布局自动调整,如何控制TControl特定衍生物,对此这样回应:
915816
916817``` pascal
917818TControl = class
918819public
919820...
920821 procedure AutoAdjustLayout(AMode: TLayoutAdjustmentPolicy;
921822 const AFromDPI, AToDPI, AOldFormWidth, ANewFormWidth: Integer); virtual;
922823 function ShouldAutoAdjustLeftAndTop: Boolean; virtual;
923824 function ShouldAutoAdjustWidthAndHeight: Boolean; virtual;
924825```
925826
926827例如,当屏幕旋转时,LCL-CustomDrawn-Android将调用AutoAdjustLayout 。
927828
928829# 更多细节
929830
930831很多控件重写(override) *TControl.DoAutoSize*执行实际地自动大小。
931832
932833重要: 很多Delphi控件重写(override)这个方法,很多控件在设置一些属性后会直接调用这个方法。
933834
934835在处理程序(handle)创建过程中,并非所有的接口都可以创建完整的设备上下文,这些上下文需要计算一些像文 本大小的事情。
935836
936837这就是为什么你应该总是调用*AdjustSize*而不是`DoAutoSize`的原因。
937838
938839`TControl.AdjustSize`以一种智能流行的方式调用`DoAutoSize`。
939840
940841在加载和处理程序(handle)创建过程中,会延迟调用。
941842
942843这种方法的最初的作用与*TWinControl.DoAutoSize*相同。但是,由于`DoAutoSize` 通常被衍生组件所重写,它并不是对执行所有的测试都有用,它会导致太多的资源开销。为减少这种情况,[[LCL]]调用`AdjustSize`来作为替换品。
943844
944845当设置AutoSize = true时,[[LCL]] 在宽度和高度上自动大小控件。这是[[LCL]]最复杂的一部分,因为最终的结果取决于近一百个属性。让我们简单地开始:
945846
946847[[LCL]]仅能自动调整*宽度(Width)* (高度(Height)),如果它可以自由重新调整大小的话。换句话说-宽度不会自动调整大小,如果:
947848
948849- 左侧和右侧是被锚定的。你可以使用**Anchors**属性锚定一侧,或者通过设置*Align*属性为alTop,alBottom或alClient。
949850- *宽度(Width)*被*Constraints(约束式)*属性所约束。约束式也可以通过widget集重写。例如,winapi并不允许重新调整一个组合框的高度。并且gtk widget集也不允许重新调整一个垂直滚动条的宽度。
950851
951852*高度(Height)*与*宽度(Width)*相同。
952853
953854新的大小是通过受保护(protected)的方法**TControl.CalculatePreferredSize**所计算得出来的。这个方法会要求widget集的一个适当的宽度和高度。例如,一个TButton有首先的宽度和高 度。一个TComboBox仅有一个首选的高度。首选宽度被作为0返回,因此[[LCL]]并不会自动大小宽度- 它保持宽度不变。一个TMemo没有首先的宽度或高度。因此,自动大小对一个TMemo没有影响。
954855
955856一些控件重写(override)这个方法。例如,TGraphicControlyan衍生物,像TLabel没有窗口处理程序,因此不能询问其widget。它们必需计算它们自己的首选宽度和高度。
956857
957858widget集必需为每个具有首选大小(宽度或高度或两者都有)的widget类重写(override)方法GetPreferredSize。
958859
959860## Parent.AutoSize(父类.自动大小)
960861
961862上面描述了简单的解释。真正的算法提供了很多的可能性因而变得更复杂。
962863
963864## Properties / Methods(属性/方法)
964865
965866- Left
966867- Top
967868
968869如果Parent\<\>nil,那么Left, Top是到父类客户端区域(非滚动的)的左侧像素,右侧像素的像素距离。记住客户端区域总是*不包含*父类的框 架和滚动条的。对于 Delphi用户来说:一些VCL控件,像TGroupbox,定义客户端区域为整个控件,包含框架,而另外一些控件则不是这样-[[LCL]]相比Delphi来说更加一致,因此其与Delphi不兼容。Left和Top可以是负数或者是大于客户端区域的数。一些widget集会定义一个最小值/最大值 ,在一些地方约有10.000或更多。
969870
970871当客户端区域是滚动的时,Left和Top将保持不变。
971872
972873在重新调整大小/移动期间,Left和Top并不总是与处理程序对的坐标同步。
973874
974875如果Parent=nil ,那么Left, Top取决于widget集和窗口管理器。直到Lazarus 0.9.25 ,这通常是窗体的客户端区域的left,top的屏幕坐标。这是与Delphi不兼容的。计划将其更改为窗口的Left, Top 。
975876
976877提示: 你每次更改Left和Top时,[[LCL]]会立即移动并重新计算整个布局。如果你想更改Left和Top,请改用:
977878
978879``` pascal
979880with Button1 do
980881 SetBounds(NewLeft, NewTop, Width, Height);
981882```
982883
983884- Width
984885- Height
985886
986887以像素形式的大小禁止为负数,并且大多数widget集不允许Width=0和/或Height=0 。在一些平台上的一些控件以Constraints.MinInterfaceWidth/Height的形式定义 一个较大的最小值约束。而不是重新大小一个控件为Width=0和/或Height=0 ,设置Visible=false或Parent=nil。在重新调整大小/移动期间,Width和Height并不总是与处理程序对的坐标同步。
987888
988889- BoundsRect
989890
990891等同于Bounds(Left, Top, Width, Height).
991892
992893新手常见错误:
993894
994895``` pascal
995896BoundsRect.Left := 3; // WRONG: common newbie mistake
996897```
997898
998899这没有效果, 因为读取BoundsRect的是一个函数。它在堆上创建一个临时的TRect 。上述等同于:
999900
1000901``` pascal
1001902var
1002903 r: TRect;
1003904begin
1004905 r := BoundsRect; // fetch the bounds
1005906 r.Left := 3; // change a value on the stack
1006907end; // no change
1007908```
1008909
1009910- ClientRect
1010911
1011912Left和Top总是0,0 。宽度和高度是客户端区域的可见像素大小。记住客户端区域不包含框架和滚动条。在一个可滚动的客户端区域中,逻辑客户端区域可能大于可见的客户端区域。
1012913
1013914- ClientOrigin
1014915
1015916返回客户端区域的左侧,顶部坐标0,0的屏幕坐标。注意,这个值是存储在接口(interface)中的位置。并不总是与[[LCL]]同步。当移动一个控件时,[[LCL]]设置边界到所需位置,并发送一条移动信息到接口(interface)。这取决于接口(interface)立即处理移动或排队处理移动。
1016917
1017918- LCLIntf.GetClientBounds
1018919
1019920返回一个控件的客户端边界。像ClientRect,但是Left和Top是到控件左侧,顶部的像素距离。例如在 一个TGroupBox上,Left, Top是左侧和右侧框架边界的宽度和高度。滚动在GetClientBounds上没有影响。
1020921
1021922- LCLIntf.GetWindowRect
1022923
1023924在调用后,ARect将是屏幕坐标中控件区域。这意味着,Left和Top将是处理程序对象的左侧和顶部像素的屏 幕坐标,Right和Bottom将是右侧和底部像素的屏幕坐标。
1024925
1025926- FBaseBoundsLock: integer
1026927
1027928通过LockBaseBounds/UnlockBaseBounds来增加/减小。在SetBounds调用期 间,用于保持FBaseBounds。
1028929
1029930- FBaseParentClientSize: TPoint
1030931
1031932Parent.ClientRect大小对FBaseBounds有效。FBaseBounds和 FBaseParentClientSize通常用于计算akRight (akBottom)的距离。当重新调整父类大小时,[[LCL]]知 道要保持什么距离。
1032933
1033934- FBoundsRectForNewParent: TRect
1034935
1035936当更改一个控件的父类时,会重新创建处理重新并可能会发送很多事情。尤其是停靠窗体过程是太不可靠了。因此将会保 存BoundsRect。VCL使用一种类似的机制。
1036937
1037938- fLastAlignedBounds: TRect
1038939
1039940查看TControl.SetAlignedBounds说明解释。简而言之:它停止在接口(interface)和[[LCL]]自动大小之间的一些循环。
1040941
1041942- FLastChangebounds: TRect
1042943
1043944用于停止调用具有相同坐标的ChangeBounds。这种情况经常发生。
1044945
1045946- FLastDoChangeBounds: TRect
1046947
1047948用于避免调用具有相同坐标的OnChangeBounds。这将减少用户定义的自动大小。
1048949
1049950- FLastResizeClientHeight: integer
1050951- FLastResizeClientWidth: integer
1051952- FLastResizeHeight: integer
1052953- FLastResizeWidth: integer
1053954
1054955用于避免调用具有相同坐标的OnResize。这将减少用户定义的自动大小。
1055956
1056957- FLoadedClientSize: TPoint
1057958
1058959在加载期间,很多东西被迟延,很多东西被设置。并且最糟糕的是:以一种错误的顺序。这就是为什么在再次加载时,存 储和设置SetClientWidth/SetClientHeight调用的原因。使用这种方法,[[LCL]]可以恢复设计期间使用的距离(例如,akRight)。
1059960
1060961- FReadBounds: TRect
1061962
1062963类似于FLoadedClientSize,但是适用于 SetLeft,SetTop,SetWidth,SetHeight。
1063964
1064965- procedure SetBoundsRectForNewParent(const AValue: TRect);
1065966
1066967用于设置FBoundsRectForNewParent。查看上文。
1067968
1068969- procedure SetAlignedBounds(aLeft, aTop, aWidth, aHeight: integer); virtual;
1069970
1070971像SetBounds,但是不更改默认大小。
1071972
1072973- procedure SetInitialBounds(aLeft, aTop, aWidth, aHeight: integer); virtual;
1073974
1074975一个SetBounds的智能版本, r在创建和加载过程中减少资源开销(overhead)。
1075976
1076977- procedure UpdateBaseBounds(StoreBounds, StoreParentClientSize, UseLoadedValues: boolean); virtual;
1077978
1078979提交当前的边界为基础边界。
1079980
1080981- procedure SetClientHeight(Value: Integer);
1081982- procedure SetClientSize(Value: TPoint);
1082983- procedure SetClientWidth(Value: Integer);
1083984
1084985也是用于Delphi兼容。重新调整控件的大小,来获取想要的ClientRect大小。
1085986
1086987- procedure ChangeBounds(ALeft, ATop, AWidth, AHeight: integer); virtual;
1087988
1088989这是内部的SetBounds。应用于约束式,更新基础边界,调用OnChangeBound, OnResize,锁定边界。
1089990
1090991- procedure DoSetBounds(ALeft, ATop, AWidth, AHeight: integer); virtual;
1091992
1092993这个实际上设置FLeft, FTop, FWidth, FHeight私有(private)变量。
1093994
1094995- procedure SetBounds(aLeft, aTop, aWidth, aHeight: integer); virtual;
1095996
1096997这是很多Delphi控件的标准的过程(procedure)重写(overriden)方法。 TWinControl也会重写它。
1097998
1098999- - 当边界被锁定时忽略调用
10991000 - 锁定FBoundsRealized来避免在自动调整大小期间的资源开销。
11001001
11011002ChangeBounds未锁定这种方法。
11021003
11031004- Function GetClientOrigin: TPoint; virtual;
11041005
11051006客户端区域的Left, Top的屏幕左侧。
11061007
11071008- Function GetClientRect: TRect; virtual;
11081009
11091010客户端区域的大小。(总是Left=0, Top=0)。
11101011
11111012- Function GetScrolledClientRect: TRect; virtual;
11121013
11131014在ClientRect中的可见客户端区域。
11141015
11151016- function GetChildsRect(Scrolled: boolean): TRect; virtual;
11161017
11171018返回相对于控件的左侧,顶部的客户端矩形。如果Scrolled是true,矩形将通过当前滚动值移动(查看示例 TScrollingWincontrol)。
11181019
11191020- function GetClientScrollOffset: TPoint; virtual;
11201021
11211022返回客户端区域的滚动偏移量。
11221023
11231024- function GetControlOrigin: TPoint; virtual;
11241025
11251026返回控件区域的左侧,顶部坐标0,0的屏幕坐标。(在屏幕上的控件的左侧,顶部像素)。注意,这个值是存储在接口 (interface)中的位置。并不总是与[[LCL]]同步。当移动一个控件时,[[LCL]]设置边界到所需位置,并发送一条移动信息到接口(interface)。这屈居于接口(interface)立即处理移动或排队处理移动。
11261027
11271028# 常见问题解答
11281029
11291030## 为什么自动大小(AutoSize)在设计器中不能正确的工作?
11301031
11311032在设计器中,控件可以被拖来拖去,属性几乎可以按照任何顺序设置。为了接受自动大小这一点,并避免可能的冲突,在 设计时,自动大小并不会在每次更改时都会更新。
11321033
11331034## 在一些东西更改时,为什么TForm.AutoSize不工作?
11341035
11351036查看[[Autosize / Layout|自动大小和窗体]]
11361037
11371038## 当创建很多控件时,我需要调用Application.ProcessMessages吗?
11381039
11391040在每一条信息后(例如,在每一次OnClick事件后),Application.ProcessMessages将被[[LCL]]自动调用。如果更改需要立即变得可看见的话,你需要自己调用它。例如:
11401041
11411042``` pascal
11421043procedure TFrom.Button1Click(Sender: TObject);
11431044begin
11441045 // change width of a control
11451046 Button1.Width := Button1.Width + 10;
11461047 // apply any needed changes and repaint the button
11471048 Application.ProcessMessages;
11481049 // do a lot of things that takes a long time
11491050 ...
11501051 // after leaving the OnClick the LCL automatically processes messages
11511052end;
11521053```
11531054
11541055## 在启用锚定的情况下,在运行时,控件重新调整大小,而不是使用当前值。为什么?
11551056
11561057akBottom意味着:与父类底部侧保持一定的距离。保持距离是通过基本边界定义。它们在设计时设置,或者在运 行时调用SetBounds或UpdateBaseBounds。
11571058
11581059例如: 一个TListBox (Anchors=\[akLeft,aTop\])在设计时有一个100像素的底部距离。并且通过一个按钮来启用/禁用TListBox的akBottom。现在启动应用程序并按下按钮来启用akBottom,将激活100像素的距离,因为这是程序员最 后定义的TListBox的基本边界。所有其它的重新调整大小动作都将会由[[LCL]]完成。无关紧要的大小,程序员会基于边界规则。你可以重新调整窗体的大小,并保持100像素的距离。为了将当前边界作为基本边界使用:
11591060
11601061``` pascal
11611062ListBox1.UpdateBaseBounds(true, true, false);
11621063ListBox1.Anchors := ListBox1.Anchors + [akBottom];
11631064```
11641065
11651066设置锚不会自动调用UpdateBaseBounds,因为这将会破坏独立更改属性的能力。
11661067
11671068## 在窗体中重新调整字符串网格列大小的OnResize事件中不工作
11681069
11691070当窗体的宽度( Width),高度(Height),客户端宽度(ClientWidth)或客户端高度(ClientHeight) 更改时,窗体的OnResize将会被触发。这本质上不依赖于TStringGrid。当然,它经常在调整窗体和 TStringGrid时发生。这意味着使用窗体的OnResize将经常会工作,但并不是总是这样。一个明显会失 败的示例,当主题更改时,在你的一个TForm中的一个TGroupBox中有一个TStringGrid。当主题 更改时,窗体大小保持不变,因此将不会触发Forms的OnResize。但是TGroupBox已更改,因此 TStringGrid也会重新调整大小。
11701071
11711072解决方案:使用TStringGrid的OnResize。
11721073
11731074## 为什么TForm.Width等同于TForm.ClientWidth?
11741075
11751076Mattias的注释:
11761077
11771078"这里有历史原因和技术原因。
11781079
11791080对于没有父类的窗体,Clientwidth等于宽度,因为真实的宽度包含框架在10年前的Linux上是不可用 的(至少在各种窗口管理器上是不可靠的)。我没有测试过,但是我听说现在使用gtk2是有这种可能的。主要问题是自 动大小,因为在窗体映射到屏幕前需要框架的大小。它可能仅在一个事件后才可用,这意味着你必须等待它,这对 ShowModal来说就意味着麻烦。但是,更改这项变化会破坏到很多使用现有[[LCL]]代 码的兼容,为此,我们需要在lfm文件中添加LCLVersion。
11801081
11811082在Lazarus trunk 1.7 中有一个新的编译器定义**LCLRealFormBounds**,它启用一个窗体的真实大小。为使用它,只需要使用LCLRealFormBounds ON来编译你的应用程序。到目前为止,仅支持win32的widget集。
11821083
11831084对于所有其它的控件来说,规则是ClientWidth\<=Width。Width是 ClientWidth加上widget框架值。问题是滚动条是否属于框架。我会说是,在不久前,它已经这样实施了。显然,这已经更改了。查看关于synedit的当前光标问题。"
11841085
11851086## 我得到一个无效循环/如何调试自动调整大小?
11861087
11871088这里有一些笔记,其它用户做错了什么,和如何发现:
11881089
11891090### 与Delphi的不同
11901091
11911092对于Delphi用户:请阅读:[[Autosize / Layout|Lazarus_vs_Delphi]]
11921093
11931094### 重写(Overriding)一个[[LCL]]方法,并触发一次重新计算
11941095
11951096你重写(override)一个方法,并告诉[[LCL]]来重新计算,即使什么都没有更改。检查AdjustSize,Realign,AlignControls, InvalidatePreferredSize。例如:
11961097
11971098``` pascal
11981099procedure TMainForm.SetBounds(ALeft, ATop, AWidth, AHeight: integer);
11991100begin
12001101 // This will create an endless loop
12011102 InvalidatePreferredSize;
12021103
12031104 // This will create an endless loop too:
12041105 OtherComponent.Left:=10;
12051106 OtherComponent.Left:=20;
12061107
12071108 inherited SetBounds(ALeft, ATop, AWidth, AHeight);
12081109end;
12091110```
12101111
12111112说明:SetBounds方法经常被调用,即使没有任何更改。例如,一个"Left:=30;"也将执行此操作。
12121113
12131114改进措施:检查更改:
12141115
12151116``` pascal
12161117procedure TMainForm.SetBounds(ALeft, ATop, AWidth, AHeight: integer);
12171118begin
12181119 if (Left <> ALeft) or (Top <> ATop) or (Width <> AWidth) or (Height <> AHeight) then
12191120 InvalidatePreferredSize;
12201121 inherited SetBounds(ALeft, ATop, AWidth, AHeight);
12211122end;
12221123```
12231124
12241125#### TWinControl.AlignControls
12251126
12261127``` pascal
12271128procedure AlignControls(aControl: TControl; var aRect: TRect); override;
12281129```
12291130
12301131AlignControls移动并改变所有子控件的大小。[[LCL]]使用Align=alCustom来实施(implementation) 忽略控件。
12311132
12321133参数aControl: TControl为保持与VCL的兼容性。[[LCL]]总是传递(passes) nil 。当应用Align属性时,TControl将给予aControl优先级。例如,当你有两个使用Align=alLeft的控件A,B时,那么在较下侧的左侧控件将被放到最左侧 。如果两者都有相同的左侧位置,那么按照创建顺序。现在假设你想在设计器中切换两个控件A,B。你拖拽B到左侧。这将导致B.Left设置为0。现在 AlignControls启动并找到A.Left=0和B.Left=0。通常情况下,A将获胜。为让B获胜,VCL调用AlignControls(B,r)。因此aControl是最后被移动的。与VCL截然不同,[[LCL]] 保持跟踪在TWinControl.fAlignControls中最后移动的控件,并应用在 TWinControl.CreateControlAlignList中的顺序。aControl参数始终是nil 。
12331134
12341135查看TWinControl.CreateControlAlignList。
12351136
12361137### OnResize/OnChangeBounds与[[LCL]]属性冲突
12371138
12381139你设置边界咬入(bite)[[LCL]]属性。例如,TLabel.AutoSize默认是true。如果你在 一个OnResiz事件中设置Label1.Width,[[LCL]]将 重新计算,调整Label1大小,并再次调用OnResize。在调试器中启动你的应用程序,使再次发生错误。当它进入循环时,如果你看到你的一个事件或你的方法在那里开始搜索,暂停应用程序并查看调用堆栈。例如:
12391140
12401141``` pascal
12411142procedure TMainForm.FormResize(Sender: TObject);
12421143begin
12431144 // if Button1 is anchored or AutoSize=true then the following might create an endless loop:
12441145 Button1.Width:=100;
12451146end;
12461147```
12471148
12481149### [[LCL]]接口(interface)错误,自定义widget
12491150
12501151有时,[[LCL]]接口(interface)或你的自定义控件有一个错误和取消[[LCL]] 边界。使用参数-dVerboseIntfSizing编译[[LCL]] 。这将写入[[LCL]]和[[LCL]]接口(interface)之间的通讯。如果一个widget不运行自由重新调整大小,它必须通过约束条件来告诉[[LCL]] 。在各种[[LCL]]接口(interface)和TWSControlClass.ConstraintWidth/Height 中搜索SetInterfaceConstraints。
12511152
12521153### alCustom
12531154
12541155你使用alCustom 。在早期的Lazarus版本中这只是实现了一半,但是一些聪明的程序员使用它来创建一些特殊的效果。现在它已经实现了,并且你的程序将不在工作。请查看 alCustom做什么: [[Autosize / Layout|alCustom]].
12551156
12561157# 参考
12571158
12581159- [[Docking|停靠]]
12591160- [[Anchor Sides|锚定侧]]
12601161
12611162# 贡献者和更改
12621163
12631164- 简体中文版本由 robsean 于 2020-02-18 创建(2021-03-10完成全部翻译)。
12641165
12651166---
12661167*From the Free Pascal wiki, page "Autosize / Layout/zh CN", as it stood on 2021-03-10 after a change by Robsean, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*

Back to history