自动规范控件前缀命名的专家的大全

自动规范控件前缀命名的专家的大全

自动规范控件前缀命名的专家的大全

自动规范控件前缀命名的专家的大全

<SCRIPT language=javascript src="http://www.sharpplus.com/counter.php?Referer=http%3A//www.google.com/search%3Fhl%3Dzh-CN%26newwindow%3D1%26q%3DIOTAModuleNotifier%26btnG%3D%25E6%2590%259C%25E7%25B4%25A2%26lr%3D&amp;Title=Delphi深度探索-自动规范控件前缀命名的专家"></SCRIPT>

自动规范控件前缀命名的专家

作者:陈省

在编程过程中对代码进行规范的命名,可以使编出的代码便于理解和维护,尤其对于大型软件,由于代码量极其巨大,规范命名就更为重要。记得在我刚开始编程的时候,对代码的规范命名很不以为然,认为实在是有点浪费时间,所以写出的程序中的控件基本上是IDE起什么名,我就用什么名,由于刚开始写的程序比较短,影响还不是太明显。后来,有一回写了一个稍微长了一点的程序,过了一段时间我又需要对它进行修改,把程序翻出来一看就傻了眼了,程序中一共用到了10个按钮,名字从button1一直排到了button10,每个按钮还定义了一堆事件,当时那个代码把我看得这个头晕呀,没办法只好重新修改,忙了半天比起完全推倒了重写,根本没节省多少时间。从那以后,我就比较注意这方面了,每生成一个控件都加上一个前缀(关于前缀的缩写Borland曾经写过一个规范,有人翻译成了中文,在网上可以查到。本文的附录中列出了基本的前后缀列表)。

后来虽然重视了,但是程序写多了,每加一个控件都要改前缀,工作量还是很大的,并经常会忘,而且很容易写错。有了OTA后,能不能利用向导在控件生成的时候来自动生成控件的前缀?

仔细研究一下ToolsApi.pas就会发现这是完全可能的,前面提到过IOTAFormNotifier接口提供了一个ComponentRenamed方法,当编程时向窗体添加删除或修改控件名称时,IDE都会调用这个方法。这就意味着通过编写一个实现了IOTAFormNotifier接口的TNotifierObject的子类,就可以在ComponentRenamed方法中获得IDE的通知,进而自动为新加的控件添加前缀使之符合需要的命名规范。要实现的FormNotifier类声明如下:

TPreFFormNotifier = class( TNotifierObject, IOTANotifier, IOTAFormNotifier )

private

FFileName : String;

public

constructor Create( FileName : String );

destructor Destroy; override;

procedure FormActivated;

procedure FormSaving;

//ComponentRenamed方法是关键!!!

procedure ComponentRenamed(ComponentHandle: TOTAHandle;

const OldName, NewName: string);

{ IOTAModuleNotifier }

end;

要添加IOTAFormNotifier需要调用IOTAFormEditor.AddNotifier方法,而只有当文件打开后才能获得对应文件的IOTAFormEditor接口,同时在文件关闭前又必须删除挂在对应模块上的Notifier(否则会引发异常),这就意味着FormNotifier的生存期是在文件打开和关闭之间,也就意味着必须在合适的时间添加和删除FormNotifier,幸运的是IDENotifier提供了FileNotification 方法,参数NotifyCode是TOTAFileNotification类型的集合类型,类型定义如下:

TOTAFileNotification = (ofnFileOpening, ofnFileOpened, ofnFileClosing,

ofnDefaultDesktopLoad, ofnDefaultDesktopSave, ofnProjectDesktopLoad,

ofnProjectDesktopSave, ofnPackageInstalled, ofnPackageUninstalled);

集合值意义如表3.3:

表3.3

意 义

ofnFileOpening

通知向导文件正被打开

ofnFileOpened

通知向导文件已经被打开

ofnFileClosing

通知向导文件正在关闭

ofnDefaultDesktopLoad

通知向导缺省桌面加载

ofnDefaultDesktopSave

通知向导缺省桌面保存

ofnProjectDesktopLoad

通知向导项目桌面配置加载

ofnProjectDesktopSave

通知向导项目桌面配置保存

ofnPackageInstalled

通知向导系统安装完包

ofnPackageUninstalled

通知向导系统卸载完包

这里需要响应ofnFileOpened和ofnFileClosing事件,所以需要实现IOTAIDENotifier接口,对象的接口声明如下:

TPreFIdeNotifier = class( TNotifierObject, IOTANotifier, IOTAIDENotifier)

public

constructor Create;

destructor Destroy; override;

{ IOTAIDENotifier }

procedure FileNotification(NotifyCode: TOTAFileNotification;

const FileName: string; var Cancel: Boolean);

procedure BeforeCompile(const Project: IOTAProject; var Cancel: Boolean); overload;

procedure AfterCompile(Succeeded: Boolean); //overload;

end;

对应的FileNotification方法的示意流程如下:

procedure TPreFIdeNotifier.FileNotification(

NotifyCode: TOTAFileNotification; const FileName: string; var Cancel: Boolean);

var

Module : IOTAModule;

Editor : IOTAEditor;

FormEditor : IOTAFormEditor;

FormNotifier : TPrefFormNotifier;

FormNotifierI, ListI, I : Integer;

begin

Case NotifyCode of

ofnFileOpened :

begin

{ 获得对应于相应文件的IOTAModule接口 }

Module := ( BorlandIDEServices as IOTAModuleServices ).FindModule (FileName);

{ 遍历相应的全部文件}

for i := 0 to Module.GetModuleFileCount - 1 do

begin

{ 获得FileEditor }

Editor := Module.GetModuleFileEditor(i);

if Editor.QueryInterface( IOTAFormEditor, FormEditor ) = S_OK then

begin

{ 如果FileEdiotr是一个FormEditor的话添加Notifier }

FormNotifier := TPrefFormNotifier.Create( FileName );

FormNotifierI := FormEditor.AddNotifier ( FormNotifier );

if FormNotifierI < 0 then

begin

FormNotifier.Free;

end

else

begin

NotifierList.AddObject(FileName, Pointer(FormNotifierI));

end;

end

end;

end;

ofnFileClosing :

begin

if NotifierList.Find(FileName, ListI) then

begin

Module := ( BorlandIDEServices as IOTAModuleServices ).FindModule (FileName);

{ 获得Notifier在列表中的索引,利用索引值可以删除相应的Notifier}

FormNotifierI := Integer(NotifierList.Objects[ListI]);

for i := 0 to Module.GetModuleFileCount - 1 do

begin

Editor := Module.GetModuleFileEditor(i);

if Editor.QueryInterface( IOTAFormEditor, FormEditor ) = S_OK then

begin

FormEditor.RemoveNotifier ( FormNotifierI );

NotifierList.Delete(ListI);

end;

end;

end;

end;

end;

end;

注意上面的代码,由于当窗体关闭时,必须确保每一个Notifier都被正确地释放。为此,需要建立一个Notifier列表来管理添加后的Notifier,这里用了一个TStringList来进行管理,注意储存Notifier索引的字符串列表必须是排序好的。另外Notifier列表需要声明为一个全局变量,并且注意不能在类中初始化列表,这样很容易造成重复创建,我们要在单元的initialization和finalization部分进行列表的初始化和释放工作,代码如下:

var

Index : Integer;

NotifierList : TStringList;

//////////////////////////////////////////////////////

initialization

NotifierList := TStringList.Create;

NotifierList.Sorted := True;

Index := (BorlandIDEServices as

IOTAServices).AddNotifier(TPreFIdeNotifier.Create);

finalization

(BorlandIDEServices as IOTAServices).RemoveNotifier(Index);

NotifierList.Free;

end.

当一个新窗体建立或老窗体被打开后,有可能要把文件另存,而事先我们已经把文件名储存在字符串列表中,现在文件名变了,原来保存的文件名就无效了。为了处理这种情况,就还需要添加一个IOTAModuleNotifier消息通知器,它的ModuleRenamed方法使我们能够截获文件重命名的消息,这时就可以用新的文件名替换字符串列表中老的文件名。同时为了保证接口IOTAModuleNotifier可以使用同前面的FormNotifier类的同样的文件名称等私有变量,要把前面的类定义修改如下:

type

TPreFFormNotifier = class(TNotifierObject, IOTANotifier, IOTAFormNotifier,

IOTAModuleNotifier)

private

FFileName: string;

FOldFileName: string;

FOldName: string;

FNewName: string;

FRenaming: Boolean;

//FModifing:Boolean;

public

constructor Create(FileName: string);

destructor Destroy; override;

procedure FormActivated;

procedure FormSaving;

//ComponentRenamed方法是关键!!!

procedure ComponentRenamed(ComponentHandle: TOTAHandle;

const OldName, NewName: string);

procedure Modified;

{ IOTAModuleNotifier }

function CheckOverwrite: Boolean;

procedure ModuleRenamed(const NewName: string);

end;

ModuleRenamed方法实现流程如下:

constructor TPreFFormNotifier.Create(FileName: string);

begin

FFileName := FileName;

FOldFileName := FileName;

//FModifing:=False;

end;

/////////////////////////////////////////////////////////////////

procedure TPreFFormNotifier.ModuleRenamed(const NewName: string);

var

ListI: Integer;

FormNotifierI: Integer;

ModNotifierI: Integer;

begin

//定位原来的文件名,删除原来文件名,添加新的文件名

ShowMessage(format('module renaming from %s to %s', [FOldFileName, NewName]));

if FormNotifierList.Find(FOldFileName, ListI) then

begin

FormNotifierI := Integer(FormNotifierList.Objects[ListI]);

FormNotifierList.Delete(ListI);

FormNotifierList.AddObject(NewName, Pointer(FormNotifierI));

 

if ModNotifierList.Find(FOldFileName, ListI) then

begin

ModNotifierI := Integer(FormNotifierList.Objects[ListI]);

ModNotifierList.Delete(ListI);

ModNotifierList.AddObject(NewName, Pointer(ModNotifierI));

end;

end;

FOldFileName := NewName;

FFileName := NewName;

inherited;

end;

现在本专家程序的大体流程已经确定下来了,但还有一个问题要说明:ComponentRenamed方法的声明procedure ComponentRenamed (ComponentHandle: TOTAHandle;const OldName, NewName: string);中NewName和OldName参数为什么定义为const类型而不是Var,这岂不是意味着无法在IDE调用ComponentRenamed方法中修改它了吗,解决办法是:TNotifierObject有个方法叫Modified,可以在这里对控件的名称进行修改,先要重新定义一个Modified方法(注意由于Borland声明Modified方法为静态的,所以不需要重载)。

现在我们回头来研究一下ComponentRename方法,只需要在Component中记录当前控件改名的状态,然后在Modified方法里修改控件名示意如下:

procedure TPreFFormNotifier.ComponentRenamed(ComponentHandle: TOTAHandle;

const OldName, NewName: string);

begin

//当前处于改名状态

FRenaming := true;

//记录老控件名,实际上没什么用,因为Modified方法是在IDE已经改完控件名之后才被

//调用,这时用FindComponent找到的控件名已经是NewName了。

FOldName := OldName;

//重要!!!在Modified方法中会用到

FNewName := NewName;

//ShowMessage(Format('rename from %s to %s',[OldName,NewName]));

end;

 

procedure TPreFFormNotifier.Modified;

var

Module: IOTAModule;

Editor: IOTAEditor;

FormEditor: IOTAFormEditor;

OTAComponent: IOTAComponent;

Component: IComponent;

I: Integer;

TempName: string;

ModifiedName: string;

begin

if FOldName = FNewName then

Exit; //当新建控件时,会出现这种情况

try

if FRenaming then

begin

Module := (BorlandIDEServices as

IOTAModuleServices).FindModule(FFileName);

for i := 0 to Module.GetModuleFileCount - 1 do

begin

Editor := Module.GetModuleFileEditor(i);

if Editor.QueryInterface(IOTAFormEditor, FormEditor) = S_OK then

begin

//查找改名后的控件

OTAComponent := FormEditor.FindComponent(FNewName);

if OTAComponent = nil then

Exit

else

begin

//如果控件类型是Tbutton,则在控件前加上前缀"Btn"

if OTAComponent.GetComponentType = 'TButton' then

begin

Component := OTAComponent.GetIComponent;

if Component = nil then

Exit

else

begin

ShowMessage('FNewName:' + FNewName);

if Pos('btn', FNewName) = 1 then

begin

ShowMessage('will not modify ' + FNewName);

Exit;

end;

TempName := 'btn' + FNewName; //应该将TempName中的数字去掉

ModifiedName := (FormEditor as

INTAFormEditor).FormDesigner.UniqueName(TempName);

FNewName:=ModifiedName;

OTAComponent.SetPropByName('Name', ModifiedName);

end;

end;

end;

end

end;

end;

finally

FRenaming:=false;

end;

end;

到此基本上大功告成了,剩下的问题就是如何提供控件类型及前缀对照表供Modified方法去使用,大家可以下载Delphi程序编码规范来自己写,另外本文提供了一个对照表文件,示例如下:

TMainMenu=mm

TPopupMenu=pm

#TMainMenuItem=mmi // I think this should be TMenuItem

TMenuItem=mmi

TPopupMenuItem=pmi

TLabel=lbl

TEdit=edt

TMemo=mem

TButton=btn

TCheckBox=chk

TRadioButton=rb

TListBox=lb

TComboBox=cb

TScrollBar=scb

TGroupBox=gb

TRadioGroup=rg

TPanel=pnl

TCommandList=cl

最后,上面的例子只是对Tbutton控件添加了前缀,在随书所附的源代码中还提供了一个完整版本的专家,可以提供对全部标准控件前缀自动命名功能。专家有待改进的是应提供一个编辑前缀对照表的界面,这样就可以添加对新的或第三方控件的支持了,这个问题留给读者去完成。


本站原创及翻译内容保留版权,欢迎转贴,转贴时请注明转自哈巴狗的小窝