СТАТЬЯ 19.09.01

Предыдущая часть

Профессиональная разработка приложений с помощью Delphi5

Часть 2. Создание компонентов Delphi

Сергей Трепалин,
УКЦ Interface Ltd.
КомпьютерПресс #2 2001

Статья была опубликована в КомпьютерПресс (www.cpress.ru)

Редакторы компонентов

Редактор свойств используется средой разработки для изменения значения свойства в инспекторе объектов. Редактор же компонента используется для изменения состояния компонента, минуя инспектор объектов. Он вызывается при щелчке правой кнопки мыши над компонентом, поставленным на форму. При этом появляется всплывающее меню, и если для данного компонента имеется редактор, то он вставляет в это меню свои команды.

Редактор компонента является потомком класса TComponentEditor. Для того чтобы в всплывающем меню компонента были вставлены новые команды, необходимо переписать два метода TComponentEditor:

TDWComponentEditor=class(TComponentEditor)  
  public  
    function GetVerbCount:integer; override;  
    function GetVerb(Index:integer):string; override;  
end;  

Сами названия этих методов позволяют догадаться, как меню, определенное программистом в редакторе компонента,  вставляется во всплывающее меню компонента. Среда разработки вызывает метод GetVerbCount, и если возвращается ненулевое значение, то вызывает несколько раз метод GetVerb с соответствующим индексом в качестве параметра. Реализация методов в этом случае выглядит следующим образом:

function TDWComponentEditor.GetVerbCount:integer;  
begin  
  Result:=3;  
end;  
   
function TDWComponentEditor.GetVerb(Index:integer):string;  
begin  
  case Index of  
    0:Result:='Copyright (C) 2001';  
    1:Result:='by me';  
    2:Result:='&Edit...';  
  end;  
end;  

Как и редактор свойств, редактор компонента нуждается в регистрации. Для этого используется метод RegisterComponentEditor, вызываемый средой разработки, при этом его вызов следует поместить в процедуру Register модуля DayStore.pas:

RegisterComponentEditor(TDayStore,TDWComponentEditor);  

Метод зависит от двух параметров: класса компонента и класса редактора компонента. Класс компонента означает, что данный редактор компонента будет вызываться для компонента главного класса и всех его потомков. В качестве шутливого упражнения можно попробовать зарегистрировать редактор компонента с классом компонента TComponent вместо TDayStore. Теперь, какой бы компонент вы ни поместили на форму, при нажатии правой кнопки мыши верхние команды всплывающего меню будут говорить о том, что данный компонент принадлежит вам.

Если для одного из потомков класса, который имеет редактор компонента, регистрируется новый редактор компонента, то старый редактор отменяется и больше не вызывается для данного класса и его потомков.

Напоминаем, что после изменения в процедуре Register компонент обязан быть переинсталлирован.

Сколько бы мы ни нажимали на соответствующие строки во всплывающем меню, ничего происходить не будет. Этого и следовало ожидать, поскольку в данной реализации редактора компонента нигде не описано, что необходимо делать при вызове соответствующего элемента меню. Это достигается переписыванием метода

ExecuteVerb(Index:integer);   

Из его объявления становится ясно, что среда разработки вызывает его, когда программист выбрал какой-либо элемент меню. Индекс выбранного элемента передается в качестве параметра этого метода. Но перед тем, как реализовать данный метода необходимо поставить задачу.  В качестве простой задачи можно выбрать следующую: редакция свойства дня недели через редактор компонента – вызов диалога. Однако реализовать вызов диалога редакции дня недели из редактора компонента сложнее, чем из редактора свойств. Проблема заключается в том, что в редакторе компонента отсутствуют методы GetOrdValue и SetOrdValue. Зато в редакторе компонента имеется свойство Component:TComponent, которое содержит ссылку на экземпляр компонента в среде разработки, для которого было вызвано меню. Им и следует воспользоваться для редакции свойства. Проблема того, что свойство DayWeek не реализовано на уровне TComponent, решается явным приведением типов. Поэтому в секции implementation модуля DayPropE.pas помещаем оператор uses DayStore. Теперь оба модуля циклически ссылаются друг на друга, но иначе привести тип TComponent к типу TdayStore невозможно.

procedure TDWComponentEditor.ExecuteVerb(Index:integer);  
var  
  F2:TForm2;  
begin  
  if Index=2 then begin  
    F2:=nil;  
    try  
      F2:=TForm2.Create(nil);  
      F2.ListBox1.ItemIndex:=TDayStore(Component).DayWeek-1;  
      if F2.ShowModal=mrOK then begin  
        TDayStore(Component).DayWeek:=F2.ListBox1.ItemIndex+1;  
        Designer.Modified;  
      end;  
    finally  
      if Assigned(F2) then F2.Release;  
    end;  
  end else ShowMessage('E-mail: aaa@bbb.cc.ru');  
end;  

Обратите внимание на обращение к свойству Designer.Modified. Вызовом этого метода мы информируем среду разработки о том, что свойства компонента изменились. Это заставляет среду разработки выполнить ряд действий: обновить содержимое инспектора объектов, сделать активным меню Save.

Еще один полезный метод, который можно переписать в редакторе компонента, – метод Edit. Он вызывается при двойном щелчке мыши на компоненте на этапе разработки. Если его не переписывать, а возвращаемое значение метода GetVerbCount при этом больше нуля, то выполняется метод с нулевым индексом, то есть вызывается команда ExecuteVerb(0). В следующем разделе мы перепишем этот метод.

Класс TFiler и сохранение данных в ресурсах

При редакции значений свойств в инспекторе объектов новые значения сохраняются в ресурсах проекта. Соответственно при запуске приложения ресурсы загружаются после создания компонентов на форме. Среда разработки Delphi запоминает в ресурсах только данные, отображаемые в инспекторе объектов. Предположим, имеется свойство, которое никаким способом невозможно представить в инспекторе объектов в виде строки. Такого типа свойства встречаются довольно часто – данные в двоичном формате. Их можно редактировать на этапе разработки при помощи, например, редактора компонента с диалогом загрузки файла. Однако эти данные не будут сохранены в ресурсах проекта и, следовательно, загружены во время приложения.

Выход из данной ситуации заключается в переписывании метода DefineProperties(Filer: TFiler), который определен на уровне TComponent. TFiler заведует сохранением в ресурсах свойств, редактируемых в инспекторе объектов. По умолчанию в ресурсы попадают все опубликованные свойства компонента. TFiler имеет методы DefineProperty и DefineBinaryProperty, используя которые, можно поместить в ресурсы неопубликованные данные.

Определим задачу следующим образом. Будем помещать в ресурсы неопубликованные двоичные данные (помещать в ресурсы остальные данные неинтересно, их можно вывести в инспектор объектов). Чтобы не связываться с какими-либо форматами и упрощением ввода и редакции данных, в качестве модели двоичных данных будем рассматривать содержимое единственной строки. Строку будем редактировать в методе Edit редактора компонента.

Перед началом работы необходимо убрать все компоненты TDayStore с формы проекта. Компиляцию далее следует производить только из редактора пакета.

Итак, в компоненте – классе TDayStore в секции private определим переменную FData:string, а в секции public (не published!) определим свойство:

Data:string read FData write FData.   

Затем в секции protected определим заголовок переписываемого метода:

DefineProperties(Filer :TFiler); override;   

При реализации этого метода требуется вызвать метод TFiler – DefineBinaryProperties. В качестве параметров этот метод принимает ссылку на процедуру записи данных в поток и ссылку на процедуру считывания данных из потока. Их также необходимо реализовать в компоненте TDayStore, заголовки этих процедур следует поместить в секции private класса TDayStore. Заголовок TDayStore после внесения всех изменений выглядит следующим образом:

  TDayStore = class(TComponent)  
  private  
    FDay:TDayWeek;  
    FData:string;  
    procedure SaveToStream(Stream:TStream);  
    procedure LoadFromStream(Stream:TStream);  
  protected  
    procedure DefineProperties(Filer:TFiler); override;  
  public  
    constructor Create(AOwner:TComponent); override;  
    property Data:string read FData write FData;  
  published  
    property DayWeek:TDayWeek read FDay write FDay;  
  end;  

В реализации переписанного метода DefineProperties в первую очередь следует вызвать метод DefineProperties класса TComponent (используя директиву Inherited). Без этого наш компонент станет вести себя некорректно: он все время будет находиться в левом верхнем углу формы. Метод DefineProperties класса TComponent записывает в ресурсы координаты левого верхнего угла пиктограммы – для невизуальных компонентов они не отображаются в инспекторе объектов:

procedure TDayStore.DefineProperties(Filer:TFiler);  
begin  
  inherited DefineProperties(Filer);  
  Filer.DefineBinaryProperty('BinData',LoadFromStream,
                             SaveToStream,length(FData)>0);  
end;  

Следующий оператор ‑ вызов метода TFiler DefineBinaryProperty. В качестве параметра он принимает имя нового свойства, которое не должно совпадать с именами уже имеющихся свойств, ссылку на процедуры чтения и записи двоичных данных, и последний параметр сообщает среде разработки, является содержимое пустым или нет. Если содержимое пустое, данные не запоминаются в ресурсах, а если нет – записываются в ресурсы. Если в компоненте имеется несколько различающихся двоичных типов данных, метод DefineBinaryProperties может быть вызван несколько раз с различными именами данных и разными процедурами чтения/записи данных.

Реализация методов чтения и записи данных выглядит следующим образом:

procedure TDayStore.SaveToStream(Stream:TStream);  
var  
  L:longint;  
begin  
  L:=length(FData);  
  Stream.Write(L,sizeof(L));  
  if L>0 then Stream.Write(FData[1],L);  
end;  
   
procedure TDayStore.LoadFromStream(Stream:TStream);  
var  
  L:longint;  
begin  
  Stream.Read(L,sizeof(L));  
  SetLength(FData,L);  
  if L>0 then Stream.Read(FData[1],L);  
end;  

Реализация метода SaveToStream очевидна. В методе LoadFromStream используется процедура SetLength, которая выделяет память строке заданной длины и делает строку уникальной – с числом ссылок, равным единице. Только после вызова метода SetLength строку можно использовать как буфер чтения данных, иначе может не хватить памяти или испортится содержимое других строк, которые ссылаются на ту же самую область памяти.

Теперь необходимо переписать метод Edit в редакторе компонентов TDWComponentEditor так, чтобы мы могли менять содержимое свойства Data. Будем использовать метод InputQuery для показа старого и ввода нового значения свойства Data:

procedure TDWComponentEditor.Edit;  
var  
  S:string;  
begin  
  S:=TDayStore(Component).Data;  
  if InputQuery('New property','Data:',S) then begin  
    TDayStore(Component).Data:=S;  
    Designer.Modified;  
  end;  
end;  

Обратите внимание, что не вызывается inherited-метод. В классе TComponentEditor метод Edit вызывает метод ExecuteVerb(0), что нам не требуется, поскольку у нас свой диалог!

Компонент заново регистрируется, после чего можно приступать к тестированию. Создадим новый проект, поставим компонент TDayStore на форму. Дважды щелкнем по нему и наберем какую-либо строку. После этого сохраним проект, затем остановим и загрузим заново Delphi. Вновь откроем данный проект, дважды щелкнем по компоненту. Теперь диалог показывает строку, введенную в данный диалог ранее.

Чтобы окончательно убедиться, что данные попали в ресурс проекта, можно щелкнуть правой клавишей мыши по форме и вызвать команду View as text:

object Form1: TForm1  
  Left = 194  
  Top = 107  
  Width = 213  
  Height = 183  
  Caption = 'Form1'  
  Color = clBtnFace  
  Font.Charset = DEFAULT_CHARSET  
  Font.Color = clWindowText  
  Font.Height = -11  
  Font.Name = 'MS Sans Serif'  
  Font.Style = []  
  OldCreateOrder = False  
  PixelsPerInch = 96  
  TextHeight = 13  
  object DayStore1: TDayStore  
    DayWeek = 1  
    Left = 128  
    Top = 48  
    BinData = {0F0000005465737420646174612073746F7265}  
  end  
end  

Итак, мы видим, что в ресурсах DayStore1 имеется свойство BinData.

Заключение

Из сказанного выше следует, что методика создания самих компонентов настолько тривиальна, что ее без труда может освоить даже начинающий программист. Выгода использования компонентов при сборке приложений очевидна: экономия времени, упрощение процесса отладки и внесения изменений. При этом созданное приложение должно подчиняться вполне определенным требованиям, но об этом мы поговорим в следующей статье данного цикла.

Дополнительную информацию Вы можете получить в компании Interface Ltd.

Отправить ссылку на страницу по e-mail
Обсудить на форуме Inprise/Borland


Interface Ltd.
Тel/Fax: +7(095) 105-0049 (многоканальный)
Отправить E-Mail
http://www.interface.ru
Ваши замечания и предложения отправляйте автору
По техническим вопросам обращайтесь к вебмастеру
Документ опубликован: 19.09.01