Статьи Королевства Дельфи

Создание pop-up меню своего компонента и кое-что еще о классе TComponentExpert


Давайте рассмотрим создание простейшего одноуровневого контекстного меню на своем компоненте, которое будет открываться при щелчке правой кнопкой по нему в самом верху контекстного меню Delphi.

Прежде всего вам следует разделить код вашего компонента на Design-time и Run-time. Для этого перенесите ваш компонент в модуль, с названием, например, MyComponent.pas, а процедуры регистрации его в палитре компонентов (procedure Register и т.д.) в модуль, с названием, например, MyComponentReg. На такие меры приходится идти из-за того, что Borland не включила в исходные коды исходник файла Proxies.pas.

Итак, получим два файла:
MyComponent.pas:

unit MyComponent; interface uses SysUtils, Classes; type TMyComponent = class(TComponent) private { Private declarations } protected { Protected declarations } public { Public declarations } published { Published declarations } end;

MyComponentReg.pas

unit MyComponentReg; interface uses DesignIntf, DesignEditors, MyComponent, Classes, Dialogs; type TMyComponentEditor = class(TComponentEditor) private procedure ExecuteVerb(Index: Integer); override; function GetVerbCount: Integer; override; function GetVerb(Index: Integer): string; override; procedure Edit; override; end; procedure Register; implementation procedure Register; begin RegisterComponents('Samples', [TMyComponent]); RegisterComponentEditor(TMyComponent, TMyComponentEditor); end; { TMyComponentEditor } procedure TMyComponentEditor.Edit; begin ShowMessage('TMyComponent component v1.0 by Rastrusny Vladislav'); end; procedure TMyComponentEditor.ExecuteVerb(Index: Integer); begin inherited; case Index of 0: //Действие при выборе первого определенного пункта меню end; end; function TMyComponentEditor.GetVerb(Index: Integer): string; begin case Index of 0: Result := 'Demo Menu Item 1'; //Название первого пункта меню end; end; function TMyComponentEditor.GetVerbCount: Integer; begin Result := 1; end; end.

Рассмотрим теперь, что же тут написано. В первом файле просто определен компонент MyComponent. В нем вы определяете все свойства и методы вашего компонента. Все как обычно. Теперь - второй файл MyComponentReg. Он содержит процедуры регистрации компонента и процедуру регистрации редактора компонента (TComponentEditor). Этот редактор и будет отображать меню и прочие безобразия. Итак:

Определяем TMyComponentEditor как потомка TComponentEditor. Сам по себе этот класс является "воплотителем" интерфейса IComponentEditor, хотя нам все равно. Для того, чтобы все это заработало нам нужно будет переопределить стандартные методы класса TComponentEditor. Рассмотрим его:

type TComponentEditor = class(TBaseComponentEditor, IComponentEditor) private FComponent: TComponent; FDesigner: IDesigner; public constructor Create(AComponent: TComponent; ADesigner: IDesigner); override; procedure Edit; virtual; function GetVerbCount: Integer; virtual; function GetVerb(Index: Integer): string; virtual; procedure ExecuteVerb(Index: Integer); virtual; procedure Copy; virtual; procedure PrepareItem(Index: Integer; const AItem: IMenuItem); virtual; property Component: TComponent; property Designer: IDesigner; end;

Конструктор нам переопределять не нужно. Поэтому начнем с описания метода Edit. Метод Edit вызывается при двойном щелчке по компоненту. Вот так просто! При двойном щелчке на компоненте! Если метод не определен, то при двойном щелчке будет выполнен первый пункт меню, которое вы определили. Метод GetVerbCount: Integer должен возвращать количество определенных вами пунктов меню. Метод GetVerb(Index: Integer): string должен возвращать название пункта меню № Index. Метод ExecuteVerb(Index: Integer) вызывается при щелчке на пункте меню, определенном вами. Index - номер меню из метода GetVerb. В нем вы определяете действия, которые будут происходить при нажатии на ваш пункт меню. Метод Copy вызывается при копировании вашего компонента в буфер обмена Свойство Component как вы уже наверное догадались позволяет получить доступ к компоненту, на котором щелкнули мышью и т.п. Метод PrepareItem(Index: Integer; const AItem: IMenuItem) вызывается для каждого определенного вами пункта меню № Index и через параметр AItem передает сам пункт меню для настройки. Для работы нам нужно будет рассмотреть саму реализацию интерфейсас IMenuItem. Он определен в модуле DesignMenus.pas и является потомком интерфейса IMenuItems.


IMenuItems = interface ['{C9CC6C38-C96A-4514-8D6F-1D121727BFAF}'] // public function SameAs(const AItem: IUnknown): Boolean; function Find(const ACaption: WideString): IMenuItem; function FindByName(const AName: string): IMenuItem; function Count: Integer; property Items[Index: Integer]: IMenuItem read GetItem; procedure Clear; 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 InsertItem(const ACaption: WideString; AShortCut: TShortCut; AChecked, AEnabled: Boolean; AOnClick: TNotifyEvent = nil; hCtx: THelpContext = 0; const AName: string = ''): IMenuItem; overload; function InsertItem(Index: Integer; const ACaption: WideString; AShortCut: TShortCut; AChecked, AEnabled: Boolean; AOnClick: TNotifyEvent = nil; hCtx: THelpContext = 0; const AName: string = ''): IMenuItem; overload; function InsertItem(AAction: TBasicAction; const AName: string = ''): IMenuItem; overload; function InsertItem(Index: Integer; AAction: TBasicAction; const AName: string = ''): IMenuItem; overload; function AddLine(const AName: string = ''): IMenuItem; function InsertLine(const AName: string = ''): IMenuItem; overload; function InsertLine(Index: Integer; const AName: string = ''): IMenuItem; overload; end;
IMenuItem = interface(IMenuItems) ['{DAF029E1-9592-4B07-A450-A10056A2B9B5}'] // public function Name: TComponentName; function MenuIndex: Integer; function Parent: IMenuItem; function HasParent: Boolean; function IsLine: Boolean; property Caption: WideString; property Checked: Boolean; property Enabled: Boolean; property GroupIndex: Byte; property HelpContext: THelpContext; property Hint: string; property RadioItem: Boolean; property ShortCut: TShortCut; property Tag: LongInt; property Visible: Boolean; end;
Начнем с конца. Т.е. с IMenuItem. Как видно, почти все члены интерфейса соответствуют членам класса TMenuItem. Т.е. обратившись в методе PrepareItem к AItem.Enabled:=false мы запретим выбор этого элемента меню. Что же касается класса TMenuItems, то они, видимо, предназначены для манипулирования элементом меню в качестве родительского для нескольких других. Думаю, в них опытным путем разобраться тоже не составит труда.

Что же касается процедуры RegisterComponentEditor, то она принимает два параметра: первый - класс компонента, для которого создается редактор свойств и второй - собственно сам класс редактора свойств.


Содержание раздела