Как бы странно это не звучало, но построение редактора компонента несколько проще, чем построение редактора свойства. Отчасти потому, что дизайнер форм обеспечивает прямой доступ к компоненту, подлежащему редактированию, посредством свойства Component, которое определено в классе TComponentEditor, который реализует интерфейс IComponentEditor.
Пример 13.3. Объявление класса TComponentEditor из модуля DesignEditors
TComponentEditor = class(TBaseComponentEditor, IComponentEditor) private FComponent: TComponent; FDesigner: IDesigner; public constructor Create(AComponent: TComponent; ADesigner: IDesigner); override; 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;
Пример 13.4. Объявление интерфейса IComponentEditor из модуля DesignIntf
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; function GetComponent: TComponent; function GetDesigner: IDesigner; end;
Понятие интерфейса будет рассмотрено позже, сейчас же достаточно понимать интерфейс, как требования к интерфейсной части класса. Фраза «реализует интерфейс» означает, что в классе реализованы все атрибуты (методы, свойства), объявленные в интерфейсе.
В отличие от редакторов свойств, редакторы компонентов не должны заботиться о строковом представлении значений свойств компонента. Однако, при фактическом выполнении редактирования редакторы компонентов аналогичны редакторам свойств в том, что это выполняется путем перекрытия определенных методов.
Пример 13.5.
Для лучшего понимания дальнейшее рассмотрение технологии построения редактора компонентов будем вести с использованием уже знакомого по предыдущим лекциям компонента TCompass.
Редактор компонента будет предоставлять возможность редактирования сразу нескольких свойств (как это и характерно для всех редакторов компонентов): азимута и меток сторон света, другие пункты меню предназначены для установки компаса в одно из восьми положений сторон света (четыре основные: север, юг, запад, восток, а также четыре промежуточные: северо-запад, юго-восток, северо-восток и юго-запад).
Пример 13.6. Объявим новый редактор:
Interface Uses …, DesignEditors, DesignIntf; TCompasEditor=class(TComponentEditor) … end;
Когда компонент выбран, и пользователь нажимает правую кнопку мыши или сочетание клавиш <Alt+F10>, то редактор форм отображает контекстное меню. Для определения количества пунктов меню редактор форм вызывает метод GetVerbCount редактора компонента. Другими словами пользовательский редактор компонента перекрывает этот метод для возвращения количества пунктов, подлежащих добавлению в контекстное меню.
Пример 13.7.
В рассматриваемом примере понадобиться девять пунктов меню: один пункт – для вызова формы и восемь пунктов – для установки сторон света.
Из дизайнерских соображений добавим пункт-разделитель между пунктом меню вызова формы и пунктами установки сторон света. С учетом этого понадобиться десять пунктов меню.
Пример 13.8. Объявим и реализуем метод GetVerbCount:
Interface Uses …, DesignEditors, DesignIntf; TCompasEditor=class(TComponentEditor) public function GetVerbCount: Integer; override; … end; implementation function TCompasEditor.GetVerbCount: Integer; begin Result:=10; end;
Обратите внимание, что в приведенном примере метод GetVerbCount не вызывается реализацию предка с использованием ключевого слова inherited, поскольку все равно явно возвращает свой результат.
Если метод GetVerbCount возвращает число, превышающее ноль, то дизайнер формы вызывает метод GetVerb редактора компонента по одному разу для каждого пункта, подлежащего добавлению. Таким образом метод GetVerb будет вызван со параметром от 0 до GetVerbCount-1.
Метод GetVerb отвечает за указание строк, которые должны быть использованы для новых пунктов меню. Когда метод вызывается, он передает индексный параметр, указывающий на то, какой пункт меню должен быть добавлен. Этот метод перекрывается для возвращения Проектировщику форм строкового значения, которое основано на параметре.
Пример 13.9. Реализация метода GetVerb редактора
function TCompasEditor.GetVerb(Index: Integer): string; begin case Index of 0: Result:='Compass Editor...'; 1: Result:='-'; 2: Result:='&N'; 3: Result:='NE'; 4: Result:='E'; 5: Result:='SE'; 6: Result:='S'; 7: Result:='SW'; 8: Result:='W'; 9: Result:='NW'; end; end;
Таким образом, после регистрации редактора компонентов верхняя (пользовательская) часть всплывающего меню будет выглядеть следующим образом:
Обратите внимание, что некоторые буквы в пунктах меню стали подчеркнутыми. Это результат назначения средой разработки так называемых «быстрых клавиш». Это значит, что после вызова контекстного меню можно нажатием, например, клавиши <С> вызвать пункт меню Compass Editor… Некоторые быстрые клавиши присвоены средой разработки по возможности автоматически, а некоторые (например пункт меню N) указаны явно с помощью символа & (ампресанд) перед символом, которые нужно сделать «быстрой клавишей».
Поскольку среда разработки автоматически присваивает горячие клавиши пунктам меню не всегда предсказуемо и логично с точки зрения разработчика, то имеет смысл указывать их явно с использованием знака амперсанда (&).
Пример 13.10. Реализация метода GetVerb редактора с указанными явно горячими клавишами
function TCompasEditor.GetVerb(Index: Integer): string; begin case Index of 0: Result:='Compass Editor...'; 1: Result:='-'; 2: Result:='&N'; 3: Result:='NE'; 4: Result:='&E'; 5: Result:='SE'; 6: Result:='&S'; 7: Result:='SW'; 8: Result:='&W'; 9: Result:='NW'; end; end;
В этом случае меню выглядит вполне логично: основные стороны света снабжены горячими клавишами, а промежуточные – нет:
Когда пользователь выбирает один из вновь добавленных пунктов меню, вызывается метод ExecuteVerb редактора компонента. Этот метод получает в качестве своего единственного параметра индекс выбранного пункта меню. Следовательно, метод ExecuteVerb отвечает за осуществление соответствующего действия, исходя из выбранного пункта меню.
Пример 13.11. Реализация метода ExecuteVerb
procedure TCompasEditor.ExecuteVerb(Index: Integer); var N, E, S, W: string; begin inherited; case Index of 0: begin dlgCompas:=TdlgCompas.Create(Application); try dlgCompas.Caption:= Format('%s.%s',[ Component.Owner.Name, Component.Name ]); with dlgCompas, TCompass(Component) do begin Compass1.Azimuth:=Azimuth; EditN.Text:=DirNames.N; EditE.Text:=DirNames.E; EditS.Text:=DirNames.S; EditW.Text:=DirNames.W; if dlgCompas.ShowModal=mrOk then begin TCompass(Component).Azimuth:=dlgCompas.Compass1.Azimuth; N:=system.copy(EditN.Text,1,1); E:=system.copy(EditE.Text,1,1); S:=system.copy(EditS.Text,1,1); W:=system.copy(EditW.Text,1,1); DirNames.N:=Char(N[1]); DirNames.E:=Char(E[1]); DirNames.S:=Char(S[1]); DirNames.W:=Char(W[1]); end; end; finally dlgCompas.Free; end; end; 2..9: TCompass(Component).Azimuth:=45*(Index-2); end; Designer.Modified; end;
Поскольку метод ExecuteVerb вызывается из каждого созданного пункта меню, то обычно он оформляется с использованием языковой конструкции case, с помощью которой определяется, какой именно фрагмент кода должен быть выполнен.
Так, в приведенном примере при вызове первого пункта меню (с индексом 0) создается и показывается диалог, на котором размещены элементы управления, необходимые для редактирования всех необходимых свойств.
Если диалог закрыть нажатием кнопки OK, то свойства редактируемого компонента принимают вновь введенные значения, а диалог – разрушается.
При вызове других пунктов меню значение азимута устанавливается кратным 45 градусам. Конкретное значение зависит от индекса пункта меню.
Какой бы пункт меню редактора компонента не был выбран, в завершении редактор форм, доступный через объект Designer вызовом метода Modified уведомляется о том, что компонент был модифицирован. Это заставляет редактор форм обновить значения свойств, отображаемых в инспекторе объектов и перерисовывает компонент.
Когда осуществляется двойной щелчок на компоненте, вызывается метод Edit редактора компонента. Этот метод обеспечивает еще один способ выполнения того же самого действия. Если метод Edit не перекрывается в порожденном классе, унаследованный метод класса TComponentEditor выполнит первое выражение, определенное в редакторе. Конечно, это происходит только в том случае, если метод GetVerbCount возвращает значение, превышающее нуль.
Пример 13.12. Реализация метода Edit редактора компонентов TComponentEditor
procedure TComponentEditor.Edit; begin if GetVerbCount > 0 then ExecuteVerb(0); end;
Такая реализация метода Edit означает, что двойной щелчок и выбор первого пункта меню приводит к одному и тому же результату – вызову метода ExecuteVerb с нулевым индексом.
Если для компонента не зарегистрирован какой-либо пользовательский редактор компонентов, то редактор форм создает для него экземпляр редактора компонентов типа TDefaultEditor.
Являясь прямым потомком класса TComponentEditor класс TDefaultEditor, не добавляет никаких пунктов к контекстному меню, но обеспечивает специфическую реализацию метода Edit.
Когда пользователь дважды щелкает на компоненте, метод Edit просматривает события компонента, отыскивая события OnCreate, OnChange или OnClick в указанном порядке и генерирует обработчик события для первого найденного события в файле формы. Если ни одного из этих событий нет в выбранном компоненте, то генерируется обработчик для первого найденного события.
Класс TDefaultEditor рационально использовать в качестве предка для создания редактора компонента, который только добавляет пункты к контекстному меню, а двойной щелчок же по-прежнему будет генерироваться обработчик события. Первый пункт меню не имеет никаких приоритета перед остальными, как это было в методе TComponentEditor.Edit.