Главная » Delphi » Создание модуля компонента

0

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

Данный  компонент  под   названием  TddgWorthless будет  потомком  класса TCustomControl, а значит, будет обладать дескриптором окна  и способностью к отображению. От  базового класса  TCustomControl этот  компонент унаследовал несколько свойств, методов  и событий.

Самый  простой способ  начать  создание модуля нового компонента заключается в использовании эксперта компонентов (Component Expert), показанного на рис. 11.1.

Рис. 11.1. Эксперт компонентов

Окно  эксперта компонентов можно  вывести на экран, выбрав в меню  Component пункт  New Component. Укажите  в нем  имя  базового класса,  имя  класса  компонента, вкладку палитры, на которой должен  отображаться компонент, а также  имя  модуля компонента. Затем  щелкните на кнопке OK — и Delphi  автоматически создаст  модуль компонента с готовым объявлением его  типа  и процедурой регистрации. В листин ге 11.1 приведен модуль, созданный системными средствами Delphi.

Листинг 11.1. Модуль Worthless.pas — пример компонента Delphi

unit Worthless;

interface

uses

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,

Dialogs;

type

TddgWorthless = class(TCustomControl)

private

{ Закрытые объявления }

protected

{ Защищенные объявления }

public

{ Открытые объявления }

published

{ Публикуемые объявления }

end;

procedure Register;

implementation

procedure Register;begin

RegisterComponents(‘DDG’, [TddgWorthless]);

end;

end.

Как видно  из примера, пока  класс TddgWorthless —  просто заготовка компонен та. В следующих разделах в класс TddgWorthless будут добавлены свойства, методы  и события.

Создание свойств

Применение свойств компонентов описано в главе 10, “Архитектура компонентов: VCL  и  CLX”,  а  в  настоящем разделе  рассмотрим,  как  добавлять различные  типы свойств  к создаваемым компонентам.

Типы свойств

Большинство типов  свойств описано в главе 10. Добавим в компонент TddgWorth- less свойства каждого  типа,  чтобы  проиллюстрировать различия между ними.  Каж дый тип в окне  инспектора объектов редактируется по разному.  Более подробная ин формация об этих типах  и способах их редактирования приведена в настоящей главе далее.

Добавление в компонент простых свойств

Под простыми свойствами следует понимать числа,  строки и символы. Они  могут непосредственно редактироваться пользователем в окне инспектора объектов и не требуют  специальных методов  доступа.  В листинге 11.2 представлен вариант компо нента  TddgWorthless с тремя  простыми свойствами.

Листинг 11.2. Простые свойства

TddgWorthless = class(TCustomControl)

private

// Внутренние данные

FIntegerProp: Integer;

FStringProp: String;

FCharProp: Char;

published

// Простые типы свойств

property IntegerProp: Integer read FIntegerProp

write FIntegerProp;

property StringProp: String read FStringProp

write FStringProp;

property CharProp: Char read FCharProp write FCharProp;

end;

Используемый здесь  синтаксис должен  уже быть  знаком, поскольку  он  подробно описан в главе  10, “Архитектура компонентов: VCL и CLX”. Здесь  представлены внут ренние данные  компонента, объявленные в разделе private. Свойства, относящиеся кэтим полям, объявлены в разделе published; это означает, что при установке компо

нента в Delphi, такие свойства можно будет редактировать в окне инспектора объектов.

НА ЗАМЕТКУ

При создании компонентов используется соглашение об именовании, в соответствии с которым имена полей начинаются с буквы F, а имена компонентов и типов — с буквы T. Если соблюдать эти несложные соглашения, то разобраться в коде будет значи- тельно проще.

Добавление в компонент перечислимых свойств

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

TEnumProp = (epZero, epOne, epTwo, epThree);

Затем  следует определить внутреннее поле для хранения значения, задаваемого пользователем. В листинге 11.3 демонстрируются два перечислимых свойства компо нента  TddgWorthless.

Листинг 11.3. Свойства перечислимого  типа

TddgWorthless = class(TCustomControl)

private

// Перечислимые типы данных

FEnumProp: TEnumProp;

FBooleanProp: Boolean;

published

property EnumProp: TEnumProp read FEnumProp write FEnumProp;

property BooleanProp: Boolean read FBooleanProp

write FBooleanProp;

end;

Для простоты остальные свойства исключены из компонента. Если  бы этот  ком понент был  установлен, то  его  перечислимые свойства отобразились бы  в окне  ин спектора объектов так, как показано на рис. 11.2.

Добавление в компонент свойства типа множества

Свойство типа множества при редактировании в окне инспектора объектов выгля дит как множество, определенное в синтаксисе языка  Pascal. Простейший способ  его редактирования — развернуть свойство в окне инспектора объектов, в результате чего каждый  его элемент станет отдельным логическим свойством. Для создания свойства типа множества необходимо в первую очередь определить его тип:

TSetPropOption = (poOne, poTwo, poThree, poFour, poFive); TSetPropOptions = set of TSetPropOption;Сначала определяется набор элементов множества соответствующего перечисли

мого типа TSetPropOption, а уже затем — само множество TSetPropOptions.

Теперь можно добавить свойство TSetPropOptions в компонент TddgWorthless:

TddgWorthless = class(TCustomControl)

private

FOptions: TSetPropOptions;

published

property Options: TSetPropOptions read Foptions

write FOptions;

end;

На  рис. 11.3  показано,  как  выглядит  это  свойство  в  развернутом  виде  в  окне  ин

спектора объектов.

Рис. 11.2. Отображение перечислимых свойств компонента TddgWorthless в окне инспектора объектов6

Рис. 11.3. Представление свойства типа множества в окне инспектора объектов

Добавление в компонент свойства объекта

Свойствами могут являться объекты или  другие  компоненты. Например, у компо нента  TShape есть свойства объекты TBrush и TPen. Когда свойство является объек том,  оно  может  быть  развернуто в окне  инспектора объектов таким  образом, чтобы его собственные свойства также  могли  быть  модифицированы. Объектные свойства должны  быть  потомками класса  TPersistent, для того  чтобы  их публикуемые свой ства (т.е. свойства, объявленные в разделе published) могли  быть  записаны в поток данных и отображены в окне инспектора объектов.

Для  определения объектного свойства компонента TddgWorthless необходимо сначала  определить класс, который будет использован в качестве типа  свойства. Объ явление этого класса приведено в листинге 11.4.

Листинг 11.4. Определение TSomeObject

TSomeObject = class(TPersistent)

private

FProp1: Integer;

FProp2: String;

public

procedure Assign(Source: TPersistent);

published

property Prop1: Integer read FProp1 write FProp1;

property Prop2: String read FProp2 write FProp2;

end;Класс TSomeObject является прямым потомком класса TPersistent, но в общем случае это необязательно. Если объект, от которого происходит новый класс, являет ся потомком класса  TPersistent, то  этот  объект также  может  быть  использован в качестве свойства другого объекта.

В приведенном выше примере класс TSomeObject, используемый для создания объектного свойства, имеет  два собственных простых свойства: Prop1 и Prop2. В его состав также входит процедура Assign(), назначение которой поясняется ниже.

Теперь  можно   добавить  в  компонент  TddgWorthless внутреннее   поле   типа TSomeObject. Так  как это  свойство представляет собой  объект, его  нужно  создать. В противном случае,  когда  пользователь поместит в форму  компонент TddgWorth- less того  экземпляра класса  TSomeObject, который он мог бы редактировать, про сто  не будет существовать. В листинге 11.5 показано объявление компонента Tddg- Worthless с его новым объектным свойством.

Листинг 11.5. Добавление свойств объектов

TddgWorthless = class(TCustomControl)

private

FSomeObject: TSomeObject;

procedure SetSomeObject(Value: TSomeObject);

public

constructor Create(AOwner: TComponent); override;

destructor Destroy; override;

published

property SomeObject: TSomeObject read FSomeObject

write SetSomeObject;

end;Обратите внимание на то, что в код включены переопределенные конструктор Cre- ate() и  деструктор  Destroy(). Кроме   того,   объявлен метод  доступа  SetSomeOb- ject(), предназначенный для записи свойства SomeObject. Метод  доступа “для запи си”  часто   называют просто  методом записи  (writer   method) (или   методом установки (setter)), а метод  доступа  “для чтения” —  методом чтения (reader) (или  методом выборки (getter method)). Как указывалось в главе  10, “Архитектура компонентов: VCL и CLX”,методы  записи должны  иметь  один параметр такого  же типа,  что и свойство, которому они принадлежат. Существует соглашение начинать имена методов записи со слова Set.

Конструктор TddgWorthless.Create() определяем следующим образом:

constructor TddgWorthless.Create(AOwner: TComponent);

begin

inherited Create(AOwner);

FSomeObject := TSomeObject.Create;

end;

Здесь  вначале был вызван  унаследованный конструктор Create(), а затем  создан экземпляр класса TSomeObject. Поскольку конструктор Create() вызывается как во время  разработки, когда  пользователь помещает компонент в форму,  так  и при  вы полнении приложения, то можно  быть  уверенным, что  объект FSomeObject всегда действителен.

Кроме  того,  необходимо переопределить деструктор Destroy(), который перед освобождением  компонента  TddgWorthless должен   предварительно  освободить объект TSomeObject. Вот код переопределенного деструктора:

destructor TddgWorthless.Destroy;

begin

FSomeObject.Free;

inherited Destroy;

end;

Теперь,  изучив  принципы  создания  экземпляра  объекта  TSomeObject,  рассмот

рим, что произойдет при выполнении следующего программного кода:

var

MySomeObject: TSomeObject;

begin

MySomeObject := TSomeObject.Create;

ddgWorthless.SomeObjectj := MySomeObject;

end;

Если  свойство TddgWorthless.SomeObject было  бы  определено без  метода  за писи,  подобного приведенному ниже,  то при  попытке пользователя присвоить полю SomeObject его собственный объект, прежний экземпляр объекта, на который ссы лался FSomeObject, был бы потерян:

property SomeObject: TSomeObject read FSomeObject write FSomeObject;

2

 

Напомним, что экземпляры объектов — это на самом деле указатели, ссылающиеся на реальный объект (см. главу 2, “Язык программирования Object Pascal”). Выполняя присвоение, как в предыдущем примере, будет получен указатель  на другой экземпляр объекта, в то время  как предыдущий экземпляр никуда  из памяти не исчезает . При разработке компонентов обычно пытаются исключить необходимость строгого со блюдения пользователями формальных правил при  доступе  к свойствам. Чтобы из бежать  этого  подводного камня и защитить новый компонент от неумелого  использо вания, для объектных свойств создают  методы  доступа.  Указанное гарантирует, что

2 Речь идет о явлении утечки памяти. — Прим. ред.при присвоении таким свойствам новых значений системные ресурсы не будут поте

ряны.  Метод доступа к объекту SomeObject предназначен как раз для этого:

procedure TddgWorthLess.SetSomeObject(Value: TSomeObject);

begin

if Assigned(Value) then

FSomeObject.Assign(Value);

end;

Метод   SetSomeObject() вызывает  метод   FSomeObject.Assign(),  передавая ему указатель  на новый объект TSomeObject. Вот  как выглядит реализация метода TSomeObject.Assign():

procedure TSomeObject.Assign(Source: TPersistent);

begin

if Source is TSomeObject then begin

FProp1 := TSomeObject(Source).Prop1;

FProp2 := TSomeObject(Source).Prop2;

inherited Assign(Source);

end;

end;

В методе  TSomeObject.Assign() вначале выполняется проверка того,  действи тельно ли пользователь в качестве параметра передал экземпляр объекта TSomeOb- ject. Если это  так,  то из параметра Source копируются соответствующие значения свойств. Это  еще  один  способ  присвоения значений объектов другим  объектам, ис пользуемым в библиотеке VCL. Реализуя метод  Assign() для  нового компонента, имеет смысл просмотреть реализацию данного метода в различных классах исходного кода библиотеки VCL — это может подсказать подходящую  идею.

CОВЕТ

Никогда не присваивайте свойству значение внутри его собственного метода записи.

Рассмотрим, например, следующее объявление свойства:

property SomeProp: integer read FSomeProp write SetSomeProp;

….

procedure SetSomeProp(Value:integer);

begin

SomeProp := Value;          // Это приведет к бесконечной рекурсии

end;

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

Добавление в компонент свойства массива

Доступ  к некоторым свойствам можно  осуществлять так,  как  если  бы  они  пред ставляли собой  массивы. То есть они  содержат список  элементов, к каждому из кото рых можно  обращаться по значению его индекса.  Такими  элементами могут быть лю бые  типы  объектов. Примерами являются, в частности, свойства TScreen.Fonts,TMemo.Lines и TDBGrid.Columns. Эти  свойства требуют  наличия собственных ре дакторов свойств. Более  подробная информация о создании редакторов свойств при ведена в главе 12, “Создание расширенного компонента VCL”. Сейчас  же рассмотрим, как определить свойство, которое может  быть  индексировано как массив,  но, тем не менее, совсем не содержит списков. Оставим пока в стороне компонент TddgWorthless и обратимся к новому компоненту TddgPlanets. Он содержит два свойства: PlanetName и PlanetPosition. Свойство PlanetName — это массив,  возвращающий название плане ты по заданному  значению целочисленного индекса  (номеру). Массив  PlanetPosition использует не целочисленный, а строковый индекс.  Если строка представляет собой  на звание существующей  планеты, то массив  PlanetPosition возвратит положение плане ты в Солнечной системе.

Например,   выполнение   следующего    оператора   с   использованием   свойства

TddgPlanets.PlanetName приведет к отображению строки “Neptune”:

ShowMessage(ddgPlanets.PlanetName[8]);

Сравните это выражение с обычным оператором, выводящим на экран  сообщение From the sun, Neptune is planet number: 8 (“Нептун —  8 я планета Солнеч ной системы”):

ShowMessage(‘From the sun, Neptune is planet number: ‘ + IntToStr(ddgPlanets.PlanetPosition[‘Neptune’]));

Прежде чем рассмотреть исходный код данного компонента, приведем основные характеристики свойств массивов, отличающие их от других, уже рассмотренных свойств.

•   Свойства массивы  объявляются с использованием одного  или  нескольких ин дексных  параметров. Тип  индексов должен  быть  простым (это  может  быть  це лое число или строка, но не запись и не класс).

•   Подпрограммы доступа к свойству (read и write) должны  быть методами. Они не могут быть внутренними полями компонента.

•  Если в определении свойства массива  используется несколько индексов (т.е. свойство представлено многомерным массивом), методы  доступа должны  со держать параметры для каждого  индекса в том же порядке, что и в свойстве.

Программный код класса TddgPlanets приведен в листинге 11.6.

Листинг 11.6. Класс TddgPlanets, иллюстрирующий создание свойств массивов

unit planets;

interface uses

Classes, SysUtils;

type

TddgPlanets = class(TComponent)

private

// Методы доступа к свойству-массиву

function GetPlanetName(const AIndex: Integer): String;function GetPlanetPosition(const APlanetName: String): Integer;

public

{ Массив индексирован целым значением. Этот вариант

используется для индексации массива по умолчанию. }

property PlanetName[const AIndex:

Integer]: String read GetPlanetName; default;

// Массив индексирован строковым значением

property PlanetPosition[const APlantetName:

String]: Integer read GetPlanetPosition;

end;

implementation const

// Объявление массива-константы с названиями планет

PlanetNames: array[1..9] of String[7] = (‘Mercury’, ‘Venus’,

‘Earth’, ‘Mars’, ‘Jupiter’, ‘Saturn’,

‘Uranus’, ‘Neptune’, ‘Pluto’);

function TddgPlanets.GetPlanetName(const AIndex: Integer): String;

{ Возвращает название планеты, указанной индексом. Если индекс

выходит за пределы допустимого диапазона, передается исключение. }

begin

if (AIndex < 0) or (AIndex > 9) then

raise Exception.Create(‘Wrong number, enter a number 1-9′)

else

Result := PlanetNames[AIndex];

end;

function TddgPlanets.GetPlanetPosition(const APlanetName: String): Integer;

var

i: integer;

begin

Result := 0;

i := 0;

{ Сравнивает PName с названием каждой планеты и возвращает

индекс подходящего значения массива-константы. При

несовпадении возвращает нуль. }

repeat

inc(i);

until (i = 10) or (CompareStr(UpperCase(APlanetName),

UpperCase(PlanetNames[i])) = 0);

if i <> 10 then // Имя планеты найдено

Result := i;

end;

end.Этот компонент может подсказать идею создания свойств массивов с целой  или строковой переменной, используемой в качестве индекса.  При  обращении к свойству чтения значение возвращает функция, в отличие от  обращения к другим  свойствам, возвращающим значение внутреннего поля  непосредственно. Комментарии в коде по могут лучше понять схему построения компонента.

Значения по умолчанию

Свойству можно  назначить значение по умолчанию, или стандартное значение (default value),  выполнив соответствующее присваивание в конструкторе компонента. Таким образом, если добавить следующие  операторы в конструктор компонента Tddg- Worthless, то значение его свойства FIntegerProp при  помещении компонента в форму всегда будет равно  100:

FIntegerProp := 100;

Теперь настало время  рассмотреть директивы Default и NoDefault, присутст вующие в объявлениях свойств. Если обратиться к исходному  коду библиотеки VCL, то можно  заметить, что некоторые объявления свойств содержат директиву Default, как и в случае свойства TComponent.Ftag:

property Tag: Longint read FTag write FTag default 0;

Не путайте  действие этого  оператора с присвоением значения по умолчанию в конструкторе. Например, измените объявление свойства IntegerProp компонента TddgWorthless следующим образом:

property IntegerProp: Integer read FIntegerProp

write FIntegerProp default 100;

Этот оператор не присваивает свойству значение 100. Здесь просто определяется, будет ли сохранено значение свойства при  сохранении формы, содержащей компо нент TddgWorthless. Если значение свойства IntegerProp не равно  100, то оно бу дет  сохранено в файле .DFM. В противном случае  оно  не  будет сохранено, так  как

100 —  это  значение, которое присваивается данному  свойству  заново  создаваемого объекта перед  считыванием его  свойств  из  потока данных.  Для  ускорения загрузки форм  рекомендуется везде,  где  только   это  возможно, использовать директиву  De- fault. Важно  понять, что  директива Default не присваивает значение свойству, — это необходимо сделать в конструкторе компонента, как было показано выше.

Директива NoDefault используется для переобъявления свойства (со  значением по умолчанию) таким образом, чтобы  оно,  независимо от исходного значения, всегда записывалось в поток  данных.  Например, можно  переобъявить компонент так, чтобы для свойства Tag вообще  не задавалось значения по умолчанию:

TSample = class(TComponent) published property Tag NoDefault;

Не  следует без особой необходимости применять директиву NoDefault. Например, свойство TForm.PixelsPerInch всегда  должно  сохраняться для правильного отобра жения формы при выполнении программы. Необходимо также отметить, что для свойств строкового, вещественного (с плавающей точкой) и типа int64 значения по умолчанию не могут быть объявлены.

Для изменения значения свойства по умолчанию достаточно переопределить свойство новым значением (но без методов чтения и записи).

Свойство массив по умолчанию

Вполне  возможно объявить свойство массив таким образом, что оно будет стандарт ным свойством того компонента, которому оно принадлежит. Это позволит обращаться с экземпляром объекта так, как если бы тот был переменной типа массива.  Например, в компоненте TddgPlanets объявлено свойство TddgPlanets.PlanetName с ключевым словом  Default. Благодаря этому пользователь компонента может  не применять имя свойства PlanetName для получения соответствующего значения. Достаточно просто поместить индекс возле идентификатора объекта. Две следующие строки кода приводят к одному и тому же результату:

ShowMessage(ddgPlanets.PlanetName[8]); ShowMessage(ddgPlanets[8]);

У объекта может быть только одно стандартное свойство массив, и потомки дан

ного объекта не могут его переопределять.

Источник: Тейксейра, Стив, Пачеко, Ксавье.   Borland Delphi 6. Руководство разработчика. : Пер.  с англ. — М. : Издательский дом “Вильямс”, 2002. —  1120 с. : ил. — Парал. тит. англ.

По теме:

  • Комментарии