1. 程式人生 > >Lazarus控制元件中控制元件尺寸變化的處理機制

Lazarus控制元件中控制元件尺寸變化的處理機制

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, DoSetBounds

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