Сергей Деев дата публикации 22-09-2008 09:40 Разработка эксперта IDE Delphi 2007 с визуальным наследованием форм и размещением их в репозитории
При переходе с Delphi 7 на Delphi 2007 я столкнулся с проблемой разработки эксперта IDE с визуальным наследованием форм и размещением их в репозитории.
Все мои эксперты, написанные под Delphi 7, отказывались работать на Delphi 2007.
Я решил разобраться почему это происходит и написать данную статью.
Частичное решение данной проблемы описано в примере на странице Erik's Open Tools API FAQ and Resources.
Попробуем разобраться в данном примере и написать свой эксперт IDE с визуальным наследованием форм и размещением их в репозитории.
Заходим на страницу Erik's Open Tools API FAQ and Resources и скачиваем оттуда пример из раздела «How do I implement a module creator (IOTAModuleCreator/ IOTAFormWizard)?»
Получаем файл GXModuleCreator.
Рассмотрим более подробно данный файл.
Как видим, этот класс является наследником от класса TNotifierObject, являющийся одним из основных классов с поддержкой интерфейсов. По сути это и есть наш класс-эксперт.
На первых трех интерфейсах IOTAWizard, IOTARepositoryWizard, IOTARepositoryWizard60 долго останавливаться не буду, т. к. они знакомы программистам по более ранним версиям Delphi.
А вот на интерфейсе IOTARepositoryWizard80 остановлюсь более подробно.
В разделе «Known bugs in the Delphi 2007 Open Tools API (most also apply to earlier releases):» написано, что для создания и правильного функционирования эксперта репозитория необходимо, чтобы наш эксперт поддерживал этот интерфейс (QC 20898).
Остановимся на функциях данного интерфейса:
function GetPersonality: string;
|
|
возвращает строку о персональной секции по умолчанию. Как видим из реализации данной функции, она всегда возвращает константу sDelphiPersonality.
function GetGalleryCategory: IOTAGalleryCategory;
|
|
возвращает категорию репозотирия, в которую будет помещен наш эксперт. Как видно из реализации этой функции она возвращает категорию по имени, описанной в константе sCategoryDelphiNewFiles
Как видим, этот класс является наследником от класса TInterfacedObject, и поддерживает хорошо знакомые по предыдущим версиям Delphi интерфейсы IOTACreator и IOTAModuleCreator TNotifierObject. По сути это класс, который будет отвечать за создание файла нашего модуля (pas) с помощью функции
function NewImplSource(const ModuleIdent, FormIdent, AncestorIdent: string): IOTAFile;
|
|
и файла описания формы (dfm) с помощью функции
function NewFormFile(const FormIdent, AncestorIdent: string): IOTAFile;
|
|
Основным отличием процедуры регистрации эксперта является использование метода RegisterPackageWizard, а не RegisterCustomModule как в предыдущих версиях Delphi (раздел «Known bugs in the Delphi 2005 Open Tools API: »).
Для простоты реализации примем, что мы будем с помощью этого эксперта создавать наследников одной и той же формы с двумя кнопками «ОК» и «Отмена».
Примем, что класс этой самой формы будет называться TBaseForm, а все наследники будут иметь префикс в названии класса TInhForm и порядковый номер.
Начнем работу с создания нового пакета. Зайдем в его опции и установим тип пакета как «DisignTime only» и сохраним данный пакет под именем dtInhFormWizard. Вставим в пакет пустой модуль и добавим к пакету в раздел «Requires» пакет designide. После чего приступим к разработке самого эксперта.
По условию задачи мы будем разрабатывать эксперта для наследования, имеет смысл предусмотреть в нашем модуле общих классов-предков нашего эксперта и создание самих модулей и форм.
Примем что, в дальнейшем мы не будем использовать этих предков в других пакетах, то есть расположим описание всех нужных нам классов после директивы implementation.
Получим следующий код:
unit UInhFormReg;
interface
procedure Register;
implementation
uses
SysUtils, Classes, Windows, DesignIntf, ToolsApi;
end;
|
|
Приступим к нашим классам-предкам
Описание класса:
TCustomFormWizard = class(TNotifierObject,
IOTAWIzard,
IOTARepositoryWizard,
IOTARepositoryWizard60,
IOTARepositoryWizard80,
IOTAProjectWizard)
protected
function BaseFormName: String; virtual; abstract;
function AncestorFormName: String; virtual; abstract;
public
procedure AfterSave;
procedure BeforeSave;
procedure Destroyed;
procedure Modified;
function GetIDString: string;
function GetName: string;
function GetState: TWizardState;
procedure Execute; virtual; abstract;
function GetAuthor: string;
function GetComment: string;
function GetPage: string;
function GetGlyph: Cardinal;
function GetDesigner: string;
function GetGalleryCategory: IOTAGalleryCategory;
function GetPersonality: string;
end;
|
|
Реализация методов данного класса ничем не отличается от реализации процедур в примере за исключением методов:
function TCustomFormWizard.GetName: string;
begin
Result := BaseFormName;
end;
function TCustomFormWizard.GetIDString: string;
begin
Result := 'TCustomForm.' + BaseFormName + '.Wizard';
end;
function TCustomFormWizard.GetComment: string;
begin
Result := 'Creates a new ' + BaseFormName;
end;
|
|
Как видим, они вызывают абстрактный метод
function BaseFormName: String; virtual; abstract;
|
|
который мы будем переопределять в наследниках данного эксперта.
Кроме того следует обратить внимание, что метод
procedure Execute; virtual; abstract;
|
|
тоже абстрактный, а значит, мы и его будем переопределять в наследниках этого эксперта.
Описание класса:
TCustomFormModuleCreator = class(TInterfacedObject, IOTACreator, IOTAModuleCreator)
private
FAncestorFormName: string;
FBaseFormName : string;
FClassName : string;
FUnitIdent : string;
FFileName : string;
public
constructor Create(AncestorFormName, BaseFormName: string); virtual;
function GetCreatorType: string;
function GetExisting: Boolean;
function GetFileSystem: string;
function GetOwner: IOTAModule; virtual;
function GetUnnamed: Boolean;
function GetAncestorName: string;
function GetImplFileName: string;
function GetIntfFileName: string;
function GetFormName: string;
function GetMainForm: Boolean;
function GetShowForm: Boolean;
function GetShowSource: Boolean;
function NewFormFile(const FormIdent, AncestorIdent: string): IOTAFile; virtual; abstract;
function NewImplSource(const ModuleIdent, FormIdent, AncestorIdent: string): IOTAFile; virtual; abstract;
function NewIntfSource(const ModuleIdent, FormIdent, AncestorIdent: string): IOTAFile;
procedure FormCreated(const FormEditor: IOTAFormEditor);
end;
|
|
Реализация методов данного класса ничем не отличается от реализации процедур в примере за исключением методов:
constructor TCustomFormModuleCreator.Create(AncestorFormName, BaseFormName: string);
begin
FAncestorFormName := AncestorFormName;
FBaseFormName := BaseFormName;
end;
function TCustomFormModuleCreator.GetAncestorName: string;
begin
Result := FAncestorFormName;
end;
function TCustomFormModuleCreator.GetFormName: string;
begin
Result := FBaseFormName
end;
|
|
Следует отметить, что конструктор данного класса
constructor Create(AncestorFormName, BaseFormName: string);
|
|
виртуальный, а методы
function NewImplSource(const ModuleIdent, FormIdent, AncestorIdent: string): IOTAFile;
|
|
и
function NewImplSource(const ModuleIdent, FormIdent, AncestorIdent: string): IOTAFile;
|
|
абстрактны, мы их будем переопределять в наследниках.
Описание класса:
TFormModuleSourceFile = class(TInterfacedObject, IOTAFile)
private
FSource: string;
public
function GetSource: string;
function GetAge: TDateTime;
constructor Create(const Source: string);
end;
|
|
Реализация методов данного класса ничем не отличается от реализации процедур в примере.
Описание класса:
TBaseFormModuleCreator = class(TCustomFormModuleCreator)
public
constructor Create(AncestorFormName, BaseFormName: string); override;
function NewFormFile(const FormIdent, AncestorIdent: string): IOTAFile; override;
function NewImplSource(const ModuleIdent, FormIdent, AncestorIdent: string): IOTAFile; override;
end;
|
|
Реализация конструктора класса:
constructor TBaseFormModuleCreator.Create(AncestorFormName,
BaseFormName: string);
var
AFileName: String;
begin
inherited Create(AncestorFormName, BaseFormName);
(BorlandIDEServices as IOTAModuleServices).GetNewModuleAndClassName(BaseFormName, FUnitIdent, FClassName, FFileName);
FUnitIdent := 'U' + BaseFormName;
AFileName := ExtractFilePath(FFileName);
FFileName := AFileName + FUnitIdent + '.pas';
end;
|
|
В данном конструкторе принимаем меры, чтобы модуль с базовой формой назывался всегда UBaseForm, а сам файл модуля имел такое же имя.
В реализации методов NewFormFile и NewImplSource следует отметить наличие констант с описанием самого модуля и описания формы. Можно было бы записать эти константы в ресурс пакета и доставать их оттуда.
Описание класса:
TBaseFormModuleWizard = class(TCustomFormWizard)
protected
function BaseFormName: String; override;
function AncestorFormName: String; override;
public
procedure Execute; override;
end;
|
|
Реализация метода Execute:
procedure TBaseFormModuleWizard.Execute;
var
Module: IOTAModule;
begin
Module := (BorlandIDEServices as IOTAModuleServices).CreateModule(TBaseFormModuleCreator.Create(AncestorFormName, BaseFormName));
end;
|
|
Как видим, в нем просто вызывается конструктор класса создания модуля TBaseFormModuleCreator с передачей в него наименований самой формы и ее предка.
Описание класса:
TInhFormCreator = class(TCustomFormModuleCreator)
public
constructor Create(AncestorFormName, BaseFormName: string); override;
function NewFormFile(const FormIdent, AncestorIdent: string): IOTAFile; override;
function NewImplSource(const ModuleIdent, FormIdent, AncestorIdent: string): IOTAFile; override;
end;
|
|
В конструкторе класса мы также принимаем меры, чтобы все модули наследуемых форм назывались UInhForm с порядковым номером, а сам файл модуля имел такое же наименование.
В методе NewFormFile в константе описания формы предусматриваем ее наследование
В методе NewImplSource в константе описания модуля предусматриваем ссылку на модуль базовой формы и наследование самой формы.
Описание класса:
TInhFormModuleWizard = class(TCustomFormWizard)
protected
function BaseFormName: String; override;
function AncestorFormName: String; override;
public
procedure Execute; override;
end;
|
|
Реализация метода Execute:
procedure TInhFormModuleWizard.Execute;
var
Module: IOTAModule;
ActiveProject: IOTAProject;
I: Integer;
IsAncestorFormName: Boolean;
begin
ActiveProject := GetActiveProject;
if ActiveProject <> nil then
begin
IsAncestorFormName := False;
for I := 0 to ActiveProject.GetModuleCount - 1 do
with ActiveProject.GetModule(I) do
if FormName = AncestorFormName then
begin
IsAncestorFormName := True;
Break;
end;
if not IsAncestorFormName then
with TBaseFormModuleWizard.Create do
try
Execute;
finally
Free;
end;
Module := (BorlandIDEServices as IOTAModuleServices).CreateModule(TInhFormCreator.Create(AncestorFormName, BaseFormName));
end;
end;
|
|
Как видим, в методе создается объект типа текущий проект, затем если текущий проект создан в нем ищется базовая форма и если ее не находят, то вызывается конструктор ее эксперта, а затем и создание самого модуля формы-наследницы.
procedure Register;
begin
RegisterPackageWizard(TInhFormModuleWizard.Create);
end;
|
|
Полностью файл проекта можно посмотреть в приложенном файле.
Данный пример, после некоторых доработок, позволяет программисту создать эксперта IDE с визуальным наследованием форм и размещением их в репозитории сколь угодно сложности.
При написании статьи использовались материалы со страницы Erik's Open Tools API FAQ and Resources.
Прилагаемые файлы
[Репозиторий объектов]
Обсуждение материала нет сообщений |