S
savetime
Unregistered / Unconfirmed
GUEST, unregistred user!
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);
===============================================================================
⊙ 结 束
===============================================================================
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);
===============================================================================
⊙ 结 束
===============================================================================