标签:修改 override cte 允许 abi 3.1 提取 表示 结果
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就像小龙女与杨过、云蕾与张丹枫的双剑合璧。
标签:修改 override cte 允许 abi 3.1 提取 表示 结果
原文地址:https://www.cnblogs.com/jijm123/p/9048811.html