属性分类及其实现

    在Delphi 5 中,它的Object Inspector又有了新的功能上的增强,这就是属性分类。   

    属性分类

    看下面的图3.1,这会注意到在状态栏中的这句话:"2 hidden."这表明我们现在可以从Object Inspector的视图中隐藏属性。这就是将要谈到的属性分类的一部分特性。

     Delphi 5提供了类似Visual Basic的对事件和属性进行分组的能力。通常条件下,缺省设置的Object Inspector视图按字母排序所有的属性(包括很多很少使用的属性和事件),这样的话,在很多属性中定位需要编辑的属性就比较麻烦。而现在,我们可以通过定制Object Inspector来按分类来显示属性。如下图3.2所示:

     当我们按分类显示属性后,注意我们仍然可以看到状态条上的"2 hidden"信息。调出右键菜单,我们会注意到"Legacy"类别缺省时是隐藏的。这个类别包含了Ctl3D和OldCreateOrder属性,是为了兼容性才保留,对于新的程序已经没有意义了,所以缺省时会被隐藏起来

扫描二维码关注公众号,回复: 10423020 查看本文章

     分类显示属性可以更好的说明属性的用途和意义。特别是当你仅仅想设定显示属性的时候,可以不受那些不相关的属性的干扰。当然需要知道的是一个属性可以分别属于多个类别。如下图3.4所示:

    就象我们看到的Hint属性分别属于"Actions"和"Help and Hints"类别,除此之外还有很多属性,比如Caption可以同时属于多个类别。

    Object Inspector会在状态条显示当前有多少隐藏属性,然而一些被隐藏的属性可能会在其他类别中显示出来(所以隐藏的属性数可能会比实际唯一被隐藏的属性数值大)。

    定制属性分类

    那么如何指定我们自己需要的属性分类呢?比如,我们可能想在自己设计的一个控件中建立一个新的属性分类。这时,我们就需要注册新的属性分类。注意我们不能修改已经分好类的属性。

    下面的函数就是Delphi 5 (声明在dsgnintf.pas单元中)中用来定制属性分类的:

    type

      TPropertyCategoryClass = class of TPropertyCategory;

      RegisterPropertyInCategory(ACategoryClass: TPropertyCategoryClass;

        const APropertyName: String): TPropertyFilter; overload;

      RegisterPropertyInCategory(ACategoryClass: TPropertyCategoryClass;

        const APropertyName: String;AComponentClass: TClass): TPropertyFilter; overload;

      RegisterPropertyInCategory(ACategoryClass: TPropertyCategoryClass;

        const APropertyName: String;APropertyType: PTypeInfo): TPropertyFilter; overload;

      RegisterPropertyInCategory(ACategoryClass: TPropertyCategoryClass;

        APropertyType: PTypeInfo): TPropertyFilter; overload;

    RegisterPropertyInCategory函数有四种调用格式。我们可以以这四种格式指定一个分类过滤器:

    (1)属性名

    (2)对象类型和属性名

    (3)属性类型和属性名

    (4)属性类型

属性名支持通配符,比如我们把所有符合“My*”的属性分成一个特殊的分类。同时要想注册一系列的属性名或属性类型过滤器,可以使用RegisterPropertiesInCategory函数, 用CategoryClass和常数数列作为参数。我们可以使用下列预定义的类别对象 (在DESNINTF.PAS中定义的): TActionCategory, TDataCategory, TDatabaseCategory, TDragNDropCategory, THelpCategory, TLayoutCategory, TLegacyCategory, TLinkageCategory, TLocaleCategory, TLocalizableCategory, TMiscellaneousCategory, TVisualCategory, TInputCategory, 或 TMidasCategory (在MIDREG.PAS中定义的)。

添加属性到预定义的类别

    例如,要想设定一个新的名为TATButton的控件的About属性为TVisualCategory属性类别,可以使用下列代码:

    unit button;

    interface

    uses

      Windows, Messages, SysUtils, Classes,

      Graphics, Controls, Forms, Dialogs,

      StdCtrls, DsgnIntf;

    Type

      TATButton = class(TButton)

      Private

      { Private declarations }

      FAbout: String;

      Published

      { Published declarations }

      property About: String read FAbout write FAbout;

    end;

    procedure Register;

    implementation

    procedure Register;

    begin

      RegisterPropertyInCategory(

      TVisualCategory,TATButton,'About');

      RegisterComponents('Property Category', [TATButton]);

    end;

    end.

    下图3.5就是运行结果:

    上面我们用了系统预定义的分类对象,接下来的问题是,如果我们不知道要传递的分类对象的类型名,如何传递CategoryClass参数?

    通过调用PropertyCategoryList函数可以得到全部TPropertyCategory对象列表。每个TPropertyCategory对象都有Name和Description方法可以用来确定要添加的属性类别。但是又有一个问题,我们无法把TPropertyCategory的实例作为第一个参数传递给RegisterPropertyInCategory函数。我们需要类引用而不是实例。而传递ClassType的值也不能解决问题,这个方法只能返回TClass而不是TPropertyCategory。我们无法使用AS操作符把TClass映射为TPropertyCategory,因为AS只对实例有效。幸好我们可以使用强制类型映射,就象下面代码所示意的那样:

    unit button;

    interface

    uses

      Windows, Messages, SysUtils, Classes, Graphics,

      Controls, Forms, Dialogs, StdCtrls, DsgnIntf;

    Type

      TATButton = class(TButton)

      Private

      { Private declarations }

      FAbout: String;

      Published

      { Published declarations }

      property About: String read FAbout write FAbout;

    end;

    procedure Register;

    implementation

    procedure Register;

    var

      PropertyCategories: TPropertyCategoryList;

      i: Integer;

    begin

      PropertyCategories := PropertyCategoryList;

      for i:=0 to Pred(PropertyCategories.Count) do

      if PropertyCategories.Categories[i].Name = 'Visual' then

        RegisterPropertyInCategory(// 强制类型映射 TPropertyCategoryClass(

          PropertyCategories.Categories[i].ClassType), TATButton,'About');

        RegisterComponents('Property Category', [TATButton]);

    end;

    end.

    属性过滤器

    仔细研究过RegisterPropertyInCategory函数内部机制后,就会发现其实上面的问题还有其他的解决办法。这就是通过动态建立一个新的属性过滤器(TPropertyFilter)并添加这个过滤器到属性类别的实例中去。代码如下:

    unit button;

    interface

    uses

      Windows, Messages, SysUtils, Classes, Graphics,

      Controls, Forms, Dialogs, StdCtrls, DsgnIntf;

    Type

      TATButton = class(TButton)

      Private

    { Private declarations }

      FAbout: String;

      Published

      { Published declarations }

      property About: String read FAbout write FAbout;

    end;

    procedure Register;

    implementation

    procedure Register;

    var

      PropertyCategories: TPropertyCategoryList;

      i: Integer;

    begin

      for i:=0 to Pred(PropertyCategories.Count) do

        if PropertyCategories.Categories[i].Name =

          'Visual' then

           PropertyCategories.Categories[i].Add(

           TPropertyFilter.Create('About', TATButton, nil));

           RegisterComponents('Property Category', [TATButton]);

    end;

    end.

    在这里,属性过滤器起到了连接属性和对象类型的作用。

    同时注册多个属性

    如果想同时注册一组属性的话,可以使用RegisterPropertiesInCategory函数的重载版本,用CategoryClass和一组常数作为参数:

    function RegisterPropertiesInCategory(

    ACategoryClass: TPropertyCategoryClass;

      const AFilters: array of const):

      TPropertyCategory; overload;

    function RegisterPropertiesInCategory(

      ACategoryClass: TPropertyCategoryClass;

      AComponentClass: TClass; const AFilters:

      array of String): TPropertyCategory; overload;

    function RegisterPropertiesInCategory(

      ACategoryClass: TPropertyCategoryClass;

      APropertyType: PTypeInfo; const AFilters:

      array of String): TPropertyCategory; overload;

    注意如果想注册一个或多个属性到超过一个的类别中的话,必须按不同类别分别调用RegisterPropertyInCategory或RegisterPropertiesInCategory函数。

    属性是否已分类?

    要想知道一个对象的属性是否已经分了类别的话,可以调用IsPropertyInCategory函数:

    IsPropertyInCategory(CategoryClass,

      ComponentClass, PropertyName): Boolean;

    IsPropertyInCategory(CategoryClass,

      ComponentClassName, PropertyName): Boolean;

    用户定制的属性类别

    要想定义一个新的属性类别,需要从TPropertyCategory衍生一个新类,并实现两个类方法: Name和Description。注意这里说的是类方法,Object Inspector 需要在没有属性类实例情况下调用Name和Description方法(换句话说,类方法可以使我们不用Create就可以直接调用TPropertyCategory.Name和TPropertyCategory.Description方法)。类方法的实现很简单,只要在方法前加上关键词class就行了。

    下面的例子实现了一个新的属性类别TDemoPropertyCategory, 它的Name类方法返回"DEMO",Description类方法返回"DEMO Property Category":

    unit button;

    interface

    uses

      Windows, Messages, SysUtils, Classes, Graphics,

      Controls, Forms, Dialogs, StdCtrls, DsgnIntf;

    Type

      TATButton = class(TButton)

      Private

    { Private declarations }

      FAbout: String;

      Published

      { Published declarations }

      property About: String read FAbout write FAbout;

    end;

    procedure Register;

    implementation

    type

      TDemoPropertyCategory = class(TPropertyCategory)

      Public

      //关键词class表示实现的是类方法

      class function Name: string; override;

      class function Description: string; override;

    end;

    class function TDemoPropertyCategory.Description:

    string;

    begin

      Result := 'Demo Property Category';

    end;

    class function TDemoPropertyCategory.Name: string;

    begin

      Result := 'Demo';

    end;

    procedure Register;

    begin

      RegisterPropertyInCategory(

      TDemoPropertyCategory,TATButton,'About');

      RegisterComponents('Demo', [TATButton]);

    end;

    end.

    控件安装后的结果如下图所示:

    注意:Name类方法完全可以返回一个已经定义好的名字,比如"Visual",这时你会得到两个同样名字的属性类别。

    DSGNINTF和发布

    Delphi 5 README.TXT里面提到DSGNINTF.DCU今后将不再随Delphi发布了。因此,控件开发者应该考虑把设计时和运行时代码放在不同的单元中。运行时代码也不能引用这个单元。

    我们也可以编译DSGNINTF.PAS后发布。但这只能作为一个权宜之计,因为无法应用到将来的版本。

    结论

    属性分类是Object Inspector一个很有用增强特性。同时用户定制类别的能力对于更好的管理控件设计也有好处,但要注意的是如果每个人都引入一大堆自定义的类别,可能会引起很大的混乱。因此,一定要谨慎的使用这项技术。

猜你喜欢

转载自www.cnblogs.com/jijm123/p/12623963.html