Вопросы и ответы - Delphi - Delphi IDE, компилятор, отладчик, редактор - Библиотека программиста
Пользователь

Добро пожаловать,

Регистрация или входРегистрация или вход
Потеряли пароль?Потеряли пароль?

Ник:
Пароль:

Меню сайта




Ваше мнение
Какой язык программирования вы используете ?

ASP
Delphi
C/C++
Basic
PHP
Pascal
Java
Другой


Результаты
Другие опросы

Всего голосов: 1970
Комментарии: 10


Наши партнеры



Статистика




Programming books  Download software  Documentation  Scripts  Content Managment Systems(CMS)  Templates  Icon Sets  Articles  Contacts  Voting  Site Search




Вопросы и ответы - Delphi - Delphi IDE, компилятор, отладчик, редактор

 
ToolsAPI: Компоненты
Создание 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, то она принимает два параметра: первый - класс компонента, для которого создается редактор свойств и второй - собственно сам класс редактора свойств.



Создание редакторов свойств

Для создания редактора свойств нужно написать класс, унаследованный от TBasePropertyEditor. Но мы рассмотрим более функционального его потомка TPropertyEditor


Код
TPropertyEditor = class(TBasePropertyEditor, IProperty, IProperty70)

protected

procedure SetPropEntry(Index: Integer; AInstance: TPersistent;

APropInfo: PPropInfo); override;

protected

function GetFloatValue: Extended;

function GetFloatValueAt(Index: Integer): Extended;

function GetInt64Value: Int64;

function GetInt64ValueAt(Index: Integer): Int64;

function GetMethodValue: TMethod;

function GetMethodValueAt(Index: Integer): TMethod;

function GetOrdValue: Longint;

function GetOrdValueAt(Index: Integer): Longint;

function GetStrValue: string;

function GetStrValueAt(Index: Integer): string;

function GetVarValue: Variant;

function GetVarValueAt(Index: Integer): Variant;

function GetIntfValue: IInterface;

function GetIntfValueAt(Index: Integer): IInterface;

procedure Modified;

procedure SetFloatValue(Value: Extended);

procedure SetMethodValue(const Value: TMethod);

procedure SetInt64Value(Value: Int64);

procedure SetOrdValue(Value: Longint);

procedure SetStrValue(const Value: string);

procedure SetVarValue(const Value: Variant);

procedure SetIntfValue(const Value: IInterface);

protected

{ IProperty }

function GetEditValue(out Value: string): Boolean;

function HasInstance(Instance: TPersistent): Boolean;

{ IProperty70 }

function GetIsDefault: Boolean; virtual;

public

constructor Create(const ADesigner: IDesigner; APropCount: Integer); override;

destructor Destroy; override;

procedure Activate; virtual;

function AllEqual: Boolean; virtual;

function AutoFill: Boolean; virtual;

procedure Edit; virtual;

function GetAttributes: TpropertyAttributes; virtual;

function GetComponent(Index: Integer): TPersistent;

function GetEditLimit: Integer; virtual;

function GetName: string; virtual;

procedure GetProperties(Proc: TGetPropProc); virtual;

function GetPropInfo: PPropInfo; virtual;

function GetPropType: PTypeInfo;

function GetValue: string; virtual;

function GetVisualValue: string;

procedure GetValues(Proc: TGetStrProc); virtual;

procedure Initialize; override;

procedure Revert;

procedure SetValue(const Value: string); virtual;

function ValueAvailable: Boolean;

property Designer: IDesigner read FDesigner;

property PrivateDirectory: string read GetPrivateDirectory;

property PropCount: Integer read FPropCount;

property Value: string read GetValue write SetValue;

end;





Предположим, нам нужно создать редактор для текстового свойства, при нажатии кнопки "…" в Object Inspector.



Объявим специальный тип этого свойства TMyComponentStringProperty = string;



Далее, в компоненте укажем свойство данного типа property MyProperty: TMyComponentStringProperty, далее в Run-time части компонента (MyComponentReg.pas) объявим класс TMyCSPEditor (в переводе: TMyComponentStringPropertyEditor), унаследовав его от класса TStringProperty, который в свою очередь является потомком рассматриваемого класса TPropertyEditor: type TMyCSPEditor = class(TStringProperty) . Переопределим в нем несколько методов таким образом (фрагменты файла):

Код
type

TVRSIDBListViewExcludeColumnsPropertyEditor = class(TStringProperty)

function GetAttributes: TPropertyAttributes; override;

procedure Edit; override;

end;





procedure TVRSIDBListViewExcludeColumnsPropertyEditor.Edit;

var Text: string;

begin

if InputQuery('Введите строковое значение',Text)=False then Exit;

Self.SetValue(Text);

end;



function TVRSIDBListViewExcludeColumnsPropertyEditor.GetAttributes: TPropertyAttributes;

begin

Result:=[paDialog];

end;







Итак, приступаем к рассмотрению методов класса TPropertyEditor. Начнем с тех, которые мы уже использовали.



Метод Edit. Просто вызывается при щелчке на кнопке "…" в Object Inspector. В TStringProperty не переопределен.



Метод SetValue(Text: string). Должен устанавливать значение свойства в переданную строку. В TStringProperty переопределен. Этот метод вызывается самим Object Inspector, когда пользователь вводит значение поля. Вы можете переопределить этот метод для установки вашего свойства в зависимости от значения, введенного пользователем. Если вы обнаруживаете ошибку в переданном параметре - вызовите исключение.



Метод GetAttributes: TPropertyAttributes. Задает параметры свойства. Рассмотрим их по порядку.



paValueList - указывает, что редактор свойств возвращает список допустимых значений свойства через метод GetValues. В редакторе свойств рядом со свойством появляется раскрывающийся список

paSortList - указывает, что список, возвращенный GetValues нужно сортировать

paSubProperties - указывает, что у свойства имеются подсвойства (типа подсвойства Name у свойства Font класса TFont). Подсвойства, если этот флаг установлен, должны возвращаться методом GetProperties.

paDialog - указывает, что рядом со свойством должна быть кнопка "…", по нажатию которой вызывается метод Edit для редактирования значения свойства. Что мы и указали в нашем примере.

paMultiSelect - Разрешает отображать свойство в Object Inspector, даже если выделено более одного объекта

paAutoUpdate - указывает, что метод SetValue нужно вызывать при каждом изменении значения свойства, а не после нажатия Enter или выхода из Object Inspector (Пример: свойство Caption у формы изменяется одновременно с набором на клавиатуре)

paReadOnly - указывает, что значение через Object Inspector изменить нельзя. Оно устанавливается в классе TClassProperty, от которого унаследованы все классовые редакторы свойств типа TStrings, TFont и т.п. При установке рядом со значением свойства отображается строка, возвращенная методом GetValue и значение это изменить нельзя.

paRevertable - указывает, изменение значения свойства можно отменить. Это не