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

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

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

Code:

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

Code:

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;

 

procedureRegister;

 

implementation

 

 

procedureRegister;

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;

caseIndexof

0: //Действие при выборе первого определенного пункта меню

end;

end;

 

function TMyComponentEditor.GetVerb(Index: Integer): string;

begin

caseIndexof

0: Result := 'Demo Menu Item 1'; //Название первого пункта меню

end;

end;

 

function TMyComponentEditor.GetVerbCount: Integer;

begin

Result := 1;

end;

 

end.

 

 

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

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

Code:

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.

Code:

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;

 

Code:

IMenuItem = interface(IMenuItems)

['{DAF029E1-9592-4B07-A450-A10056A2B9B5}']

 

// public

functionName: 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

Code:

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: stringread GetPrivateDirectory;

property PropCount: Integer read FPropCount;

property Value: stringread GetValue write SetValue;

end;

 

 

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

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

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

Code:

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 - указывает, изменениезначениясвойстваможноотменить. Этонекасаетсявложенныхподсвойств.
· paFullWidthName - указывает Object Inspector, чтопрорисовказначениясвойстванетребуетсяиможнозанятьподимясвойствавсюдлинупанели.
· paVolatileSubProperties - установкаэтогозначенияуказывает, чтоприлюбомизменениисвойстванужноповторитьсборкуподсвойств (GetProperties)
· paVCL - ???
· paReference - указывает, чтосвойствоявляетсяуказателемначто-либо. Используетсявместес paSubProperties дляуказазанияотображенияобъекта, накотороессылаетсявкачествеподсвойств (TFont).
· paNotNestable - указывает, чтоотображатьзначениесвойствавмомент, когдаегоподсвойстваразвернуты - небезопасно (этотпунктмнепоканепонятен)

Методы GetXXXValue и SetXXXValue.

Используютсядлявнутреннейустановкиреальногозначениясвойства. Какправило, используютсяметодом GetValue и SetValue. Впринципе, всеэтиметодыужеопределенывклассе TPropertyEditor, ипереопределятьихненужно.

Метод Modified

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

Метод GetEditValue

возвращает true, еслизначениеможноредактировать

Метод GetIsDefault

возвращает true, еслизначениесвойствавтекущиймоментявляетсязначениемсвойствапоумолчанию. Т.е. методдолженвозвращать true, еслиНЕнужносохранятьзначениесвойствав .dfm файле.

Метод Activate

вызываетсяпривыборесвойствав Object Inspector. Прииспользованиипереопределенияэтогометодадляотображениязначениясвойстваисключительновмоментактивизациинужнобытьосторожным, еслиуказаныпараметрысвойства paSubProperties и paMultiSelect.

Метод AllEqual

вызываетсявсякийраз, когдавыделяетсяболееодногокомпонента. Еслиэтотметодвернет true, будетвызванметод GetValue, впротивоположномслучаебудетотображенапустаястрока. Вызываетсятолько, еслиуказаносвойство paMultiSelect. Очевидно, методдолженпроверятьсовпадениесвойствувсевыбранныхкомпонентовпутемопросаметоде GetComponent.

Метод AutoFill

вызываетсядляопределения, могутлиэлементыспискабытьвыбраныповозрастанию. Указывается, толькоеслиуказанпараметр paValueList.

Метод GetComponent

возвращаеткомпонентсзаданныминдексомизвыбранныхкомпонентов.

Метод GetEditLimit

возвращаетмаксимальноеколичествосимволов, которыеможноввестивтекстовоезначениесвойства. Поумолчанию 255.

Метод GetName

возвращаетимясвойства, вкоторомзнакиподчеркиваниязамененынапробелы. Методдолженпереопределятьсятолько, еслисвойствонепредназначенодляотображенияв Object Inspector

Метод GetComponentValue

возвращаетзначениесвойстватипа TComponent втомитольковтомслучае, еслисвойствоунаследованоот TComponent. Этотметодпереопределяетсявклассе TComponentEditor

Метод GetProperties

вызываетсядлякаждогоподсвойства, котороередактируется. Вметодпередаетсяпараметртипа TGetPropertyProc. Этоуказательнапроцедурудляобработкикаждогосвойства. Например, TClassProperty вызываетпроцедуру TGetPropertyProc длякаждого published элементакласса, а TSetProperty - длякаждогоэлементамножества. Т.е. прииспользованииподсвойстввыдолжныопределитьпроцедуру TGetPropertyProc, чтобыонаопределялакаждоеподсвойство.

Метод GetPropType

возвращаетуказательнаинформациюотипередактируемогосвойства (TypeInfo (Type))

Метод GetValue

возвращаетзначениесвойстваввидетекстовойстроки. Например, в TClassProperty этотметодпереопределендлявозвращениявкачестверезультатаименитипакласса (TStrings ит.п.).

Метод ValueAvailable

возвращает true, еслиможнополучитьдоступкзначениюсвойства, невызываяисключения.

Описаниядляостальныхметодовисвойств, ксожалению, найтинеудалось, поэтомуисследоватьихможнотолькоопытнымпутем.

Позавершениисозданияредакторасвойствнезабудьтезарегистрироватьеговнутриметода register вызовом

RegisterPropertyEditor(TypeInfo(<типсвойства>), <типкомпонента>, <имясвойства>, <типредакторасвойства>);

RegisterPropertyEditor(TypeInfo(TMyComponentsStringProperty), TMyComponent, '', TMCSPEditor);

Передаввместоименисвойствапустуюстроку, мыуказалитемсамым, чтоимяможетбытьлюбым. Такжепустуюстрокуможнопередатьвместоименикомпонента.

Вот, собственно, ивсе. Пишитесвойредакторсвойств, переопределяйтенужныеметодыивперед!

РаструсныйВладислав

 

Добавить комментарий

Не использовать не нормативную лексику.

Просьба писать ваши замечания, наблюдения и все остальное,
что поможет улучшить предоставляемую информацию на этом сайте.

ВСЕ КОММЕНТАРИИ МОДЕРИРУЮТСЯ ВРУЧНУЮ, ТАК ЧТО СПАМИТЬ БЕСПОЛЕЗНО!