BCB的元件编辑器

Delphi Open Tools API 浅探 - 元件编辑器

savetime2k@yahoo.com  2004.1.28
http://savetime.delphibbs.com


今天开始学习元件编辑器,感觉比属性编辑器简单许多,但还是遇到了一些疑问。如果你能解答文中记录的问题,请告诉我答案,谢谢!


目 录
===============================================================================
⊙ TBaseComponentEditor class
⊙ IComponentEditor interface
⊙ TComponentEditor class
⊙ TComponentEditor.ExecuteVerb 方法
⊙ TComponentEditor.PrepareItem 方法
⊙ TComponentEditor.Edit 方法
⊙ TComponentEditor.IsInInlined 方法
⊙ TDefaultEditor class
⊙ TSelectionEditor class
===============================================================================

本文排版格式为:
    正文由窗口自动换行;所有代码以 80 字符为边界;中英文字符以空格符分隔。

(作者保留对本文的所有权利,未经作者同意请勿在在任何公共媒体转载。)


正 文
===============================================================================
⊙ TBaseComponentEditor class
===============================================================================
TBaseComponentEditor 是所有元件编辑器的基类,它的构造函数由 Delphi IDE 在选中一个元件时被 IDE 调用。所有的元件编辑器必须实现 TBaseComponentEditor.Create 函数和 IComponentEdiotr 接口。元件编辑器的构造函数中传入当前选中的元件和 IDesigner 接口。

  { DesignIntf.pas }
  TBaseComponentEditor = class(TInterfacedObject)
  public
    constructor Create(AComponent: TComponent; ADesigner: IDesigner); virtual;
  end;

(* 为什么要以 TInterfacedObject 为基类呢?)

===============================================================================
⊙ IComponentEditor interface
===============================================================================
IComponentEditor 定义了元件编辑器需要实现的接口:

  IComponentEditor = interface
    ['{ECACBA34-DCDF-4BE2-A645-E4404BC06106}']
    procedure Edit;
      双击元件时触发
    procedure ExecuteVerb(Index: Integer);
      执行一个自定义动作
    function GetVerb(Index: Integer): string;
      获取自定义动作的字符串名称
    function GetVerbCount: Integer;
      获取自定义动作的数量
    procedure PrepareItem(Index: Integer; const AItem: IMenuItem);
      自定义动作的菜单项
    procedure Copy;
      此方法在元件信息被拷贝到剪贴板之后被调用
      (* 试验结果:好像不会被调用?)
    function IsInInlined: Boolean;
      返回元件的 Owner 是否是 csInline 状态
    function GetComponent: TComponent;
      获取当前选中的元件
    function GetDesigner: IDesigner;
      获取当前 IDE Designer
  end;

===============================================================================
⊙ TComponentEditor class
===============================================================================
TComponentEditor 是自定义元件编辑器的基类,它定义了 IComponentEditor 接口的虚方法,后继类只需要重载这些虚方法。

  { DesignEditors.pas }
  TComponentEditor = class(TBaseComponentEditor, IComponentEditor)
  private
    FComponent: TComponent;
    FDesigner: IDesigner;
  public
    procedure Edit; virtual;
    procedure ExecuteVerb(Index: Integer); virtual;
    function GetComponent: TComponent;
    function GetDesigner: IDesigner;
    function GetVerb(Index: Integer): string; virtual;
    function GetVerbCount: Integer; virtual;
    function IsInInlined: Boolean;
    procedure Copy; virtual;
    procedure PrepareItem(Index: Integer; const AItem: IMenuItem); virtual;
    property Component: TComponent read FComponent;
    property Designer: IDesigner read GetDesigner;
  end;

TComponentEditor 在构造函数中保存当前编辑的元件指针和 IDE 接口指针,可以使用 Component 属性和 Designer 属性访问。

  { TComponentEditor }
  constructor Create(AComponent: TComponent; ADesigner: IDesigner);
  begin
    FComponent := AComponent;  // 保存当前元件指针
    FDesigner := ADesigner;    // 保存当前 IDE 接口
  end;

注意:如果元件编译器中的任意方法修改了 Component 元件的属性,必须调用 Designer.Modified 方法通知 IDE 刷新 Object Inspector。

TComponentEditor 需要用 RegisterSelectionEditor 注册后才能使用:

  { DesignIntf.pas }
  procedure RegisterComponentEditor(ComponentClass: TComponentClass;
    ComponentEditor: TComponentEditorClass);

===============================================================================
⊙ TComponentEditor.ExecuteVerb 方法
===============================================================================
ExecuteVerb 方法和 GetVerbCount / GetVerb 方法互相配合,完成右键点击元件时的快捷菜单操作。

GetVerbCount 返回自定义动作的数量,缺省值为 0,即没有自定义动作。GetVerb 返回第 Index 项的菜单字符串。ExecuteVerb 执行第 Index 项动作。重载这些方法以实现自定义的元件编辑菜单。

  { TComponentEditor }
  function GetVerbCount: Integer; virtual;
  function GetVerb(Index: Integer): string; virtual;
  procedure ExecuteVerb(Index: Integer); virtual;

注意:如果在 ExecuteVerb 方法中修改了元件属性,必须调用 Designer.Modified 方法通知 IDE 刷新 Object Inspector。

===============================================================================
⊙ TComponentEditor.PrepareItem 方法
===============================================================================
PrepareItem 在 GetVerb 之后被调用,用于建立更复杂的动作菜单项。IDE 传入的 Index 参数是指菜单序号,AItem 参数是新建菜单项的接口句柄。

    procedure PrepareItem(Index: Integer; const AItem: IMenuItem); virtual;

IMenuItems 接口在 DesignMenus.pas 中定义,可以被元件设计者使用的主要方法有:

  IMenuItem = interface(IMenuItems)
    ['{DAF029E1-9592-4B07-A450-A10056A2B9B5}']
    // 新增子菜单
    function AddItem(const ACaption: WideString; AShortCut: TShortCut;
      AChecked, AEnabled: Boolean; AOnClick: TNotifyEvent = nil;
      hCtx: THelpContext = 0; const AName: string = ''): IMenuItem; overload;
    function AddItem(AAction: TBasicAction;
      const AName: string = ''): IMenuItem; overload;
    // 新增菜单分隔符
    function AddLine(const AName: string = ''): IMenuItem;
    // 设置菜单项是否被 Checked
    property Checked: Boolean read GetChecked write SetChecked;
    // 设置菜单项是否 Enabled
    property Enabled: Boolean read GetEnabled write SetEnabled;
    // 设置菜单项是否 Visible
    property Visible: Boolean read GetVisible write SetVisible;
  end;

===============================================================================
⊙ TComponentEditor.Edit 方法
===============================================================================
TComponentEditor.Edit 方法在元件被双击时调用,重载此方法可以实现自定义的属性编辑器。缺省的操作是调用 TComponentEditor 的第一个动作。

  { TComponentEditor }
  procedure TComponentEditor.Edit; virtual;
  begin
    if GetVerbCount > 0 then ExecuteVerb(0);  // 缺省调用第一个动作
  end;

注意:如果在 Edit 方法中修改了元件属性,必须调用 Designer.Modified 方法通知 IDE 刷新 Object Inspector。

===============================================================================
⊙ TComponentEditor.IsInInlined 方法
===============================================================================
IsInInlined 判断当前元件是否处于内嵌的 top-class 对象中。例如,如果当前元件位于嵌入在 Form 中的 Frame 中,则返回 True,否则返回 False。IsInInlied 方法不是虚方法,不能被重载。在 TComponentEditor 中它判断 Owner 是否包含 csInline 状态。

  { TComponentEditor }
  function TComponentEditor.IsInInlined: Boolean;
  begin
    Result := csInline in Component.Owner.ComponentState;
  end;

(* 这个方法可用在何处?估计是被 IDE 使用。缺省不能在嵌入表单中的 Frame 中插入元件。)

===============================================================================
⊙ TDefaultEditor class
===============================================================================
TDefaultEditor 是 IDE 缺省的元件编辑器,提供对元件双击事件的增强处理。它重载了 Edit 方法实现在双击元件时生成元件的缺省事件代码。

TDefaultEditor 继承自 TComponentEditor 和 IDefaultEditor:

  { DesignEditors.pas }
  TDefaultEditor = class(TComponentEditor, IDefaultEditor)

  { DesignIntf.pas }
  IDefaultEditor = interface(IComponentEditor)
    ['{5484FAE1-5C60-11D1-9FB6-0020AF3D82DA}']
  end;

IDefaultEditor 没有声明任何方法。(我猜想) 这是为了标识当前的元件编辑器是从 TDefaultEditor 继承下来的,但是为什么不直接使用 TObject.InheritsFrom 来识别呢?

TDefaultEditor 在 Edit 方法中调用 GetComponentProperties 函数获取属性列表。GetComponentProperties 调用 TDefaultEditor.CheckEdit 过滤合适的方法:

  { TDefaultEditor.pas }
  procedure CheckEdit(const Prop: IProperty);

CheckEdit 方法将从 GetComponentProperties 传进来的 IProperty 参数传递给 TDefaultEditor.EditProperty 虚方法:

  { TDefaultEditor }
  procedure EditProperty(const Prop: IProperty; var Continue: Boolean); virtual;

EditProperty 使用 IProperty 判断属性的名称是否是:ONCREATE、ONCHANGE、ONCHANGE、ONCLICK 之一,如果是则将该 IProperty 句柄保存在私有成员 FFirst、FBest 之中。EditProperty 还传入一个 Continue 参数,如果设置为 False,则 Edit 方法不会执行任何操作。否则 Edit 方法调用 IProperty 的 Edit 方法,生成事件句柄的代码。

* 可以从 TDefaultEditor 继承以实现特殊的元件编辑操作。

===============================================================================
⊙ TSelectionEditor class
===============================================================================
TSelectionEditor 与 TComponentEditor 类似,它实现 ISelectionEditor 接口,可以为 IDE 中选中的一批不同类型的元件生成动作菜单项。比如,如果选中表单上若干个从 TControl 类派生的不同类型的元件,TSelectionEditor 会被创建。

  { DesignEditors.pas }
  TSelectionEditor = class(TBaseSelectionEditor, ISelectionEditor)

TSelectionEditor 的 GetVerb、GetVerbCount、PrepareItem 方法与 TComponentEditor 类似,只是 ExecuteVerb 方法有些不同。因为 TSelectionEditor 实现对一批元件的操作,所以 ExecuteVerb 中多传递了一个元件列表接口句柄:

  { TSelectionEditor }
  procedure ExecuteVerb(Index: Integer; const List: IDesignerSelections);

IDesignerSelections 代表当前被选中的一批元件,可被使用的主要属性是 Count 和 Items 数组。Count 表示选中的元件数量,Items 数组表示选中的元件。

  { DesignIntf.pas }
  IDesignerSelections = interface
    ['{7ED7BF30-E349-11D3-AB4A-00C04FB17A72}']
    function Add(const Item: TPersistent): Integer;
    function Equals(const List: IDesignerSelections): Boolean;
    function Get(Index: Integer): TPersistent;
    function GetCount: Integer;
    property Count: Integer read GetCount;
    property Items[Index: Integer]: TPersistent read Get; default;
  end;

* 为什么 Items 声明为 TPerisistent 而不是 TComponent ?

TSelectionEditor 还有一个 RequiresUnits 虚方法,需要重载以返回可能使用到的元件所在的单元名称,不太清楚这个方法如何使用,如果只使用标准的 VCL 元件应该不用重载,至少我设计 TControl 的 TSelectionEditor 没有出错。

  { TSelectionEditor }
  procedure RequiresUnits(Proc: TGetStrProc); virtual;

TSelectionEditor 需要用 RegisterSelectionEditor 注册后才能使用:

  { DesignIntf.pas }
  procedure RegisterSelectionEditor(AClass: TClass; AEditor:
    TSelectionEditorClass);

===============================================================================
⊙ 结 束
===============================================================================
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值