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就像小龙女与杨过、云蕾与张丹枫的双剑合璧。
Delphi 组件渐进开发浅谈(二)——双简合璧的更多相关文章
- Delphi 组件渐进开发浅谈(一)——由简入繁
最近业余时间在写游戏修改器玩,对于Delphi自带的组件总觉得差强人意,需要书写大量冗余代码,如果大量使用第三方组件,在以后的移植和与他人交互时也不是很方便,因此便产生了自己封装组件的想法. 实际上这 ...
- Android开发-浅谈架构(二)
写在前面的话 我记得有一期罗胖的<罗辑思维>中他提到 我们在这个碎片化 充满焦虑的时代该怎么学习--用30%的时间 了解70%该领域的知识然后迅速转移芳草鲜美的地方 像游牧民族那样.原话应 ...
- Python测试开发-浅谈如何自动化生成测试脚本
Python测试开发-浅谈如何自动化生成测试脚本 原创: fin 测试开发社区 前天 阅读文本大概需要 6.66 分钟. 一 .接口列表展示,并选择 在右边,点击选择要关联的接口,区分是否要登录, ...
- springboot开发浅谈 2021/05/11
学习了这么久,本人希望有时间能分享一下,这才写下这篇浅谈,谈谈软件,散散心情. 这是本人的博客园账号,欢迎关注,一起学习. 一开始学习springboot,看了好多网站,搜了好多课程.零零落落学了一些 ...
- delphi 组件安装工具开发
当一个组件的dpk文件数量较多且安装工具不顺手的时候,写一个属于自己的组件安装工具就很有必要了. 本例以 Dev Express 16.1.2 为例,设计一个组件安装工具,以便更深入理解 delphi ...
- J1001.Java原生桌面及Web开发浅谈
自从Java问世以来,在服务端开发方面取得了巨大的发展.但是在桌面/Web开发方面,一直没有得到大的发展.从最初的AWT,到Swing,再到JavaFX,Java从来没有在桌面/Web解决方案中取得重 ...
- 九,微信小程序开发浅谈
最近在帮朋友做一款微信小程序(后面统称为小程序),有简单的交互,以及分享和支付功能.下面就简单的对小程序开发做一个简单的介绍,希望可以帮助大家!!! 当前的小程序我们是在windows系统里开发的,如 ...
- OPC服务器开发浅谈 — 服务器模型(转)
这里主要讨论的是OPC Data Access 2.0服务器的开发,在掌握了这个最常用的OPC服务器开发之后,对其它类型的OPC服务器,如A&E.HDA等就可以触类旁通了. 一个OPC服务器的 ...
- Android组件之Service浅谈
Service是Android中的四大组件之一,和windows中的服务是类似,服务一般没有用户操作界面,它运行于系统中不容易被用户发觉,可以使用它开发如监控之类的程序Service,手机中有的程序的 ...
随机推荐
- Mac配置MySql
MySql在Mac下的情况如下: 首先,我们进入MySql的官网下载MySql(直接点击即可),打开之后便是这样. 我们点击红色方框标记的内容,之后我等待下载完成. 下载完成之后,我们需要点点,注意一 ...
- PostgreSQL的pg_stats学习
磨砺技术珠矶,践行数据之道,追求卓越价值 回到上一级页面: PostgreSQL统计信息索引页 回到顶级页面:PostgreSQL索引页 对于pg_stas,说明文档在这里: http://w ...
- CF 1025 D. Recovering BST
D. Recovering BST http://codeforces.com/contest/1025/problem/D 题意: 给出一个连续上升的序列a,两个点之间有边满足gcd(ai ,aj) ...
- 图论-最短路径--3、SPFA算法O(kE)
SPFA算法O(kE) 主要思想是: 初始时将起点加入队列.每次从队列中取出一个元素,并对所有与它相邻的点进行修改,若某个相邻的点修改成功,则将其入队.直到队列为空时算法结束. 这个算 ...
- MySql Host is blocked because of many connection errors; unblock with 'mysqladmin flush-hosts' 的解决方法
解决方法如下: 方法 1.在线修改提高允许的max_connection_errors数量: A. 登录Mysql数据库查看max_connection_errors: mysql>show ...
- Mac Eclipse快捷键
Command + O:显示大纲Command + 1:快速修复Command + D:删除当前行Command + Option + ↓:复制当前行到下一行Command + Option + ↑: ...
- 移动端车牌识别/车牌OCR识别
周末,小编约了朋友商场shopping. 开车进地下车库时,“滴”的一声,完成车牌录入:开车离开时,扫描二维码,输入车牌,完成停车收费.小编不禁感叹科技改变生活,人工智能给生活带来的便利. 车牌自动识 ...
- Ubuntu 安装python后,安装python-dev
1.通常情况下: sudo apt install python-dev 或者 在 sudo apt install python 命令下安装应该也附带了 python-dev 上述 pyhthon ...
- JY播放器【蜻蜓FM电脑端,附带下载功能】
今天给大家带来一款神器----JY播放器.可以不用打开网页就在电脑端听蜻蜓FM的节目,而且可以直接下载,对于我这种强迫症患者来说真的是神器.我是真的不喜欢电脑任务栏上面密密麻麻. 目前已经支持平台(蜻 ...
- jsp 修改页面感受
什么事情只有做过才知道. 最近在负责官网的开发,有一些页面需要和前端商量着修改,但是看到jsp那繁杂的标签和各种css,js混到一起,实在觉得jsp已经是一种落后的技术了,在修改过程中频频出现各种格式 ...