第一部分:窗体是类(A Form is A Class)(rule 1-rule 15) 程序员常常将窗体看作是对象,而事实上窗体是类。两者的差别在于你创建基于相同的窗体类的多个窗体对象。令人感到疑惑的是Delphi为你定义的每一个窗体类创建了一个默认的全局对象。这对于新手来说是相当方便的,但是这同样会使他们形成坏习惯。
规则四:使用窗体方法(Use Form Methods) 窗体都是一些类,因此窗体的代码是以方法组织的。你可以向窗体中添加事件处理程序,这些处理程序完成一些特别的功能,而且他们能被其他方法调用。除了事件处理方法外,你还可以向窗体添加完成动作的特别定义的方法以及访问窗体状态的方法。在窗体中添加一些公共的(Public)方法供其他窗体调用要比其他窗体直接操作他的组件要好。
规则5:添加窗体构造器(Add Form Constructors) 在运行时创建的第二个窗体除了一个默认的构造器(从Tcomponent 类继承而来)外还会提供其他特殊的构造器。如果你不需要考虑和Delphi4以前的版本的兼容性问题,我建议你重载(Overload)Create方法,添加必要的初始化参数。具体代码可参见下面的代码:
Public Constructor Create(Text:string): reintroduce ; overload; Constructor TformDialog.Create(Text:string); Begin Inherited Create(Application); Edit1.Text:=Text; End;
规则6:避免全局变量(Avoid Global Variables) 应该避免使用全局变量(就是那些在单元的interface 部分定义的变量)。下面将会有一些建议帮助你如何去做。 如果你需要为窗体存储额外的数据,你可以向窗体类中添加一些私有数据。这种情况下,每一个窗体实例都会有自己的数据副本。你可以使用单元变量(在单元的implementation部分定义的变量)声明那些供窗体类的多个实例共享的数据。 如果你需要在不同类型的窗体之间共享数据,你可以把他们定义在主窗体里来实现共享,或者使用一个全局变量,使用方法或者是属性来获得数据。
规则7:永远不要在Tform1类中使用Form1(Never Use Form1 in Tform1) 你应该避免在类的方法中使用一个特定的对象名称,换句话说,你不应该在TForm1类的方法中直接使用Form1.如果你确实需要使用当前的对象,你可以使用Self关键字。请牢记:大多数时候你都没有必要直接使用当前对象的方法和数据。 如果你不遵循这条规则,当你为一个窗体类创建多个实例的时候,你会陷入麻烦当中。
规则8:尽量避免在其他的窗体中使用Form1(Seldom Use Form1 In Other Forms ) 即使在其他窗体的代码中,你也应该尽量避免直接使用全局变量,如Form1.定义一些局部变量或者私有域供其他窗体使用会比直接调用全局变量要好。 例如,程序的主窗体能够为对话框定义一个私有域。很显然,如果你计划为一个派生窗体创建多个实例,这条规则将是十分有用。你可以在主窗体的代码范围内保持一份清单,也可以更简单地使用全局Sreen对象的窗体数组。
规则10:添加窗体属性(Add Form Properties) 正如我已经提到过的,当你需要为你的窗体添加数据时,请添加一个私有域。如果你需要访问其他类的数据,可以为你的窗体添加属性。使用这种方法你就能够改变当前窗体的代码和数据(包含在它的用户界面中)而不必改变其他窗体或类的代码。 你还应该使用属性或是方法来初始化派生窗体或是对话框,或是访问他们的最终状态。正如我前文所说的,你应该使用构造器来完成初始化工作
private function GetText:String; procedure SetText(const Value:String); public property Text:String; read GetText write SetText; function TformDialog.GetText:String; begin Result:=Edit1.Text; end; procedure TformDialog.SetText(const Value:String); begin Edit1.Text;=Value; end;
type TformDialog =class(TForm) private listItems:TlistBox; function GetItems(Index:Integer):String; procedure SetItems(Index:Integer:const Value:String); public property Items[Index:Integer]:string; end; function TFormDialog.GetItems(Index:Integer):string; begin if Index >=ListItems.Items.Count then raise Exception.Create(‘TformDialog:Out of Range’); Result:=ListItems.Items[Index]; end; procedure TformDialog.SetItems(Index:Integer;const alue:string); begin if Index >=ListItems.Items.Count then raise Exception.Create(‘TformDialog:Out of Range’); ListItems.Items[Index]:=Value; end;
规则13:使用属性的附加作用(Use Side-Effects In Properties) 请记住:使用属性而不是访问全局变量(参见规则10、11、12)的好处之一就是当你设置或者读取属性的值时,你还可能有意想不到的收获。 例如,你可以直接在窗体界面上拖拉组件,设置多个属性的值,调用特殊方法,立即改变多个组件的状态,或者撤销一个事件(如果需要的话)等等。
procedure Tcomponent.SetReference(Enable:Boolean); var Field:^Tcomponent; begin If Fowner<> nil then begin Field:=Fowner.FieldAddress(Fname); If Field<>nil then Field^:=Self else Field^:=nil; end; end;
上面的代码是Tcomponent类的SetReference方法,这个方法可以被InserComponent,RemoveComponent和SetName等方法调用。 当你理解了这一点后,你应该不难想到如果你将组件参照从published部分移到了private段,你将失去VCL的自动关联功能。为了解决这个问题,你可以通过在窗体的OnCreate事件中添加如下代码解决: Edit1:=FindComponent(‘Edit1’) as Tedit; 你接下来应该做的就是在系统中注册这些组件类,当你为他们注册过后就能使RTTI包含在编译程序中并且能够被系统所使用。当你将这些类型的组件参照移到private部分时,对于每一个组件类,你只需为他们注册一次。即使为他们注册不是一定必要的时候,你也可以这样做,因为对于RegisterClasses的额外调用有益无害。通常你应该在单元中负责生成窗体的初始化部分添加以下的代码: RegisterClass([TEdit]);
规则15:面向对象编程的窗体向导(The OOP Form Wizard) 为每一个窗体的每一个组件重复上述两个操作不仅十分的烦人,而且相当的浪费时间。为了避免额外的负担,我已经为此写了一个简单的向导程序。这个程序将会生成一些可以完成以上两步工作的代码,你需要做的仅仅是做几次复制和粘贴就行了。 遗憾的是这个向导程序不能自动将代码放置到单元中合适的地方,我目前正在修改这个向导程序,希望能实现这个功能。你可以到我的网站(www.marcocantu.com)查找更加完善的程序。
规则16:可视化窗体继承(Visual Form Inheritance) 如果应用得当,这将是一个强大的工具。根据我的经验,你所开发的项目越大,越能体现它的价值。在一个复杂的程序中,你可以使用窗体的不同等级关系来处理一组相关窗体的多态性(polymorphism)。 可视化窗体继承允许你共享多个窗体的一些公共的动作:你可以使用共享的方法,公用的属性,甚至是事件处理程序,组件,组件属性,组件事件处理方法等等。
规则20:用于属性的虚拟方法(Virtual Methods For Properties) 即使是访问属性的方法也能定义成virtual,这样派生类就能改变属性的动作而不必重定义他们。虽然这种方法在VCL当中很少使用,但是它确实十分灵活、强大。为了实现这一点,仅仅需要将Rule 11当中的Get 和Set 方法定义成Virtual。基类的代码如下所示:
type TformDialog = class ( TForm) Procedure FormCreate(Sender:Tobject); Private Edit1:Tedit; Protected function GetText:String;virtual; procedure SetText(const Value:String);virtual; public constructor Create(Text :String):reintroduce;overload; property Text:String read GetText write SetText; end;
在继承窗体中,你可以添加一些额外的动作来重载虚拟方法SetText: procedure TformInherit.SetText(const Value:String); begin inherited SetText(Value); if Value=’’ then Button1.Enabled:=False; end;