TControl类通过Left、Top、Width和Height四个属性定义了控件的尺寸和位置信息,对这四个属性以及BoundsRect的修改(GetBoundsRect和SetBoundsRect函数也是操作这四个变量)会调用SetBounds过程(SetBounds过程亦可手动调用)重新设置控件的相关变量。
当通过SetBounds过程(KeepBase=false)或布局器(KeepBase=true)修改控件尺寸和位置的时候,下一步会调用ChangeBounds过程。在ChangeBounds过程中会首先检查和修正传入的尺寸和位置数据
control.inc
// constraint the size
DoConstrainedResize(ALeft, ATop, AWidth, AHeight);
然后再调用DoSetBounds函数把修正过的数据实际存入控件的相关变量中
control.inc
DoSetBounds(ALeft, ATop, AWidth, AHeight);
最后通知用户控件的尺寸和位置发生了改变
control.inc
// notify user about resize
if (not (csLoading in ComponentState)) then
begin
Resize;
CheckOnChangeBounds;
// for delphi compatibility send size/move messages
PosSizeKept;
SendMoveSizeMessages(SizeChanged,PosChanged);
end;
因此如果在TControl的子类中覆盖ChangeBounds的话,需要注意传入的ALeft、ATop、AWidth、AHeight参数并不一定是最终的实际数据。
DoSetBounds是一个较为底层的过程,它对关于控件尺寸和位置的变量进行实际操作,因此尽量避免直接调用它,而让LCL负责。当覆盖这个过程的时候,不要在其中进行绘制工作,而是在OnPaint事件中调用Invalidate过程,或覆盖Paint过程。
Resize过程会触发OnResize事件,而为了与Delphi兼容,也会通过SendMoveSizeMessages过程发送尺寸和位置变化的消息。
继承自TControl的控件可以覆盖这个虚方法发送指定的消息。在TWinControl中,该方法会发送LM_SIZE和LM_MOVE消息。在Windows环境下,这两个消息的定义与WM_SIZE和WM_MOVE消息相同,但TLMMOVE和TWMMOVE消息结构略有差异。
messages.inc
WM_MOVE = 3;
WM_SIZE = 5;
lmessages.pp
LM_MOVE = $0003;
LM_SIZE = $0005;
因此如有必要,继承自TWinControl的控件也可以通过处理这两个消息来响应尺寸和位置变化。
如
private
procedure WMSize(var Message: TWMSize); message WM_SIZE;
需要注意的是,在特定情况下(not (csLoading in ComponentState)),这个消息不会被发送。
参考:
Custom Controls
When you write your own control, you can override and fine tune many parts of the LCL autosizing.
SetBounds, ChangeBounds, DoSetBoundsSetBounds is called when the properties Left, Top, Width, Height, BoundsRect is set or the user calls it directly. SetBounds updates the BaseBounds and BaseParentClientSize, which are used by anchoring to keep the distance. For example loading a Form with TMemo and the lfm contains TMemo’s Left and Width, then SetBounds is called two times for the memo. When the user maximizes a window, SetBounds is called for the form, but not for the Memo, keeping the BaseBounds of the Memo. If the Memo is anchored to the right, the Width of the Memo is changed based on the BaseBounds and BaseParentClientSize. Keep in mind that the given aLeft, aTop, aWidth, aHeight might not be valid and will be changed by the LCL before applied. Delphi calls SetBounds more often. SetBounds calls ChangeBounds with KeepBase=false.
ChangeBounds is called whenever the position or size of the control is set, either via the properties or by the layouter of the LCL. SetBounds calls internally ChangeBounds with KeepBase=false, while the LCL layouter calls it with KeepBase=true. Override this for code that might change the preferred size or resizes other controls. Keep in mind that the given aLeft, aTop, aWidth, aHeight might not be valid and will be changed by the LCL before applied. You can call this function.
DoSetBounds is a low level function to set the private variables FLeft, FTop, FWidth, FHeight. Do not call this function, only the LCL calls it. It also updates FClientWidth and FClientHeight accordingly. Override this to update the content layout of the control, for example scroll bars. As always: do not paint here, but call Invalidate and paint in OnPaint or override Paint.
DoAdjustClientRectChange is called by the LCL and the LCL interface, when the ClientRect has changed and the Width and Height were kept.
WMSize exists for Delphi/VCL compatibility. It is called by the LCL interface and on every change of bounds.