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

转自Delphi深度探索

http://delphi.sqlitedeveloper.com/Delphi_Old/Autoname.htm

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

作者:陈省


在编程过程中对代码进行规范的命名,可以使编出的代码便于理解和维护,尤其对于大型软件,由于代码量极其巨大,规范命名就更为重要。记得在我刚开始编程的时候,对代码的规范命名很不以为然,认为实在是有点浪费时间,所以写出的程序中的控件基本上是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控件添加了前缀,在随书所附的源代码中还提供了一个完整版本的专家,可以提供对全部标准控件前缀自动命名功能。专家有待改进的是应提供一个编辑前缀对照表的界面,这样就可以添加对新的或第三方控件的支持了,这个问题留给读者去完成。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值