属性和控件编辑器

  • 来源: 互联网 作者: 若水   2008-03-20/14:50
  • Delphi提供了开放的API,是程序员可以增强Delphi IDE的功能。共有4种开放工具的APIs:属性编辑器、控件编辑器、专家/导航和版本控制系统。本文讨论属性编辑器和控件编辑器,给出的例子说明如何写自己的Delphi属性、控件编辑器。

    属性编辑器
    属性编辑器是Delphi IDE的扩展。这听起来非常复杂和困难,但是实际上是很简单的。我们可以为枚举类型构造一个属性编辑器。记得TForm的颜色属性吗?当我们想改变它的值,看到了下拉框中列出了所有的可选值。那就是枚举类型的属性编辑器,我们也同样能做到,只需要几行代码,没什么特别的。注意到程序员并没有写一个属性编辑器,而是通知Delphi使用枚举类型的属性编辑器,为它的枚举特别定义的。

    现有的属性编辑器

    在我们搞清楚属性编辑器到底内部是什么之前,先看看Delphi中已有的。开始一个新工程,在implementation中加入"uses DsgnIntf;"编译,打开browser查找TPropertyEditor(只要输入TPrope):

    Object Browser

    如果没算错的话,在DSGNINTF中注册了至少21个客户属性编辑器(custom property editors),注意:事实上,还有更多的属性编辑器在其他单元中,例如C:\DELPHI\LIB\PICEDIT.DCU.中的TPictureEditor。

    TPropertyEditor

    对象察看器为所有的属性提供缺省的编辑。我们可以使用不同的方法重载这种行为,来使用特别的属性编辑器(21种预制的属性编辑器都扩充了对象察看器来处理其属性)。那么,究竟是怎样工作的呢?它是起源一个基类,我们必需重载已达到我们的目的。五个新的Delphi 2.0的方法-其中三个是变量相关的-在编译开关{$IFDEF WIN32}中一保证一下代码在所有的delphi版本中适用。

    TypeTPropertyEditor = classprotectedfunction GetPropInfo: PPropInfo;function GetFloatValue: Extended;function GetFloatValueAt(Index: Integer): Extended;function GetMethodValue: TMethod;function GetMethodValueAt(Index: Integer): TMethod;function GetOrdValue: Longint;function GetOrdValueAt(Index: Integer): Longint;function GetStrValue: string;function GetStrValueAt(Index: Integer): string;{$IFDEF WIN32}function GetVarValue: variant;function GetVarValueAt(Index: Integer): variant;{$ENDIF}procedure Modified;procedure SetFloatValue(Value: Extended);procedure SetMethodValue(const Value: TMethod);procedure SetOrdValue(Value: Longint);procedure SetStrValue(const Value: string);{$IFDEF WIN32}procedure SetVarValue(const Value: variant);{$ENDIF}
    publicdestructor Destroy; override;
    procedure Activate; virtual;function AllEqual: Boolean; virtual;procedure Edit; virtual;function GetAttributes: TPropertyAttributes; virtual;function GetComponent(Index: Integer): TComponent;function GetEditLimit: Integer; virtual;function GetName: string; virtual;procedure GetProperties(Proc: TGetPropEditProc); virtual;function GetPropType: PTypeInfo;function GetValue: string; virtual;procedure GetValues(Proc: TGetStrProc); virtual;procedure Initialize; virtual;{$IFDEF WIN32}procedure Revert;{$ENDIF}procedure SetValue(const Value: string); virtual;{$IFDEF WIN32}procedure ValueAvailable: Boolean;{$ENDIF}
    property Designer: TFormDesigner read FDesigner;property PrivateDirectory: string read GetPrivateDirectory;property PropCount: Integer read FPropCount;property Value: string read GetValue write SetValue;end;

    TPropertyEditor编辑对象察看器中一个或是一串控件的一个属性。属性编辑器根据属性的类型而被创建,由RegisterPropertyEditor注册的类型决定。稍候有一个指示程序员如何使用这些工程的例子。所有的published属性都将出现在对象察看器中,当设计者进行读写属性的值时,其属性编辑器(为这种属性类型的)将被使用。

    在以下的时间里,我们将只注意方法中的需要被重载的重要部分,属性编辑器的行为。#p#分页标题#e#

    GetAttributes

    这是最重要的方法,他决定了属性编辑器的类型和行为。有三种属性编辑器(除了缺省的编辑框):下拉框(我们在前面提到过的),分属性列表和对话框。
    GetAttributes返回TPropertyAttributes类型,包含了一下内容:

    • paValueList:属性编辑器能返回属性的枚举列表。如果GetValues调用过程附带值,这个属性必需设置。这将使在对象察看其中的属性的右边出现下拉按钮。
    • paSubProperties: 属性编辑器有子属性时,将在当前属性下方显示成标准的大纲格式。如果GetProperties产生属性对象时这个属性必需设置。
    • paDialog:表示这个编辑方法将产生对话框。这将在对象察看其中的属性右边出现'...'按钮。
    • paSortList: 对象察看器将把GetValues返回的列表按照字母排序。
    • paAutoUpdate: 每当编辑发生改变是调用SetValue方法,而不是改变别提交时。例如Caption属性。
    • paMultiSelect: 允许多个控件被选择时显示属性的值。有些属性不适合多选的情况。例如Name属性。
    • paReadOnly: 属性值不允许改变。
    • GetValue:返回属性的串值,缺省时返回'(unknown)',这应该被重载以返回适当的值。
    • GetValues:当GetAttributes返回paValueList时被调用。它必须为每一个属性所接受的值调用参数函数。TEnumProperty将在列举中传递所有的参数。
    • SetValue(Value):设置属性的值。属性编辑器必须能够知道调用哪一个SetXxxValue函数。如果字符串不是合适的格式或不是合法的值,属性编辑器应该产生一个例外,描述产生的问题。SetValue可以忽略所有的改变,允许通过Edit方法编辑所有的属性。例如Picture属性。
    • Edit
      当'...'按钮被安下或是属性被连击识别调用。这样,例如弹出一个对话框,通过更有效的方法,而不是简单的文本来编辑属性。例如Font属性。

    TFileNameProperty

    使用这几个重要的方法我们就能写出自己的属性编辑器了:为filename建立一个大卡文件对话框属性编辑器。我们得记住编写控件从本质来说是非可视化的任务,写书信编辑器并不复杂。我们需要制定一个我们说想要的'Dialog'类型,所以我们在GetAttributes中返回[paDialog]。然后,我们在Edit过程中处理,这次包含一个TOpenDialog来找到任何存在的文件。

    unit FileName;interfaceusesSysUtils, DsgnIntf;
    TypeTFileNameProperty = class(TStringProperty)publicfunction GetAttributes: TPropertyAttributes; override;procedure Edit; override;end;
    procedure Register;
    implementationusesDialogs, Forms;
    function TFileNameProperty.GetAttributes: TPropertyAttributes;beginResult := [paDialog]end {GetAttributes};
    procedure TFileNameProperty.Edit;beginwith TOpenDialog.Create(Application) dotryTitle := GetName; { name of property as OpenDialog caption }Filename := GetValue;Filter := 'All Files (*.*)|*.*';HelpContext := 0;Options := Options + [ofShowHelp, ofPathMustExist, ofFileMustExist];if Execute then SetValue(Filename);finallyFreeendend {Edit};
    procedure Register;beginRegisterPropertyEditor(TypeInfo(TFileName),nil, '', TFileNameProperty)end;end.

    注意到我们调用属性编辑器的GetName函数来得到属性的名字。

    属性编辑器需要注册过程(register)在delphi中来注册它本身(确切的说是在delphi应用程序中)。我们可以只为一个控件注册属性编辑器,也可以我所有的相同类型的属性注册。例如上面的例子TFileNameProperty就是为所有的控件做的。当然,属性编辑器必需安装了并且首先注册。

    为了在Register过程中注册,我们需要调用RegisterPropertyEditor。它有4个参数:第一个是属性类型的类型信息的指针。这里,我们使用内置的函数TypeInfo。第二个是这个编辑器应用的控件类型,如果为nil,这个编辑器为所有控件的所有给定的类型的属性。这里,我们希望属性编辑器为所有的控件的TFileName类型工作。所以只需要把第二个参数置为nil。第三个参数时属性的名字,这个参数只有在第二个参数指定了控件的类型的情况下才有作用。同样,我们把它置为空字符串。第四个参数属性编辑器的自己的类型,这里是TFileNameProperty。

    安装属性编辑器和安装控件类似。这里,属性编辑器有自己的注册过程(不失为某个控件的属性编辑器,而是某个属性的)。一般来说,如果一个属性编辑器是为特别控件的特别属性,最好和控件一起注册。现在,我们只要把带有TFileNameProperty的单元FILENAME加到控件版中(delphi 1:使用Options | Install Components,#p#分页标题#e#Delphi 2中使用Component | Install

    安装之后,在任何控件的TFileName类型的属性,我们可以看到省略号,这表明对话框的属性编辑器已在这个属性中安装了。

    Object Inspector on property of type TFileName

    如果点击省略号,导致Delphi 2弹出如下对话框。

    Win95 FileOpen Dialog

    只用了几行代码,我们就写出了为所有控件的TFileName类型的属性的TFileName属性编辑器。这仅仅是个例子,展示了属性编辑器在编写Delphi控件和程序的巨大潜能。

    在我们研究下一个例子之前,来看看TPropertyEditor其他可以别重载的方法:

    • Activate
      这个方法在属性被选中时别调用。这可能有用,决定某些属性被选中时的行为。只有GetAttributes返回paSubProperties和paMultiSelect时,才学要准确的控制。
    • AllEqual
      当超过一个控件被选中时别调用。如果这个方法返回true,调用GetValue,否则在对象察看其中显示空白。只有在GetAttributes返回paMultiSelect时被调用。
    • GegComponent
      返回属性编辑器的控件的索引。当项获得控件时要用倒它。只有在GetAttributes返回paMultiSelect时,属性编辑器才能处理多个控件。
    • GetEditLimit
      返回使用这可以输入的值得字符串的个数,对象察看其内置的编辑器对这有限制,缺省值为255。
    • GetName
      返回属性的名字,缺省时值时从类型信息中得到的。如果属性的值和对象察看其中所显示的不一样时才有必要重载。
    • GetProperties
      应该在被编辑的属性的每一个子属性时重载,调用PropertyProc,并为每一个子属性传递一个新的TPropertyEditor。缺省时,假定没有子属性,PropertyProc不别调用。TClassProperty将为每一个published属性传递一个新的属性编辑器。TSEtProperty为每一个元素传递一个新的编辑器。
    • GetPropType
      返回被编辑的属性的类型信息的指针。
    • Initialize
      由属性编辑器创建之后,使用之前调用。属性编辑器经常被创建,但因为不是整个选择的公用属性而被抛弃,Initialize只有在对象察看器使用时,而不是被抛弃属性编辑器时调用。

    以下是创建新的TPropertyEditor类其他非常有用的属性和方法

    • Name 属性
      GetName返回的属性的名称。
    • PrivateDirectory 属性
      是.exe或Delphi.ini指定的工作目录(Working Directory),如果属性编辑器需要辅助程序或是状态文件(模版、例子等),他们应保存在这个目录中。
    • Properties indexed property
      TProperty代表了所有被属性编辑器编辑的控件,如果不只有一个控件,每个控件都有创建的相应的TProperty,一般来说,用不着它,因为Get/SetXxxValue方法能适当的处理它。
    • Value属性
      作为字符串,GetValue返回属性的当前值。
    • Modified
      调用它来指示属性的值是否改变了。SetXxxValue方法会自动调用,如狗我们直接调用SetXxxValue,我们必须同样调用Modified。
    • GetXxxValue
      得到属性中的一个属性的值。调用TRroperty适当的GetXxxValue方法来得到相应的值。
    • SetXxxValue
      设置所有属性的值。调用TRroperty适当的GetXxxValue方法来得到相应的值。

    TPicture 属性编辑器

        好了,我们已经明白如何时属性编辑器的行为像对话框,只是我想起了Delphi中最令人急躁的属性编辑器:图形、图标、图像的picture编辑器。并不是它不工作,而是他并不友好。如果我们点击了Load按钮,在对话框中选这所需要的文件。问题是,在关闭对话框之前,我们无法看到文件中的内容。返回Picture编辑器,决定是否适合要求,所以我们呢不得不一次又一次的点击Load按钮。这在我们现在许多小文件中查找时特别令人恼火。
        我们需要预览功能,看看目录种的图形文件中的图像,这对我来说是一个新的属性编辑器(Borland公司没有提供PICEDIT.DCU的源代码,PICEDIT.DFM通用没用,所以我们只能写自己的Picture编辑器,而不是增强现有的。

    TImageForm

    首先,我们的实际想要的对话框或表单,我已经设计了如下所示的一个,右下角显示了所选择的文件的图形,根据我们的需要,甚至可以对图像进行拉伸(对小图像没什么价值,对大图形就有效了)。


    Win31:

    Win95: #p#分页标题#e#

    TPictureEditor

       现在我们有了表单来选择图形,来看看如何让它在属性编辑器中工作。首先我们需要看看GRAPHIC.PAS来搞清楚什么图形、图像在第一个位置存在。我们受到TPersistert的两个继承所限制,TPicture和TGraphic,这用一来,我们只注意.bmp文件。只增强TPicture和TBitmap类,这意味着我们想为TPicture和TBitmap提供新的图形属性编辑器。

    unit PictEdit;interfaceusesDsgnIntf;
    TypeTPictureEditor = class(TClassProperty)publicfunction GetAttributes: TPropertyAttributes; override;procedure Edit; override;end;
    procedure Register;
    implementationusesSysUtils, Controls, Graphics, TypInfo, ImageFrm;
    function TPictureEditor.GetAttributes: TPropertyAttributes;beginResult := [paDialog]end {GetAttributes};
    procedure TPictureEditor.Edit;beginwith TImageForm.Create(nil) dotryImageDrBob.Picture := TPicture(GetOrdValue);if ShowModal = mrOk thenbeginif (GetPropType^.Name = 'TPicture') thenSetOrdValue(LongInt(ImageDrBob.Picture))else { Bitmap }SetOrdValue(LongInt(ImageDrBob.Picture.Bitmap))endfinallyFreeendend {Edit};
    procedure Register;beginRegisterPropertyEditor(TypeInfo(TPicture), nil, '', TPictureEditor);RegisterPropertyEditor(TypeInfo(TBitmap), nil, '', TPictureEditor)end;end.

    注意到我们并不想让TPictureEditor属于任何特别的控件,我们只有自己注册、安装,如同其他自定义的控件、专家,使用options|install components...对话框,在重新编译控件库之后(记住先备份!),我们得到了为每一个TPicture(TImage中的)和TBitmap(在TSpeedButton和TBitBtn)得到了新的Picture编辑器。

    最重要的是,已有了为TPictures和TBitmaps的属性编辑器:Borland提供的名为picture的编辑器。如果我们用自己的名字会不会有麻烦呢?不会的。因为最后注册的特别的控件和属性编辑器将精确的重载上一个。例如,我们在装一个TBitmaps的属性编辑器,将覆盖我们刚才所安装的。这次,我们用增强了的TPictureEditor覆盖缺省的Borland的Picture编辑器。

    好了,我们已经看了仅有的几种属性编辑器,我们特别讨论了paDialog属性编辑器。从我个人的观点来看,这是最容易的定制开发者在设计时输入属性值的方法。还有很多种方法来写属性编辑器,但我只能写到这里了。你可以自己察看TPropertyEditor类。下面,我们讲述Component Editor--控件编辑器。

    Component Editors

    控件编辑器和属性编辑器类似,都是用来增强Delphi的开发环境。和属性编辑器一样,他们是一个简单的类的继承,并且要重载和重定义一些方法来达到你想要的目的
    当然,和属性编辑器不同的是控件编辑器编辑的是控件,而不是属性。它限制在特别的控件中,通常是在鼠标右击控件时运行(设计时),这种行为和属性编辑器大不相同,但另一个方面,编写控件编辑器的工程和属性编辑器很相似。

    控件编辑是根据控件类型,为每一个设计表单上的控件创建的(参看DSGNINTF.PAS中的GetComponentEditor和RegisterComponentEditor)。当控件被双击,edit方法被调用。但控件的相关菜单被调用,GetVerbCount和GetVerb方法被调用来建立菜单。如果一个动作被选择,调用ExecuteVerb。但控件被粘贴到剪贴板,调用Paste方法。只用当我们想要在想光彩当中加入新的动作,改变鼠标双击的行为,或者是粘贴额外的剪贴板格式时,我们才需要创建控件编辑器。

    基类TComponentEditor的类型定义在DSGNINTF.PAS,如下(适合于所有的delphi版本):

    TypeTComponentEditor = classprivateFComponent: TComponent;FDesigner: TFormDesigner;publicconstructor Create(AComponent: TComponent;ADesigner: TFormDesigner); virtual;procedure Edit; virtual;procedure ExecuteVerb(Index: Integer); virtual;function GetVerb(Index: Integer): string; virtual;function GetVerbCount: Integer; virtual;procedure Copy; virtual;
    property Component: TComponent read FComponent;property Designer: TFormDesigner read FDesigner;end;

    控件编辑器程序员可以重载6个虚拟的方法(和属性编辑器相比较简单的多)

    • Create(AComponent, ADesigner)
      调用它来创建控件编辑器。AComponent是编辑器要编辑的控件,ADesigner是设计者找到控制和创建方法的界面(不常用)。#p#分页标题#e#
    • Edit
      单控件被双击时别调用,控件编辑器可以弹出对话框来响应这个动作,或者是一些设计指南。如果GetVerbCount比0大,edit会调用列表中的第一个动作。
    • ExecuteVerb(Index)
      使用相关菜单的动作索引。这意味着这是由控件编辑器决定的。
    • GetVerb
      属性编辑器应该返回在相关菜单中显示的字符串。放置&和...字符时控件编辑器的责任。
    • GetVerbCount
      为GetVerb和ExecuteVerb提供的合法的数值,这个值从0开始计数(0...GetVerbCount - 1)。
    • copy
      单控件被粘贴到剪贴板时被调用。控件的文件图形已经在剪贴板中了。这个了控件编辑器一个改变类型格式的机会,设计者会忽略,但其他程序可以识别。

    缺省的控件编辑器

    除了通常的控件编辑器,还有其他的控件编辑器在许多控件中使用(除非安装了其他控件编辑器重载了缺省的)。TDefaultEditor

    Apart from the general type TComponentEditor, there is also a default component editor that is used by most components (unless another component editor is installed to override the default one). The TDefaultEditor default component editor implements Edit to search the properties of the component and to generate (or navigate to an already existing) the OnCreate, OnChange or OnClick event, whichever it finds first, or just the first alphabetic event handler which is available.

    TypeTDefaultEditor = class(TComponentEditor)privateFFirst: TPropertyEditor;FBest: TPropertyEditor;FContinue: Boolean;procedure CheckEdit(PropertyEditor: TPropertyEditor);protectedprocedure EditProperty(PropertyEditor: TPropertyEditor;var Continue, FreeEditor: Boolean); virtual;publicprocedure Edit; override;end;

    当控件编辑器改变控件时,它必须调用Designer.Modfied方法告知设计者控件所属的表单经改变了。Designer是TFormDesigner类型的属性,可以从TComponentEditor基类中得到。任何控件编辑器都可以和设计者交流(至少要告诉设计者控件已经被修改了),如果我们只是用控件编辑器来显示一些信息(如产生一个about对话框),就没有必要通知设计者。

    定制控件编辑器Custom Component Editor
    当编写定制的控件时,一般考虑几个可能的基类,所有vcl的类都以TObject为根,TObject类包含了Create和Destroy方法,用于创建和注销类的一个实例。TPersistent类,从Tobject继承,包含了从表单文件中读写属性的方法。TComponent是所有控件父类,因为它包含的属性和方法允许Delphi使用TComponent作为设计单元,通过对象查看器查看他们的属性,并把这些控件放置在控件面板上。

    TObject +- TPersistent +- TComponent +- TControl +- TGraphicControl +- TWinControl +- TCustomControl 

    如果我们想创建一个新的非可视化的控件,就须要从TComponent继承。可视化控件类应该从TControl类继承。因为它包含了可视化设计控件的基本功能,象位置、可视性、字体和标题。从TControl类继承的是TCraphicControl和TWinControl,两者的区别在于TWinControl包含了实际的窗口句柄而TCraphicControl没有。这样,标准的Windows控制从TWinControl继承,而TBevel,TImage,TSpeedButton和TShape则从TCraphicControl继承,最后,TCustomControl与TWinControl TGraphicControl都相似(???)既然缺省的控件编辑器能产生事件句柄的基本代码,象OnCreate,OnChange,OnClick事件,我们首先看没有或不需要这样事件的控件,不需要窗口句柄的控件,例如不需要输入焦点,非可视化控件从Tcomponent继承。

    其它控件编辑器需要控制的控件,是通用对话框控件。例如TOpenDialog,TSaveDialog,ColorDialog,PrintDialog和PriterSetupDialog,FontDialog,FindDialog,RePlaceDialog已经有一些事件,所以不适合(当要写一个事件句柄时,缺省的控件编辑器将把我们带到代码编写器中),这五个有趣的对话框没有事件,缺省的控件编辑器也没有缺省的行为。

    那么这些对话框的控件编辑器意味着什么?

    当你设置TOverDialog所有的属性,你是否要知道[你所改变的是什么样子]?我们只有运行程序,这样才能运行对话框,能否简单一些,只要双击对话框就能预览其状态?好,我想可以,以下部分是如何解决这个问题。

    我们只要重载TComponentEdit类的edit方法,看看运行的是什么TComponent,然后运行它。

      procedure TCommonDialogComponentEditor.Edit;beginif (Component IS TOpenDialog) then { also TSaveDialog }(Component AS TOpenDialog).Executeelseif (Component IS TPrintDialog) then(Component AS TPrintDialog).Executeelseif (Component #p#分页标题#e#IS TPrinterSetupDialog) then(Component AS TPrinterSetupDialog).Executeelseif (Component IS TColorDialog) then(Component AS TColorDialog).Executeelseinherited Edit { default behaviour }end {Edit};

    注意“inherited Edit”是必须的,这样才能确保不是TComponent时缺省的控件编辑器行为能保证执行。

    注册控件编辑器,如同定制控件及属性编辑器,设置比注册属性编辑器更简单,因为我们调用RegisterComponentEditor只需要两个参数,第一个参数是控件编辑器所需控件的名字(类型),第二个是参数是控件编辑器本身的类型。

    安装控件编辑器同样和安装控件和属性编辑器相似,只需加入到我们的控件库中,就可以在设计时执行Execute方法!控件编辑器可以为单独的类创建,也可以为一套类(包括所有的继承),这里,这个控件编辑器为TCommomDialog的一套控件而设置的。

    unit CompEdit;{ TCommonDialogComponentEditor version 0.1 }interfaceusesDsgnIntf;
    TypeTCommonDialogComponentEditor = class(TComponentEditor)publicprocedure Edit; override;end;
    procedure Register;
    implementationusesDialogs;
    procedure TCommonDialogComponentEditor.Edit;beginif (Component IS TOpenDialog) then { also TSaveDialog }(Component AS TOpenDialog).Executeelseif (Component IS TPrintDialog) then(Component AS TPrintDialog).Executeelseif (Component IS TPrinterSetupDialog) then(Component AS TPrinterSetupDialog).Executeelseif (Component IS TColorDialog) then(Component AS TColorDialog).Executeelseinherited Edit { default behaviour }end {Edit};
    procedure Register;begin{ register TCommonDialogComponentEditor forTCommonDialog and all its derived classes }RegisterComponentEditor(TCommonDialog, TDialogEditor)end;end.

    安装后,拖一个TOpenDialog到表单中,连击它,看看吧!嘿嘿

    TCommonDialogComponentEditor

    不幸的是,我们测试TFontDialog时,缺省的行为(编辑OnApply事件)并没有发生,我们确实调用了inherited Edit方法,但这是TComponentEditor.editor方法,而不是缺省的控件编辑器(TDefaultEditor)的Edit方法。有两种方法补救:一种是只为TCommonDialog的5种继承的控件安装,或者是从TDefaultEditor继承,而不是从TComponentEditor类继承。

    菜单控件编辑器 Menu Component Editor
    控件编辑器不是只能作一件事(在Edit中所作的),实际上,我们能创建自己的弹出菜单来提供几个选择,让我们再来作通用对话框的‘Execute/Preview’,但这次加入一些信息显示。为了避免打断TCommonDialog继承类的缺省行为,我们只注册TCommonDialogComponentEditor到5个没有缺省事件属性的类,这回我们要重载两个函数和一个过程:函数GetVerbCount、GetVerb和过程ExecuteVerb。

    TCommonDialogComponentEditor = class(TComponentEditor)publicfunction GetVerbCount: Integer; override;function GetVerb(Index: Integer): string; override;procedure ExecuteVerb(Index: Integer); override;end;

    因为有两个菜单选项,一个是运行Dialog.Excute,另一个是显示“About”信息,函数GetverbCount应该返回2。

    function TCommonDialogComponentEditor.GetVerbCount: Integer;beginGetVerbCount := 2end {GetVerbCount};

    我们知道Delphi总是从0开始记数的,另一个函数,GetVerb的Index参数可能得到0和1两种可能的菜单输入选择。在0时我们应返回‘Execute’,在1(或其它值)应返回‘About’

    function TCommonDialogComponentEditor.GetVerb(Index: Integer): string;beginif Index >= 1 then GetVerb := '&About...'else GetVerb := '&Execute...'end {GetVerb};

    为了显示是哪一个通用对话框,我可以作如下改写:

    #p#分页标题#e#GetVerb := '&Execute ' + Component.ClassName + '...'

    最后,我们要执行动作,在0时,和以前在TDialogEditor中一样,在比0大时显示对话框

    procedure TCommonDialogComponentEditor.ExecuteVerb(Index: Integer);beginif index >= 1 thenMessageDlg('TCommonDialogComponentEditor (c) 1996 by Dr.Bob',mtInformation, [mbOk], 0)elseif (Component IS TOpenDialog) then { also TSaveDialog }(Component AS TOpenDialog).Executeelseif (Component IS TPrintDialog) then(Component AS TPrintDialog).Executeelseif (Component IS TPrinterSetupDialog) then(Component AS TPrinterSetupDialog).Executeelseif (Component IS TColorDialog) then(Component AS TColorDialog).Execute;end {ExecuteVerb};

    注意,可以使用Compent属性来得到确实的控件

    试试吧,能得到如下的弹出式菜单

    Pop-up Menu

    如果我们执行TColorDialog,选择另一种颜色,关闭对话框,在对象查看器中还是原来的值,为什么没有更新?

    实际上,我们忘记调用了Designer.Modified 方法来通知设计者(包括对象查看器)控件已经改变了,例如某个属性的值改变了。

    全部代码如下:

    unit CompMenu;{ TCommonDialogComponentEditor version 0.5 }interfaceusesDsgnIntf;
    TypeTCommonDialogComponentEditor = class(TComponentEditor)function GetVerbCount: Integer; override;function GetVerb(index: Integer): String; override;procedure Executeverb(index: Integer); override;end;
    procedure Register;
    implementationusesDialogs;
    function TCommonDialogComponentEditor.GetVerbCount: Integer;beginGetVerbCount := 2end {GetVerbCount};
    function TCommonDialogComponentEditor.GetVerb(index : Integer): String;beginif Index >= 1 then GetVerb := '&About...'else GetVerb := '&Execute...'end {GetVerb};
    procedure TCommonDialogComponentEditor.ExecuteVerb(index: Integer);beginif index >= 1 thenMessageDlg('TCommonDialogComponentEditor (c) 1996 by Dr.Bob',mtInformation, [mbOk], 0)elseif (Component IS TOpenDialog) then { also TSaveDialog }(Component AS TOpenDialog).Executeelseif (Component IS TPrintDialog) then(Component AS TPrintDialog).Executeelseif (Component IS TPrinterSetupDialog) then(Component AS TPrinterSetupDialog).Executeelseif (Component IS TColorDialog) then(Component AS TColorDialog).Execute;Designer.Modified { inform the Object Inspector of the change }end {Edit};
    procedure Register;beginRegisterComponentEditor(TCommonDialog, TCommonDialogComponentEditor)end;end.

    菜单缺省控件编辑器Menu Default Component Editors
    现在我们知道了如何为没有缺省行为的TCommonDialogs写一个新的控件编辑器,如何扩展有缺省行为的,但是又不丢失其原有的行为?例如,提供弹出菜单第一项(缺省)选择OnEvent转向代码编写,第二项预览执行,第二项选择信息显示。为了做到这一个,我们需要另一个从TDefaultEditor继承的TCommomDialogDefaultEditor类。同样我们要重载两个函数和一个过程。

    TCommonDialogDefaultEditor = class(TDefaultEditor) { not TComponentEditor }publicfunction GetVerbCount: Integer; override;function GetVerb(Index: Integer): string; override;procedure ExecuteVerb(Index: Integer); override;end;

    现在要3个选项,一个是缺省动作(TDefaultEditor.Edit),一个为Dialog.Excute,一个显示信息。所以GetVerbCount应返回3。#p#分页标题#e#

    function TCommonDialogDefaultEditor.GetVerbCount: Integer;beginGetVerbCount := 3end {GetVerbCount};

    GetVerb的Index参数能得到0,1,2三个可能值,在0时返回‘OnEvent’handler code’,在1时返回‘Exeute TxxxDialog’,2时返回其它值)返回‘About’

    function TCommonDialogComponentEditor.GetVerb(Index: Integer): string;begincase Index of0: GetVerb := '&OnEvent handler code';1: GetVerb := '&Execute ' + Component.ClassName + '...';else GetVerb := '&About...'endend {GetVerb};

    最后,执行动作。

    procedure TCommonDialogComponentEditor.ExecuteVerb(Index: Integer);beginif index >= 2 thenMessageDlg('TCommonDialogDefaultEditor (c) 1996 by Dr.Bob',mtInformation, [mbOk], 0)elseif index = 1 thenbeginif (Component IS TFindDialog) then { also TReplaceDialog }(Component AS TFindDialog).Executeelseif (Component IS TFontDialog) then(Component AS TFontDialog).Execute;Designer.Modifiedendelse inherited Edit { TDefaultEditor.Edit for index = 0 }end {ExecuteVerb};

    我想,你已经明白了。如果安装且激活了新的TCommonDialog DefaultEditor ,在TFindDialog 时,可得到如下弹出式菜单:

    TFindDialog

    如果选择第一项,双击控件,这一般是执行菜单动作的第一项),自动地弹到代码编辑器中,一切都如你所愿,代码如下:

    unit CompMenu;interfaceusesDsgnIntf;
    TypeTCommonDialogComponentEditor = class(TComponentEditor)function GetVerbCount: Integer; override;function GetVerb(index: Integer): String; override;procedure Executeverb(index: Integer); override;end;
    TCommonDialogDefaultEditor = class(TDefaultEditor)function GetVerbCount: Integer; override;function GetVerb(index: Integer): String; override;procedure Executeverb(index: Integer); override;end;
    procedure Register;
    implementationusesDialogs;
    { TCommonDialogComponentEditor }
    function TCommonDialogComponentEditor.GetVerbCount: Integer;beginGetVerbCount := 2end {GetVerbCount};
    function TCommonDialogComponentEditor.GetVerb(index : Integer): String;beginif Index >= 1 then GetVerb := '&About...'else GetVerb := '&Execute ' + Component.ClassName + '...'end {GetVerb};
    procedure TCommonDialogComponentEditor.ExecuteVerb(index: Integer);beginif index >= 1 thenMessageDlg('TCommonDialogComponentEditor (c) 1996 by Dr.Bob',mtInformation, [mbOk], 0)elsebeginif (Component IS TOpenDialog) then { also TSaveDialog }(Component AS TOpenDialog).Executeelseif (Component IS TPrintDialog) then(Component AS TPrintDialog).Executeelseif (Component IS TPrinterSetupDialog) then(Component AS TPrinterSetupDialog).Executeelseif (Component IS TColorDialog) then(Component AS TColorDialog).Execute;Designer.Modifiedendend {ExecuteVerb};
    { TCommonDialogDefaultEditor }
    function TCommonDialogDefaultEditor.GetVerbCount: Integer;beginGetVerbCount := 3end {GetVerbCount};
    function TCommonDialogDefaultEditor.GetVerb(index : Integer): String;begincase Index of0: GetVerb := '&OnEvent handler code';1: GetVerb := '&Execute ' + Component.ClassName + '...';#p#分页标题#e#else GetVerb := '&About...'endend {GetVerb};
    procedure TCommonDialogDefaultEditor.ExecuteVerb(index: Integer);beginif index >= 2 thenMessageDlg('TCommonDialogDefaultEditor (c) 1996 by Dr.Bob',mtInformation, [mbOk], 0)elsebeginif (Component IS TFindDialog) then { also TReplaceDialog }(Component AS TFindDialog).Executeelseif (Component IS TFontDialog) then(Component AS TFontDialog).Execute;Designer.Modifiedendelse inherited Edit { TDefaultEditor.Edit }end {ExecuteVerb};
    procedure Register;begin{ empty default component editors }RegisterComponentEditor(TOpenDialog, TCommonDialogComponentEditor);RegisterComponentEditor(TPrintDialog, TCommonDialogComponentEditor);RegisterComponentEditor(TPrinterSetupDialog, TCommonDialogComponentEditor);RegisterComponentEditor(TColorDialog, TCommonDialogComponentEditor);{ event default component editors }RegisterComponentEditor(TFontDialog, TCommonDialogDefaultEditor);RegisterComponentEditor(TFindDialog, TCommonDialogDefaultEditor)end;end.

    结论:
    在本文中,我们讨论了如何使用属性编辑器和控件编辑器来扩展和增强Delphi设计环境,我们知道了如何使用已有的编辑器,更重要的是,怎样写自己的,属性编辑器和控件编辑器只是所谓OpenTools API 的头两个,甚至不是最重要的,但是确实是非常有益。从这里开始吧!

     

    评论 {{userinfo.comments}}

    {{money}}

    {{question.question}}

    A {{question.A}}
    B {{question.B}}
    C {{question.C}}
    D {{question.D}}
    提交

    驱动号 更多