TWinControl.SetBounds与TWinControl.UpdateBounds赏析(定义和调用)

先看它们的函数内容:

procedure TControl.SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
begin
  // 虚函数,TWinControl有覆盖函数
  if CheckNewSize(AWidth, AHeight) and // TControl的类函数,重新计算长宽
    ((ALeft <> FLeft) or (ATop <> FTop) or (AWidth <> FWidth) or (AHeight <> FHeight)) then // 有一个不等就要重新计算和排列
  begin
    InvalidateControl(Visible, False); // TControl的类函数,非虚函数,第二个参数表示暂时设置当前控件是透明的。fixme 好像重复了,且也不明白什么意思
    FLeft := ALeft;
    FTop := ATop;
    FWidth := AWidth;
    FHeight := AHeight;
    UpdateAnchorRules; // TControl的类函数,坐标和长宽设置完了,就要重新铆接一下
    // important 手法:属性设置完了,如果有API可以使之起作用就当场调用(关于显示部分,不需要句柄就有API使用,这是特殊情况)
    Invalidate; // TControl的类函数,调用TControl.InvalidateControl,再调用API声明无效区域

    // important 图像控件只需要使用WM_WINDOWPOSCHANGED消息即可
    // 此消息在TControl和TWinControl里都有相应的函数,图形控件使用消息再做一些自己力所能及的变化,Win控件使用消息调用类函数使之调用API真正起作用
    // 前者重新计算最大化最小化的限制和坞里的尺寸,后者使用API调整边框和控件自己的位置,当然也得重新计算最大化最小化的限制和坞里的尺寸(三明治手法)
    // 当尺寸,位置或Z轴被改变了,那么就会发送这个消息。比如调用SetWindowPos API就会发送这个消息。
    Perform(WM_WINDOWPOSCHANGED, 0, 0); // 就这一处使用。它比WM_SIZE和WM_MOVE更高效。
    // Windows位置调整完了,还要重新对齐(本质是调用TWinControl.RequestAlign,然后调用API重新排列)
    // 但实际上是靠父Win控件重新排列自己,因为它自己没有能力拥有别的控件,当然也就不能实质上让所有控件对齐。
    RequestAlign; // TControl的虚函数,各WinControl的子类可自己改写,比如TCustomForm就改写了,但Win控件没有改写
    if not (csLoading in ComponentState) then Resize; // TControl的虚函数,简单调用程序员事件。子类一般不需要改写它。
  end;
end;

procedure TWinControl.SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
var
  WindowPlacement: TWindowPlacement; // Windows结构类型,包含最大化最小化窗口位置等6项内容
begin
  // 覆盖函数
  // fixme 貌似没有调用父类同名函数,说明各有各的章法
  if (ALeft <> FLeft) or (ATop <> FTop) or (AWidth <> FWidth) or (AHeight <> FHeight) then
  begin
    // 如果有句柄,并且不是最小化状态,就使用API立刻调整位置(一般调用这里)
    if HandleAllocated and not IsIconic(FHandle) then // API
      // fixme 使用完毕后Windows自己会更改WindowPlacement里的数据,
      SetWindowPos(FHandle, 0, ALeft, ATop, AWidth, AHeight, SWP_NOZORDER + SWP_NOACTIVATE) // API,此函数会自动触发WM_WINDOWPOSCHANGED消息,见MSDN
    else
    // 如果还没有句柄,或者处于最小化状态下,使用API更改WindowPlacement结构的值,以备下次调用API直接显示
    begin
      // 重新设置左上角坐标以及长宽
      FLeft := ALeft; // TControl的类属性,通用属性
      FTop := ATop;
      FWidth := AWidth;
      FHeight := AHeight;
      if HandleAllocated then
      begin
        // 取得窗口的位置信息
        WindowPlacement.Length := SizeOf(WindowPlacement);
        GetWindowPlacement(FHandle, @WindowPlacement); // API
        // 更改窗口的位置信息
        WindowPlacement.rcNormalPosition := BoundsRect; // TControl的类属性,通用属性
        SetWindowPlacement(FHandle, @WindowPlacement); // API
      end;
    end;
    // super 手法:前面使用API设置了真实的Windows窗口属性和Delphi控件属性后,可放心大胆的调用一些函数,
    // 不是TControl已经提供了通用逻辑,就是它自己定义了一些特殊的函数,可随便使用,直接产生效果,而不再依赖别人来完成某件事情。
    UpdateAnchorRules; // TControl类函数,通用函数
    RequestAlign; // TControl类函数,通用函数
  end;
end;

procedure TWinControl.UpdateBounds;
var
  ParentHandle: HWnd;
  Rect: TRect;
  WindowPlacement: TWindowPlacement;
begin
  // 非最小化状态下,取得Win控件显示区域
  if IsIconic(FHandle) then // API
  begin
    WindowPlacement.Length := SizeOf(WindowPlacement);
    GetWindowPlacement(FHandle, @WindowPlacement); // API
    Rect := WindowPlacement.rcNormalPosition;
  end else
    GetWindowRect(FHandle, Rect); // API

  // important 如果具有子窗口风格,就要根据父控件在屏幕上的位置重新显示
  if GetWindowLong(FHandle, GWL_STYLE) and WS_CHILD <> 0 then
  begin
    ParentHandle := GetWindowLong(FHandle, GWL_HWNDPARENT); // API,fixme 还可以这样简单取得父控件句柄?
    if ParentHandle <> 0 then
    begin
      Windows.ScreenToClient(ParentHandle, Rect.TopLeft); // API
      Windows.ScreenToClient(ParentHandle, Rect.BottomRight);
    end;
  end;
  // important 显示完以后,根据windows内部信息更新Win控件的属性
  FLeft := Rect.Left;
  FTop := Rect.Top;
  FWidth := Rect.Right - Rect.Left;
  FHeight := Rect.Bottom - Rect.Top;
  // 更新坐标和长宽后,要重新铆接
  // fixme 难道不用对齐吗,别处好多地方也都是这样
  UpdateAnchorRules;
end;

后看看SetBounds是怎么被调用的:

procedure TControl.SetLeft(Value: Integer);
begin
  SetBounds(Value, FTop, FWidth, FHeight); // 虚函数,本类有覆盖函数
  Include(FScalingFlags, sfLeft);
end;

procedure TControl.SetTop(Value: Integer);
begin
  SetBounds(FLeft, Value, FWidth, FHeight);
  Include(FScalingFlags, sfTop);
end;

procedure TControl.SetWidth(Value: Integer);
begin
  SetBounds(FLeft, FTop, Value, FHeight);
  Include(FScalingFlags, sfWidth);
end;

procedure TControl.SetHeight(Value: Integer);
begin
  SetBounds(FLeft, FTop, FWidth, Value);
  Include(FScalingFlags, sfHeight);
end;

procedure TControl.SetBoundsRect(const Rect: TRect);
begin
  with Rect do SetBounds(Left, Top, Right - Left, Bottom - Top); // 类函数
end;

procedure TControl.SetAlign(Value: TAlign);
var
  OldAlign: TAlign;
begin
  if FAlign <> Value then
  begin
    OldAlign := FAlign;
    FAlign := Value;
    Anchors := AnchorAlign[Value];
    if not (csLoading in ComponentState) and (not (csDesigning in ComponentState) or (Parent <> nil))
    and (Value <> alCustom) and (OldAlign <> alCustom) then
      if ((OldAlign in [alTop, alBottom]) = (Value in [alRight, alLeft])) and
        not (OldAlign in [alNone, alClient]) and not (Value in [alNone, alClient]) then
        SetBounds(Left, Top, Height, Width) // 类函数
      else
        AdjustSize; // 类函数
  end;
  RequestAlign; // 类函数
end;

procedure TControl.ChangeScale(M, D: Integer);
var
  X, Y, W, H: Integer;
  Flags: TScalingFlags;
begin
  if M <> D then
  begin
    if csLoading in ComponentState then
      Flags := ScalingFlags
    else
      Flags := [sfLeft, sfTop, sfWidth, sfHeight, sfFont];
    if sfLeft in Flags then
      X := MulDiv(FLeft, M, D) else // 复杂
      X := FLeft;
    if sfTop in Flags then
      Y := MulDiv(FTop, M, D) else
      Y := FTop;
    if (sfWidth in Flags) and not (csFixedWidth in ControlStyle) then
      if sfLeft in Flags then
        W := MulDiv(FLeft + FWidth, M, D) - X else
        W := MulDiv(FWidth, M, D)
    else W := FWidth;
    if (sfHeight in Flags) and not (csFixedHeight in ControlStyle) then
      if sfHeight in Flags then
        H := MulDiv(FTop + FHeight, M, D) - Y else
        H := MulDiv(FTop, M, D)
    else H := FHeight;
    SetBounds(X, Y, W, H); // 类函数
    if [sfLeft, sfWidth] * Flags <> [] then
      FOriginalParentSize.X := MulDiv(FOriginalParentSize.X, M, D);
    if [sfTop, sfHeight] * Flags <> [] then
      FOriginalParentSize.Y := MulDiv(FOriginalParentSize.Y, M, D);
    if not ParentFont and (sfFont in Flags) then
      Font.Size := MulDiv(Font.Size, M, D);
  end;
  FScalingFlags := [];
end;

procedure TControl.SetClientSize(Value: TPoint);
var
  Client: TRect;
begin
  Client := GetClientRect; // 类函数
  SetBounds(FLeft, FTop, Width - Client.Right + Value.X, Height - Client.Bottom + Value.Y); // 类函数
end;

procedure TControl.AdjustSize;
begin
  // 根据类属性重新设置长宽
  if not (csLoading in ComponentState) then
    SetBounds(Left, Top, Width, Height);
end;

procedure TWinControl.ScaleBy(M, D: Integer);
const
  SWP_HIDE = SWP_NOSIZE + SWP_NOMOVE + SWP_NOZORDER + SWP_NOACTIVATE + SWP_HIDEWINDOW; // 一个隐藏,其它都一样
  SWP_SHOW = SWP_NOSIZE + SWP_NOMOVE + SWP_NOZORDER + SWP_NOACTIVATE + SWP_SHOWWINDOW; // 一个显示
var
  IsVisible: Boolean;
  R: TRect;
begin
  IsVisible := HandleAllocated and IsWindowVisible(Handle); // API
  if IsVisible then SetWindowPos(Handle, 0, 0, 0, 0, 0, SWP_HIDE); // API
  R := BoundsRect;   // 类函数
  ChangeScale(M, D); // 类函数
  SetBounds(R.Left, R.Top, Width, Height); // 类函数
  if IsVisible then SetWindowPos(Handle, 0, 0, 0, 0, 0, SWP_SHOW); // API
end;

再看看UpdateBounds是如何被调用的:

procedure TWinControl.CreateWnd;
var
  Params: TCreateParams; // 是一个Record,即在栈上分配内存。出了这个函数,这部分内存就被收回。
  TempClass: TWndClass;  // Windows标准类的记录
  ClassRegistered: Boolean;
begin
  CreateParams(Params); // 虚函数,类函数,就这一处被调用。important7 虚函数的作用就是下降,它有可能会执行子类的同名覆盖函数去了。

  with Params do
  begin
    if (WndParent = 0) and (Style and WS_CHILD <> 0) then
      if (Owner <> nil) and (csReading in Owner.ComponentState) and (Owner is TWinControl) then
        WndParent := TWinControl(Owner).Handle // important 记录句柄,Params的11项内容之一。注意,这里记录的是Owner的句柄,而并非Parent的句柄
      else
        raise EInvalidOperation.CreateFmt(SParentRequired, [Name]);

    FDefWndProc := WindowClass.lpfnWndProc; // important 就这一处。这里使用Delphi类属性记录系统默认的窗口函数,不是实例对象的窗口函数。
    ClassRegistered := GetClassInfo(WindowClass.hInstance, WinClassName, TempClass); // API,第一个参数是创造这个类的instance,第二个参数是类名(Delphi的结构体项),第三个参数是取到的类信息放在里面
    if not ClassRegistered or (TempClass.lpfnWndProc <> @InitWndProc) then
    begin
      if ClassRegistered then
        Windows.UnregisterClass(WinClassName, WindowClass.hInstance); // API
      WindowClass.lpfnWndProc := @InitWndProc;   // item10.xxx = 全局函数,InitWndProc函数与标准WndProc函数的参数完全一致
      WindowClass.lpszClassName := WinClassName; // item10.xxx = item11 补充刚才CreateParams没有初始化的内容
      if Windows.RegisterClass(WindowClass) = 0 then RaiseLastOSError; // API
    end;

    CreationControl := Self;    // 全局变量,记录下来以供InitWndProc使用,而且CreationControl变量有大用处。
    CreateWindowHandle(Params); // important 注意,这可是虚函数,比如TEdit就覆盖了。
    if FHandle = 0 then RaiseLastOSError;

    if (GetWindowLong(FHandle, GWL_STYLE) and WS_CHILD <> 0) and (GetWindowLong(FHandle, GWL_ID) = 0) then // API
      SetWindowLong(FHandle, GWL_ID, FHandle); // API important 设置新ID,用句柄当作ID,比较有创意 fixme 不知道干嘛用的 fixme 看看执行了几次?
  end;

  StrDispose(FText); // fixme 为什么?可能是仅仅在创建窗口时有用,等创建完以后,大多数类不需要它,比如TButton不需要它 important TControl的消息处理的就是它
  FText := nil;
  UpdateBounds; // 类函数,调用API设置上下左右
  Perform(WM_SETFONT, FFont.Handle, 1); // fixme 为什么?TEdit里有例子
  if AutoSize then AdjustSize; // 类函数,调用API显示和对齐
end;

procedure TWinControl.WMWindowPosChanged(var Message: TWMWindowPosChanged);
var
  Framed, Moved, Sized: Boolean;
begin
  // 三明治手法,这里使边框失效
  // 判断是否有边框,是否移动了,是否改变了尺寸
  Framed := FCtl3D and (csFramed in ControlStyle) and (Parent <> nil) and (Message.WindowPos^.flags and SWP_NOREDRAW = 0);
  Moved := (Message.WindowPos^.flags and SWP_NOMOVE = 0) and IsWindowVisible(FHandle); // API
  Sized := (Message.WindowPos^.flags and SWP_NOSIZE = 0) and IsWindowVisible(FHandle);
  // 如果有边框,并且已经移动或者改变了尺寸,那么使边框无效
  if Framed and (Moved or Sized) then InvalidateFrame;  // 类函数 fixme 这不是重复了吗?
  // 仅仅调整边框不够,更主要是调整控件自己的位置
  if not (csDestroyingHandle in ControlState) then UpdateBounds; // 类函数,使用API调整控件在屏幕上的位置

  inherited; // super 三明治手法,调用程序员潜在的消息函数,并重新计算最大化最小化的限制和坞里的尺寸

  // fixme 根据消息的内容,再次使边框无效(如果有显示或隐藏标记的话)
  if Framed and ((Moved or Sized) or (Message.WindowPos^.flags and (SWP_SHOWWINDOW or SWP_HIDEWINDOW) <> 0)) then
    InvalidateFrame; // 类函数,简单调用API
end;

procedure TWinControl.WMSize(var Message: TWMSize);
begin
  UpdateBounds; // 类函数
  inherited;
  Realign; // 类函数
  if not (csLoading in ComponentState) then Resize;
end;

procedure TWinControl.WMMove(var Message: TWMMove);
begin
  inherited;
  UpdateBounds;
end;

感觉也很重要~

时间: 2024-08-11 07:46:35

TWinControl.SetBounds与TWinControl.UpdateBounds赏析(定义和调用)的相关文章

实验八——函数定义及调用总结

实验八--函数定义及调用总结 1.本次课学习到的知识点: (1)void为不反回结果的函数,且void不能省略,否则默认为int,函数体中没有表达式的return语句,也可省略return. (2)不返回结果的函数在定义.调用.参数传递.函数声明上,思路与以前相同,适用于把一些确定的.相对独立的程序功能封装成函数. (3)局部变量:定义在函数的内部,且有效作用范局部变量一般定义在函数或复合语句的开始处,围局限于所在的函数内部,形参是局部变量. (4)不能定义在中间位置. (5)全局变量:定义在函

实验七——函数定义及调用总结

1.本次课学习到的知识点: 函数 (1)定义:函数是一个完成特定工作的独立程序模块,包括函数和自定义函数两种: 1.scanf(),printf()等为库函数,由c语言系统提供定义,编程时只要直接调用即可. 2. cylinder(),fact()函数,需要用户自己定义,为自定义函数. (2)cylinder(),fact()功能不同,但他们能实现一个计算,并可以得到一个明确的计算结果. (3) 函数定义的一般形式为: 函数类型    函数名(形式参数表) { 函数实现过程 } (4)函数部首:

普通(实例)方法和实例方法的定义和调用

/** *普通方法和实例方法的定义和调用 */ class ClassName {     public $num = 1;        //实例属性     static $num2 = 2;        //静态属性     //实例方法     function showInfo()     {         echo "实例方法被调用!<br />";         echo "num的值{$this->num}<br>"

第9课 - 函数定义及调用

第9课 - 函数定义及调用 1. makefile中的函数 (1)make 解释器提供了一系列的函数供 makefile 调用 (2)在 makefile 中支持自定义函数实现,并调用执行 (3)通过 define 关键字实现自定义函数 2. 在makefile中自定义函数 (1)自定义函数的语法 其中,$(0) 代表被调用的函数名,$(1) , $(2) , $(3) ... 代表调用函数时后面的传参 (2)深入理解自定义函数 - 自定义函数是一个多行变量,无法直接调用 - 自定义函数是一种过

Ansible 的角色定义及调用

ansible 角色定义及调用 ==========================================================================  概述:    本章是上篇ansible的后续,将主要介绍ansible中角色的定义和调用,内容如下: 角色目录的定义方法 在playbook中调用角色的方法: 示例: ·定义nginx角色并调用: ·定义memcached角色并调用: ·定义mysql角色并调用: =========================

shell - 函数、数组定义与调用

#!/bin/bash # returning an array value function arraydblr() { local origarray local newarray local elements local i origarray=(`echo "[email protected]"`) newarrray=(`echo "[email protected]"`) elements=$[ $# -1 ] for((i=0;i<=$eleme

O-C相关04:类方法的概述与定义和调用

类方法的概述与定义和调用 1, 类方法的概述 类方法(class method)在其他编程语言中常常称为静态方法(例如 Java 或 C# 等). 与实例方法不同的是,类方法只需要使用类名即可调用, 不需要引用对象, 也就不需要创建对象了. 而实例方法必须是先将对象(实例)创建出来, 再利用对象来调用方法. OC 中使用 "+" 表示类方法, 使用 "-" 表示实例方法. 类方法与实例方法的定义比较: 2, 类方法的定义 3, 使用类方法的优势分析: -> 类

JavaScript作用域、上下文环境、函数对象的定义与调用、匿名函数的定义与调用、闭包

提到闭包总给人很高深的感觉,网上的例子也数不胜数.但是我发现相当一部分并不容易理解.根据我的观察,是因为这些例子把标题中提到的概念糅杂在了一起,往往越看越糊涂.所以我希望化整为零,拆成简单例子来解释. 1.先看作用域: JavaScript作用域只有两种--全局作用域和函数内作用域,没有代码块作用域.示例: function loop(){ for(var i=0;i<5;i++){ //doSomething; } alert(i); } loop(); //执行函数结果为5. 尽管变量i已经

函数的定义与调用

博客园首发,转载请注明出处,多谢支持.http://www.cnblogs.com/xuema/ 函数的定义与调用 在TypeScript中定义函数的语法为: function function_name(arg:number,arg1:number,....):return_type{ code 函数要执行的代码; return data; } 其中 function 为声明函数的关键字,functionname 为自定义函数的名字,arg为参数列表,_return_type为该函数的返回值类