




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、Delphi之面向对象的界面复用技术代码复用和界面复用面向对象的编程思想强调代码的可复用。而Delphi的精髓实际上就是Object Pascal语言,Object Pascal语言是一个非常强大的面向对象的编程语言,可以通过对象的继承实现代码复用。同时Delphi作为一个强大的RAD开发工具,不仅可以实现代 码复用,还可以实现可视化界面的复用。基于复制粘贴的界面重用Delphi最早提出的复用不是面向对象的,而是类似于代码库的重用,比如在执行窗体右键菜单的Add To Repository命令,可以将一些常用的窗体如关于对话框添加到Delphi的代码库中,以后可以在新建窗体时,直接创建一个完全
2、的一样的对话框。其 实这种复用无法是帮助我们简化了复制粘贴的过程而已,会带来很多后续维护的问题,过多的使用这种方式编程,会导致大量重复的代码,大量重复的错误。而现代 的编程思想如XP,则认为不允许复制粘贴代码,一旦遇到这种情况,就要进行重构。可视化窗体继承(Visual Form Inheritance)可视化窗体继承,以下我们 简称其为VFI是Delphi2开始出现的一种软件复用技术。允许我们创建一个基类窗体,并从这个基类窗体派生新的窗体。它在标准的以代码重用为目的类继 承的基础上实现了对可视化界面元素的重用。让我们做个试验,假设我们现在编写一组系统配置管理界面,为了统计界面样式,规定所有的
3、配置管理界面都应该有一 个容器面板,一个确定和一个取消按钮,由于这样的界面非常多,为了界面的统一,我们就来创建这样一个基类界面,首先新建一个项目起名VFI,然后使用 File | New Form菜单命令新建一个界面,起名为TBaseOptionDlg,界面示意图如下:然后下面创建一个派生类窗 体,用来配置数据库连接的参数,选择 File | New 菜单,调出New Items 对话框中,切换到当前的项目VFI下,选中刚才创建的基类BaseOptionDlg,注意在界面的下边inherit的单选框处于选中的状态。点击确 定,就会创建一个新的派生配置管理界面了。可以看到我们的派生类自动就继承了
4、父窗体所有的按钮和面板等界面元素。下面,在主窗体上添加一个数据库连接参数菜单,添加我们的DB参数配置界面,procedure TFormMain.N4Click(Sender: TObject);var AForm:TDBOptionDlg;begin AForm:=TDBOptionDlg.Create(Application); try AForm.ShowModal; finally AForm.Free; end;end;运行一下后,我突然想起 来,一般配置管理界面都会有一个默认值的按钮,可以用来恢复默认配置参数的值,而刚才设计界面时忽略了这个问题。打开基类窗体,在窗体上放置一个新的默
5、认 设置按钮,保存后。回过头来,可以发现我们的数据库配置界面也神奇增加了一个新的按钮。想像一下,如果你的工程中需要编写几十个配置管理窗体,如果不使用 窗体继承的方式来编写的话,在程序已经进入测试阶段时候,客户突然发现上面这个问题,要求修改,那么修改的工作量就会非常大,而且很难保证不会因为疏忽而 忘记修改某个配置界面。而使用窗体继承的方式,我们只要修改基类窗体就可以保证修改对所有的派生类都生效。除了界面继承之外,VFI也可以实现代码继承,在基类窗体的OnCreate事件中显示一个提示信息对话框:procedure TBaseOptionDlg.FormCreate(Sender: TObject
6、);begin ShowMessage(配置参数界面);end;运行程序后,你会发现虽然我们没有编写派生窗体的OnCreate事件处理过程,但是显示界面时,仍然会弹出消息对话框。同时,由于窗体的属性通过VFI被共用,可以有效地减 少占用的系统资源,比如有时我们可能会在界面上放上一个大的图片进行界面美化,如果这个图片被放在多个界面中,而这些界面之间没有继承关系的话,图片就会 被多次编译进资源中,在我们不知不觉中文件大小可能会翻了几倍。而将图片放在基类窗体中,无论图片被多少个子窗体共用,资源都只被编译一次,因此可以极大 的减少生成的可执行文件尺寸和加载速度。VFI窗体属性及代码重载VFI支持继承,
7、使我们可以重用一些有共性的代码,但是每个界面又有它特性的一面,这可以通过重载来实现。比如这回我觉得配置管理窗 体上面板的颜色有些单调,想调整为淡黄色的,但是我又不确定其它人是否会赞同我的审美眼光,所以我不打算修改基类的窗体面板颜色属性,而只是修改派生的数 据库配置界面上面板的颜色为clInfoback,可以看到我们的派生类窗体上面板的颜色变成了淡黄色,但是基类的面板仍然保持不变,也就是说我在子类窗 体中对父类窗体的属性进行了重载。如果修改之后,客户不满意我的颜色搭配,而喜欢基类的颜色搭配,有一个简便的办法可以恢复继承的父类属性,那就是选中面 板,然后执行右键菜单中的Revert to inhe
8、rited命令就可以了。见下图:除了属性重载外, Delphi还支持事件重载。接下来,在数据库参数配置界面上添加一个编辑框,用来指定数据库名,当显示界面的时候,需要在编辑框中给用户展现当前配置的 数据库名,因此要在窗体的OnCreate事件中进行编辑框内容的初始化。双击窗体,创建OnCreate事件处理函数,你会发现新建的OnCreate 事件不同于普通的OnCreate事件,窗体设计器自动在代码中加了一句Inherited语句,代码示意:procedure TDBOptionDlg.FormCreate(Sender: TObject);begin inherited;end;新加的inhe
9、rited 语句调用的其实就是基类的OnCreate事件处理过程。前面基类的OnCreate事件中只是简单的显示一个消息对话框。如果在派生的 TDBOptionDlg中不想显示那个愚蠢的消息框,只要把inherited注释掉就可以了。下面是修改后的初始化代码:procedure TDBOptionDlg.FormCreate(Sender: TObject);begin /inherited; edtDB.Text:=c:hubdog.db;end;VFI的局限在好的技术都有它的局限 性,不能包制百病,VFI同样如此。第一个限制就是在派生类的窗体中,我们不能删除从父类继承的组件,同时我们也不能
10、象代码继承那样,使用 protected等关键字降低某些界面组件的保护级别,使其对于子类不可见,从这一点上来说VFI不能实现界面元素信息的隐藏,这不符合面向对象的封装 要求。那么这就产生一个问题,在某些配置界面中,可能我们不想提供基类界面要求的恢复默认值的操作功能,一个比较丑陋的办法就是修改默认值按钮的 visible属性为false。与此相关的一个问题是,如果基类中定义了一个面板,而在派生类中在面板上放了一个按钮,如果修改基类界面时将面板删除的 话,则派生类中的按钮也将被删除,这类问题处理不好的话,有时会产生很大的混乱。另外,虽然VFI允许重载 属性和事件,这就会产生另外一个问题,平时很多人
11、习惯了使用事件来实现界面初始化,响应按钮点击实现参数配置更新等操作。但是由于VFI默认情况会调用基 类继承的事件,而如果窗体的继承层次很多,并且在不同层次上都使用事件处理函数来实现业务逻辑,那么很有可能会出现,在不同继承层次上的事件处理函数实现 了互相矛盾的业务处理,比如在继承树的中间的某个界面在初始化时,向界面上某个列表框添加了很多字符串,而后面的派生类界面的作者不清楚这个问题,在初始 化时先清空列表框的字符串,然后又添加了一些字符串,这就造成了原来信息的丢失。因此使用VFI的时候,建议继承的层次不要太多,同时尽量使用虚方法来代 替事件的使用,对于需要某些强制派生类实现的方法,要用定义纯虚方
12、法。回到我们的参数配置基类,对于一般参数配置过程,可以抽象出以下一些共性的必须执行的方法:1、 显示界面时,初始化参数值。2、 输入参数的有效性校验,如果无效,禁止点击确定按钮。3、 执行参数的修改,如果成功,则关闭窗体,如果失败,则等待用户重新输入。4、 点击默认设置时,恢复参数默认设置。为了强制实现上面的四个业务逻辑,修改后的基类代码如下:type TBaseOptionDlg = class(TForm) public Public declarations constructor Create(AOwner:TComponent);override; procedure InitUI;
13、virtual;abstract; function ParamsValid:Boolean;virtual;abstract; function UpdateParams:Boolean;virtual;abstract; procedure DefaultParams;virtual;abstract; end;procedure TBaseOptionDlg.ActionOKUpdate(Sender: TObject);begin (Sender as TAction).Enabled:=ParamsValid;end;procedure TBaseOptionDlg.ActionOK
14、Execute(Sender: TObject);begin if UpdateParams then begin ModalResult:=mrOk; end;end;procedure TBaseOptionDlg.ActionCancelExecute(Sender: TObject);begin ModalResult:=mrCancel;end;constructor TBaseOptionDlg.Create(AOwner: TComponent);begin inherited; initUI;end;procedure TBaseOptionDlg.ActionDefaultE
15、xecute(Sender: TObject);begin DefaultParams;end;end.为了实现前面定义的业务逻 辑,我们在窗体构造方法中调用InitUI过程来初始化界面,而在Action的OnUpdate事件中不停调用ParamsValid函数判断当前输入 的参数是否有效,如果有效,则允许点击确定按钮。点击确定按钮后,会调用UpdateParams方法来更新参数,如果更新成功,则关闭界面,否则等待用 户重新输入。最后,在用户点击恢复默认参数值按钮时,调用DefaultParams方法来完成。要注意的是,基类中的initUI, ParamValid,UpdateParams和D
16、efaultParams过程都被定义为纯虚的抽象方法。这是因为对于基类来说,由于没有具体的参数 输入界面元素,实现这些方法是没有意义的,只有到了具体的参数配置界面,才需要实现这些方法,同时定义为Abstract抽象方法可以强迫派生类必须实现 这几个方法来完成业务逻辑,否则编译后无法正确运行。下面是TDBOptionDlg实现的示意性代码:procedure TDBOptionDlg.DefaultParams;begin edtDB.Text:=c:hubdog.db;end;procedure TDBOptionDlg.InitUI;begin edtDB.Text:=c:hubdog.db
17、;end;function TDBOptionDlg.ParamsValid: Boolean;begin Result:=trim(edtDB.Text);end;function TDBOptionDlg.UpdateParams: Boolean;begin Result:=True;end;end.另外,窗体继承不支持窗口嵌套,也就是不支持窗口的组合复用,因此为了扩充窗口的功能,无形中鼓励人们使用继承机制,而不是组合机制。这加深了窗口之间的偶合度,不利于灵活性和扩展性。窗体继承还有一个很大的问题就是它只支持窗口级别的组件复用,但是更多的时候,我们想要的只是粒度更小的界面中某一部分显示区
18、域或者组件级别的复用。Borland也考虑到了这个问题,因此在Delphi4中给出了Component Template的解决方案。组件模板组件模板技术相当简单,比如下图所示意的双列表框组合界面是很常用的一类界面,可以用来从一个列表框向另一个列表框移动对象。那么我们就可以选择列表框和按钮,然后执行 Component | Create Component Template.命令来创建组件模板,示意图如下:设定组件模板名称为TDualListBox,并将其作为一个组件放到Templates组件面板上,组件面板除了能保存各个组件的属性外,还可以保存相关组件的事件代码。还有一点限制是它不能将窗体设定
19、为组件模板。生成好的组件模板可以象其它组件一样从组件面板上拖放到窗体,并生成模版副本。组件模板同VFI本质上的不同在于它不能实现继承,当把TDualListBox作为一个组件放到窗体上之后,它只相当于原来所有的组件的一个拷贝,我们可以随意地将一个按钮删除,而VFI的派生类则不允许我们删除从父类继承的组件。因此,组件模板不能算是一个面向对象的界面复用解决方案,即便我们随后修改了组件模板的设计,这些设计的变化并不会影响到先前的被实例化了的组件。因此可以认为,组件模板只是一个简化我们操作的复制粘贴工具。另外,组件模板还有一个很致命的问题就是,它很难被共享,因为组件模板信息是被统一保存到Delphi.
20、dct文件中的,而不是保存在pas文件中的,因此要想在不同机器间共享模版,必须将Delphi.dct拷贝到其它机器上,或者将Delphi.dct进行共享,而Delphi.dct又是一个单独的文件,你无法从中抽取单独的一些模板出来分发。所以,Component Template不太适用于多人开发的项目中。针对VFI和Component Template等技术暴露出来的问题,Borland进一步的提出了TFrame的解决方案。基于TFrame的复用TFrame比较像一个综合了Component Template和VFI优点的产物,本身的实现机制同VFI非常类似,都是面向对象的,对基类的可视化变更会
21、立刻反映到派生类,支持代码共享和界面共 享,很容易分发。同时,它又像Component Template那样支持小粒度的组件复用,支持子窗口嵌套,是一个比VFI更为轻量级的解决方案。对于那些非常复杂有很多输入选项的独立的界面编程,TFrame是非常适合的,因为利用TFrame我们可以将一个复杂的界面分解为多个简单的模块 的编程。假设这回客户要求我们编写一个客户资源信息录入界面。客户资源信息包括很多内容,比如Email,电话,地址几十项信息。分析一下客户对象这个实体的属性,一个客户可以对应多个Email地址 ,多个电话号码,对于这种一对多的关系,可以采用一个输入框,一个列表框和添加,删除按钮来完
22、成信息的编辑修改的,同时考虑到电话和Email地址同客户 的关系都为一对多,那么就需要编辑组件和大同小异的功能实现。对于这种情况,我们就可以使用TFrame来复用这样的一对多信息输入界面。首先,新建项目,在主界面上添加一个面板,然后在窗体上放上确定和取消按钮。然后,使用菜单命令 File | New Frame新建一个TFrame。在新建的Frame上添加列表框等输入组件,完成的界面示意图如下:为Frame添加信息编辑的代码type TFrameList = class(TFrame) protected function CanAdd:Boolean;virtual; end;procedu
23、re TFrameList.ActionAddUpdate(Sender: TObject);begin (Sender as TAction).Enabled:=CanAdd;end;function TFrameList.CanAdd: Boolean;begin Result:=(trim(edtInput.Text) and end;procedure TFrameList.ActionAddExecute(Sender: TObject);begin end;procedure TFrameList.ActionDelUpdate(Sender: TObject);begin (Se
24、nder as TAction).Enabled:=ListBox.ItemIndex-1;end;procedure TFrameList.ActionDelExecute(Sender: TObject);begin end;FrameList使用 Action对添加删除动作会进行有效性判断,添加时调用函数CanAdd判断是否可以添加,FrameList基类的CanAdd函数只是简单的判断当 前输入框中文本是否为空,以及当前输入框中文本是否已经被添加进了列表框,如果不满足,禁止添加按钮。注意为了派生类扩展的需要,这里CanAdd声明为 虚方法,后面我们的Email和电话输入Frame要想对
25、Email和电话的有效性进行校验的话,可以重载这个函数。删除前只是简单的判断ListBox 中是否有选中的要被删除的信息。接下来就是从我们的基类派 生出Email和电话的编辑框架,其中电话的编辑框架只是修改的按钮和标签的Caption,显示添加电话,删除电话,以及电话列表等信息,同时重载了 CanAdd函数,提供了对电话号码的简单判断。而Email除了修改显示信息和重载CanAdd函数外,还为列表框增加了双击列表框,激活向Email 地址发送邮件的功能。/判断是否可以添加电话号码function TFrameTele.CanAdd: Boolean;var I, code:Integer;be
26、gin if inherited CanAdd then begin Val(trim(edtInput.Text), I, code); Result:=code=0; end;end;/判断是否可以添加Emailfunction TFrameEmail.CanAdd: Boolean;begin if inherited CanAdd then begin result:=Pos(, trim(edtInput.Text)0; end;end;/激活Email客户端,收件人为当前Email账户procedure TFrameEmail.ListBoxDblClick(Sender: TOb
27、ject);begin inherited; if ListBox.ItemIndex-1 then ShellExecute(Handle,open, PChar(mailto:+trim(edtInput.text),nil,nil,SW_NORMAL);end;注意由于本例子只是演示Frame的用法,所以我只是简单的进行输入有效性判断,真正完备的判断应该是基于正则表达式的,虽然VCL库中没有提供正则表达式的支持,但是有一些免费的第三方库,比如TRegExpr可以使用,这里就不详述了。接下来选中面板,然后点击组件面板Standard页面上的Frames图标,调出Frames列表框,在面板上
28、添加FrameEmail和FrameTele,接下来是编写界面初始化代 码来加载,这时你会发现TFrame不同于TForm,它没有提供OnCreate和OnDestroy事件(不知道是什么原因,我猜测Borland的 R&D Team一定也研究这个问题,不知是出于什么考虑从Delphi5到Delphi7一直没有实现这一显而易见的需求),所以要想在TFrame创建时对其 进行初始化,只能是在TFrame的OnCreate事件中进行初始化。constructor TFrameEmail.Create(AOwner: TComponent);begin inherited;hubdog);hubca
29、t);end;constructor TFrameTele.Create(AOwner: TComponent);begin inherited;861088888888);861066666666);end;可以看到,使用TFrame后,原来需要集中在主界面完成的代码,现在全都分散到各个单元来实现,同时TFrame可以嵌套在主界面中实现可视化修改,甚至TFrame中也可以继续嵌套TFrame,将TFrame想像成建筑中的砖头,工人可以通过砖头的堆砌和组合建立起摩天大楼,同样的我们通过TFrame的组件组合的复用模式,也可以实现操作复杂的交互界面。TFrame的局限性虽 然TFrame有着很多的好处,但是也一样有它的缺点,比如它和VFI一样,无法实现信息隐藏,因为界面上所有的组件默认都是published属性,并 且无法像代码那样通过protected等保护级别关键字进行隐藏,它暴露了内部的太多的实现细节,不满足面向对象思想中窄接口的封装原则。要想实现真正 的信息隐藏,必须通过纯代码方式编写的组件来实现,但是编写组件虽然能满足封装的原则,但是无法像TFrame那样无须编译注册,就可以在
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025仓库土地租赁合同范本
- 2025两人合伙经营合同模板
- 《创意产业概述》课件
- 安全教育法制
- 重庆申论2类真题及答案
- 教育场景插画教程课件
- 紫色简约大气卡通风商业计划模板
- 幼儿园户外环境创设课件
- 人教版高中历史必修二第16课《大众传媒的变迁》课件
- 住宅绿化养护培训课件
- 仁爱版初中英语单词表(默写版)
- 企业防渗漏标准做法案例库图文丰富
- Unit 2 Listening and talking -高中英语人教版(2019)必修第一册
- 医院分娩记录单
- GB/T 17872-1999江海直达货船船型系列
- GB/T 12027-2004塑料薄膜和薄片加热尺寸变化率试验方法
- 中医手诊培训资料课件
- 消防主机运行记录表(标准范本)
- 应急处置措施交底
- Q∕GDW 12154-2021 电力安全工器具试验检测中心建设规范
- 第四章 金融监管(商业银行管理-复旦大学)
评论
0/150
提交评论