Версия для печати


Разработка эксперта IDE Delphi 2007 с визуальным наследованием форм и размещением их в репозитории
http://www.delphikingdom.com/asp/viewitem.asp?catalogID=1380

Сергей Деев
дата публикации 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.

Рассмотрим более подробно данный файл.

Класс TGxModuleCreatorWizard

Как видим, этот класс является наследником от класса TNotifierObject, являющийся одним из основных классов с поддержкой интерфейсов. По сути это и есть наш класс-эксперт.

На первых трех интерфейсах IOTAWizard, IOTARepositoryWizard, IOTARepositoryWizard60 долго останавливаться не буду, т. к. они знакомы программистам по более ранним версиям Delphi.

А вот на интерфейсе IOTARepositoryWizard80 остановлюсь более подробно.

Интерфейс 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

Класс TGxModuleCreator

Как видим, этот класс является наследником от класса 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

Описание класса:

{TCustomFormWizard}

  TCustomFormWizard = class(TNotifierObject,
                     IOTAWIzard,
                     IOTARepositoryWizard,
                     IOTARepositoryWizard60,
                     IOTARepositoryWizard80,
                     IOTAProjectWizard)
  protected
    function BaseFormName: String; virtual; abstract;
    function AncestorFormName: String; virtual; abstract;
public
    { IOTANotifier }
    procedure AfterSave;
    procedure BeforeSave;
    procedure Destroyed;
    procedure Modified;
    { IOTAWizard }
    function GetIDString: string;
    function GetName: string;
    function GetState: TWizardState;
    procedure Execute; virtual; abstract;
    { IOTARepositoryWizard }
    function GetAuthor: string;
    function GetComment: string;
    function GetPage: string;
    function GetGlyph: Cardinal;
    {IOTARepositoryWizard60}
    function GetDesigner: string;
    {IOTARepositoryWizard80}
    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

Описание класса:

{ TCustomFormModuleCreator }

  TCustomFormModuleCreator = class(TInterfacedObject, IOTACreator, IOTAModuleCreator)
  private
    FAncestorFormName: string;
    FBaseFormName : string;
    FClassName : string;
    FUnitIdent : string;
    FFileName : string;
  public
    constructor Create(AncestorFormName, BaseFormName: string); virtual;
    { IOTACreator }
    function GetCreatorType: string;
    function GetExisting: Boolean;
    function GetFileSystem: string;
    function GetOwner: IOTAModule; virtual;
    function GetUnnamed: Boolean;
    { IOTAModuleCreator }
    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 для поддержки интерфейса IOTAFile

Описание класса:

{ TFormModuleSourceFile }

  TFormModuleSourceFile = class(TInterfacedObject, IOTAFile)
  private
    FSource: string;
  public
    function GetSource: string;
    function GetAge: TDateTime;
    constructor Create(const Source: string);
  end;

Реализация методов данного класса ничем не отличается от реализации процедур в примере.

Класс-наследник создания модуля основной формы TBaseFormModuleCreator

Описание класса:

{ TBaseFormModuleCreator }

  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

Описание класса:

{ TBaseFormModuleWizard }

  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

Описание класса:

{ TInhFormCreator }

  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

Описание класса:

{ TInhFormModuleWizard }

  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.

Прилагаемые файлы