Давайтерассмотримсозданиепростейшегоодноуровневогоконтекстногоменюнасвоемкомпоненте, котороебудетоткрыватьсяприщелчкеправойкнопкойпонемувсамомверхуконтекстногоменю 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);
Передаввместоименисвойствапустуюстроку, мыуказалитемсамым, чтоимяможетбытьлюбым. Такжепустуюстрокуможнопередатьвместоименикомпонента.
Вот, собственно, ивсе. Пишитесвойредакторсвойств, переопределяйтенужныеметодыивперед!
РаструсныйВладислав
Просьба писать ваши замечания, наблюдения и все остальное,
что поможет улучшить предоставляемую информацию на этом сайте.
ВСЕ КОММЕНТАРИИ МОДЕРИРУЮТСЯ ВРУЧНУЮ, ТАК ЧТО СПАМИТЬ БЕСПОЛЕЗНО!