• Delphi 组件渐进开发浅谈(二)——双简合璧


    2.双简合璧
    2.1.带有T[x]Label的T[x]Edit组件
      请允许我用[x]的书写方式来表示不同的对象。因为随后将大量提及TLabeledEdit与TTntLabeledEdit、TCustomLabeledEdit与TTntCustomLabeledEdit这样及其雷同的类。
    2.2.分析T[x]LabeledEdit组件结构
      现在要设计一个类似TLabeledEdit的组件,查看ExtCtrls的TLabeledEdit定义如下:
      TLabeledEdit = class(TCustomLabeledEdit)
      TLabeledEdit从TCustomLabeledEdit继承并开放属性,TCustomLabeledEdit部分定义如下:
      TCustomLabeledEdit = class(TCustomEdit)
      private
        FEditLabel: TBoundLabel;
      Public
        property EditLabel: TBoundLabel read FEditLabel;
      可以看出来,TCustomLabeledEdit继承自TCustomEdit,并构造了一个TBoundLabel对象。
    TBoundLabel对象定义如下:
      TBoundLabel = class(TCustomLabel)
      TBoundLabel与TLabel都是从TCustomLabel继承,两者溯本逐源,是兄弟关系。
      TLabeledEdit的源头是TCustomEdit和TCustomLabel,而TTntLabeledEdit的源头是TTntCustomEdit和TTntCustomLabel。T[x]CustomEdit和T[x]CustomLabel结合而形成了T[x]CustomLabeledEdit。
      在这里,T[x]CustomEdit和T[x]CustomLabel如同夫妻,两者与T[x]CustomLabeledEdit如同父(母)子关系。
      上一章设计的TGcxCustomEdit和TGcxCustomIntEdit可以替代T[x]CustomEdit,现在我们需要一个新的T[x]BoundLabel对象。
    2.3.设计TGcxCustomLabel和TGcxBoundLabel
    2.3.1.设计TGcxCustomLabel

      为了求简单,我们先简化TGcxCustomLabel设计,直接从TTntCustomLabel派生,不做任何修改。
      TGcxCustomLabel = class(TTntCustomLabel)
      end;
    2.3.2.从TBoundLabel、TTntBoundLabel到TGcxBoundLabel
      TBoundLabel的部分定义:
      TBoundLabel = class(TCustomLabel)
      private
        function GetTop: Integer;
        function GetLeft: Integer;
        function GetWidth: Integer;
        function GetHeight: Integer;
        procedure SetHeight(const Value: Integer);
        procedure SetWidth(const Value: Integer);
      protected
        procedure AdjustBounds; override;
      public
        constructor Create(AOwner: TComponent); override;
      TTntBoundLabel的部分定义:
      TTntBoundLabel = class(TTntCustomLabel)
      private
        function GetTop: Integer;
        function GetLeft: Integer;
        function GetWidth: Integer;
        function GetHeight: Integer;
        procedure SetHeight(const Value: Integer);
        procedure SetWidth(const Value: Integer);
      protected
        procedure AdjustBounds; override;
      public
        constructor Create(AOwner: TComponent); override;
      那么,TGcxBoundLabel应如下定义:
      TGcxBoundLabel = class(TGcxCustomLabel)
      private
        function GetTop: Integer;
        function GetLeft: Integer;
        function GetWidth: Integer;
        function GetHeight: Integer;
        procedure SetHeight(const Value: Integer);
        procedure SetWidth(const Value: Integer);
      protected
        procedure AdjustBounds; override;
      public
        constructor Create(AOwner: TComponent); override;
      代码部分基本剽窃TTntBoundLabel,但AdjustBounds方法略有差异,为什么呢?这需要阅读TBoundLabel.AdjustBounds和TTntBoundLabel.AdjustBounds代码。
    2.3.3.AdjustBounds的变化
      TTntBoundLabel.AdjustBounds代码如下:
    procedure TTntBoundLabel.AdjustBounds;
    begin
      inherited AdjustBounds;
      if Owner is TTntCustomLabeledEdit then
        with Owner as TTntCustomLabeledEdit do
          SetLabelPosition(LabelPosition);
    end;
      可以看到,TTntBoundLabel检查它的所有者(Owner)是否为TTntCustomLabeledEdit,并调用Owner的SetLabelPosition。
      这样一来,就局限了TTntBoundLabel的Owner必须为TTntCustomLabeledEdit,限制了TTntBoundLabel的应用范围,这是一个缺陷,违背了OOP的基本原则。
      如果按照这种设计思路,当我们想把T[x]BoundLabel绑定在其它对象上的时候,就需要重新从T[x]CustomLabel或者T[x]BoundLabel继承,并重写AdjustBounds方法。这是一个很臃肿的设计思想,会导致代码和维护量增加,这是我们不愿意看到的。
      Delphi的类只能从一个基础类继承,我们如何改变这个局面呢?对,就是接口。
      接口的概念最早是微软从COM的思想提出的,Delphi引入并延伸了这部分定义。
      我们可以让类拥有一个基础类,并拥有多个接口,并用SysUtils.Supports函数判断该类是否支持某接口。
      好了,为了让TGcxBoundLabel能够为更多的类服务,我们最终的代码如下修改:
    procedure TGcxBoundLabel.AdjustBounds;
    begin
      inherited AdjustBounds;
      if Supports(Owner, IBoundLabelOwner) then
        with Owner as IBoundLabelOwner do
          SetLabelPosition(GetLabelPosition);
    end;
      这样一来,原本是TTntCustomLabeledEdit类的SetLabelPosition方法和LabelPosition属性,被修改成了IBoundLabelOwner接口的SetLabelPosition和GetLabelPosition方法。
    2.3.4.IBoundLabelOwner接口定义
      因为接口定义的成员列表memberList只能包括方法和属性。接口中不允许含有域。因为接口中没有域,所以属性的read和write说明符都必须是方法。
      IBoundLabelOwner = interface
      ['{0056AA66-CCD0-4D56-9555-2DE908E89F8A}']
        function GetLabelPosition: TLabelPosition;
        procedure SetLabelPosition(const Value: TLabelPosition);
        property LabelPosition: TLabelPosition read GetLabelPosition write SetLabelPosition;
      end;
      这个定义很简单,就是两个函数方法声明,该接口间接应用于TGcxBoundLabel。
    2.3.5.最后一个重要属性Bind
      定义如下:
      TGcxBoundLabel = class(TGcxCustomLabel)
      private
        FBind: TWinControl;
      protected
        procedure SetBind(ABind: TWinControl);
      public
        property Bind: TWinControl read FBind;
      代码如下:
    procedure TGcxBoundLabel.SetBind(ABind: TWinControl);
    begin
      Self.FBind := ABind;
    end;
      这个属性是TBoundLabel和TTntBoundLabel没有的,它是做什么的呢?
      参考TCustomLabeledEdit和TTntCustomLabeledEdit的SetLabelPosition方法,在计算T[x]BoundLabel对象的位置时,计算公式中的Left、Top、Height、Width都是基于当前对象Self的,这样就有了一些麻烦。为什么呢?
      当T[x]BoundLabel由T[x]CustomLabeledEdit构造,并且T[x]CustomLabeledEdit是最终组件时,这不是问题,当T[x]CustomLabeledEdit是一个新组件的部分时,T[x]BoundLabel的位置计算将很痛苦。
      参考T[x]CustomLabeledEdit.SetParent方法,由于T[x]CustomLabeledEdit对象在构造T[x]BoundLabel的时候,会将自身的Parent复制给T[x]BoundLabel的Parent,这就是T[x]CustomLabeledEdit的Height、Width并不包含T[x]BoundLabel的Height、Width的原因。T[x]CustomLabeledEdit是在它的Parent对象中管理并安置T[x]BoundLabel对象。
      TTntCustomLabeledEdit与TCustomLabeledEdit在此处是一样处理的,代码及其雷同。
      所以,我们需要一个属性去修正TGcxBoundLabel的位置,Bind属性就是为了这个目的出现的。对于SetLabelPosition方法的说明,将在后面的TGcxCustomLabeledEdit和TGcxCustomIntLabeledEdit中叙述;对于SetParent方法的说明,将在后面的TGcxCustomValueInfoEdit中叙述。
    2.4.合成TGcxCustom[x]LabeledEdit
      TGcxCustomLabeledEdit、TGcxCustomIntLabeledEdit是仿照T[x]CustomLabeledEdit建立的。前者从TGcxCustomEdit继承,用于文本输入;后者从TGcxCustomIntEdit继承,用于数值输入。
      参照前面“2.1.2.3.AdjustBounds的变化”以及“2.1.2.4IBoundLabelOwner接口定义”,TGcxCustomLabeledEdit与TGcxCustomIntLabeledEdit的出场是有些与众不同的,定义如下:
      TGcxCustomLabeledEdit = class(TGcxCustomEdit, IBoundLabelOwner)
      TGcxCustomIntLabeledEdit = class(TGcxCustomIntEdit, IBoundLabelOwner)
      可以看到他们引入了一个接口IBoundLabelOwner。
      下面的定义就基本一致了,唯一与T[x]CustomLabeledEdit略有区别的地方,就是增加了GetLabelPosition方法,修改了LabelPosition属性的read定义部分,原因在前面已经提到了,不在重述。
      private
        { Private declarations }
        FEditLabel: TGcxBoundLabel;
        FLabelPosition: TLabelPosition;
        FLabelSpacing: Integer;
        function GetLabelPosition: TLabelPosition;
        procedure SetLabelPosition(const Value: TLabelPosition);
        procedure SetLabelSpacing(const Value: Integer);

      protected
        { Protected declarations }
        procedure SetParent(AParent: TWinControl); override;
        procedure Notification(AComponent: TComponent;
          Operation: TOperation); override;
        procedure SetName(const Value: TComponentName); override;
        procedure CMVisibleChanged(var Message: TMessage); message CM_VISIBLECHANGED;
        procedure CMEnabledChanged(var Message: TMessage); message CM_ENABLEDCHANGED;
        procedure CMBidimodeChanged(var Message: TMessage); message CM_BIDIMODECHANGED;
      public
        { Public declarations }
        constructor Create(AOwner: TComponent); override;
        procedure SetBounds(ALeft: Integer; ATop: Integer;
          AWidth: Integer; AHeight: Integer); override;
        procedure SetupInternalLabel;
        property EditLabel: TGcxBoundLabel read FEditLabel;
        property LabelPosition: TLabelPosition
          read GetLabelPosition write SetLabelPosition default lpLeft;
        property LabelSpacing: Integer
          read FLabelSpacing write SetLabelSpacing default 3;
    2.4.1.GetLabelPosition函数和SetLabelPosition方法
      由于引入IBoundLabelOwner接口,必须为LabelPosition属性提供新的read函数,代码如下:
    function TGcxCustomLabeledEdit.GetLabelPosition: TLabelPosition;
    begin
      Result := FLabelPosition;
    end;
      原来的SetLabelPosition代码很长,而且只能相对自身(Self)计算T[x]BoundLabel的位置。考虑到TGcxBoundLabel与T[x]BoundLabel的差异(前者将为多个类服务),我们将原有的代码重组,设计一个公共的方法UpdateLabelPosition。最后,SetLabelPosition方法简化如下:
    procedure TGcxCustomLabeledEdit.SetLabelPosition(
      const Value: TLabelPosition);
    begin
      FLabelPosition := Value;
      UpdateLabelPosition(Self, FEditLabel, FLabelPosition, FLabelSpacing);
    end;
    2.4.2.公共方法UpdateLabelPosition
      这个方法源自SetLabelPosition,提取出来的目的首先是简化单个TGcxBoundLabel子对象TGcxCustom[x]LabeledEdit的代码量,再者就是代码的维护工作量。
    procedure UpdateLabelPosition(AOwner: TWinControl;
      AEditLabel: TCustomLabel;
      const NewLabelPosition: TLabelPosition;
      const ALabelSpacing: Integer);
    var
      P: TPoint;
      obj: TWinControl;
    begin
      if AEditLabel = nil then Exit;

      obj := AOwner;
      if (AEditLabel is TGcxBoundLabel) then
      begin
        with (AEditLabel as TGcxBoundLabel) do
          if Assigned(Bind) then
            obj := Bind;
      end;
      if not Assigned(obj) then
        Exit;

      with obj do
        case NewLabelPosition of
        lpAbove: P := Point(Left, Top - AEditLabel.Height - ALabelSpacing);
        lpBelow: P := Point(Left, Top + Height + ALabelSpacing);
        lpLeft : P := Point(Left - AEditLabel.Width - ALabelSpacing,
                        Top + ((Height - AEditLabel.Height) div 2));
        lpRight: P := Point(Left + Width + ALabelSpacing,
                        Top + ((Height - AEditLabel.Height) div 2));
        end;

      AEditLabel.SetBounds(P.x, P.y, AEditLabel.Width, AEditLabel.Height);
    end;
      这段代码中,与T[x]CustomLabeledEdit. SetLabelPosition不同的地方如下:
      obj := AOwner;
      if (AEditLabel is TGcxBoundLabel) then
      begin
        with (AEditLabel as TGcxBoundLabel) do
          if Assigned(Bind) then
            obj := Bind;
      end;
      if not Assigned(obj) then
        Exit;

      with obj do
      因为该方法中AEditLabel的类型定义为TCustomLabel,所以代码首先会检查AEditLabel是否为TGcxBoundLabel,然后判断Bind属性是否有效,如果以上判断不成立,则选择缺省的AOwner对象,并根据判断结果去计算AEditLabel的位置。
    2.5.珠联璧合
      写完这一段的时候,仔细检查了一下代码,发现TGcxCustomLabeledEdit和TGcxCustomIntLabeledEdit的代码竟然一摸一样。而且SetLabelSpacing、SetParent、Notification、SetName、CMVisibleChanged、CMEnabledChanged、CMBidimodeChanged、Create、SetBounds、SetupInternalLabel与T[x]CustomLabeledEdit的代码一摸一样。
      原来就是这么简单,如果说T[x]CustomLabeledEdit是周伯通的双手互搏之术,TGcxCustomLabeledEdit与TGcxCustomIntLabeledEdit就像小龙女与杨过、云蕾与张丹枫的双剑合璧。

  • 相关阅读:
    【noi 2.5_1789】算24(dfs)
    【bzoj 4455】小星星(树型DP+容斥原理+dfs建树和计算的2种方式)
    【bzoj 1190】梦幻岛宝珠(DP)
    【bzoj 3333】排队计划(线段树)
    【noi 2.5_7834】分成互质组(dfs)
    【noi 2.5_1792】迷宫(bfs 或 dfs)
    【noi 2.2_7891】一元三次方程求解(二分枚举+输出程序运行时间)
    JFrame包含的容器(JRootPane)
    JFrame背景色设置
    java自动装箱的一个例子
  • 原文地址:https://www.cnblogs.com/jijm123/p/9048811.html
Copyright © 2020-2023  润新知