- •9 Разработка компонентов в среде Delphi
- •9.1. Выбор класса-предка
- •9.1.1. Класс tControl
- •9.1.4. Класс tCustomControl
- •9.2. Создание модуля компонента и тестового приложения
- •Разработка тестового приложения
- •9.3.1. Простые свойства
- •9.3.6. Массив свойств
- •9.3.7. Перекрытие и переопределение свойств
- •9.3.8. Создание событий
- •9.3.9. Создание методов
- •Что такое событие? в чем отличие создания свойства от события?
9.1.4. Класс tCustomControl
Класс TCustomControl представляет собой комбинацию классов TWinControl и TGraphicControl. Являясь прямым потомком класса TWinControl, TCustomControl наследует способность управления идентификатором окна и всеми сопутствующими возможностями. Кроме этого, как и класс TGraphicControl, класс TCustomControl обеспечивает потомков виртуальным методом Paint, ассоциированным со свойством Canvas.
Таким образом, в зависимости от того, какой компонент будет исходным (базовым) для создания нового класса, можно выделить 4 случая:
создание Windows-элемента управления (TWinControl);
создание графического элемента управления (TGraphic-Control);
создание нового элемента управления (TCustomControl); О создание невизуального компонента (TComponent).
9.2. Создание модуля компонента и тестового приложения
Определившись с выбором компонента, можно приступить к созданию модуля компонента. Для этого необходимо выполнить следующие шаги.
Выполните команду File/ New.../ Component или Component/ New Component.
В диалоговом окне New Component (рис. 9.2.1.) установите основные параметры создания компонента: Ancestor type (имя класса-предка), Class Name (имя класса компонента), Palette Page (вкладка палитры, на которой должен отображаться компонент) и Unit file name (имя модуля компонента).
После щелчка на кнопке ОК будет сгенерирован каркас нового класса.
По ходу процесса построения компонента необходимо тестировать его, не устанавливая в палитру компонентов. Тестовое приложение должно содержать код, который динамически помещает новый компонент на форму, изменяет его свойства и вызывает методы.
Упражнение 9.2.1. Разработайте новый компонент, который объединяет компоненты TEdit и TLabel. Компонент Label располагается выше поля редактирования (TEdit). При перемещении поля редактирования TLabel следует за ним. При удалении поля редактирования TLabel также удаляется.
Решение
В качестве предка класса нового компонента используем TEdit.
Выполните команду Component/ New component. Установите следующие значения параметров окна: Ancestor type TEdit
Class Name TLabelEdit
Palette Page Test
Unit file name ...\LabelEdit\LabelEdit.pas
Щелкните на кнопке ОК, автоматически будет сгенерирован следующий код:
unit LabelEdit;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type
TLabelEdit = class(TEdit)
private
{ Private declarations }
protected
{ Protected declarations }
public
{ Public declarations }
published
{ Published declarations }
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Test', [TLabelEdit]);
end;
end.
В модуле описан каркас нового класса и написана процедура регистрации компонента (Register), которая помещает его на страницу Test. Сохраните файл модуля компонента.
Разработка тестового приложения
Создайте новый проект. Сохраните его файлы в папке ...\LabelEdit: файл модуля — под именем Main.pas, файл проекта — Test Application, dpr.
Добавьте имя модуля разрабатываемого компонента в раздел Uses формы тестового приложения:
...
uses ..., LabelEdit;
...
В общедоступный раздел класса TForml добавьте поле
le: TLabelEdit;
В обработчике события OnCreate формы динамически создайте новый компонент:
procedure TForml.FormCreate(Sender: TObject);
begin
le:=TLabelEdit.Create(Self);
with le do
begin
parent:=forml;
left:=10;
top:=10;
end;
end;
Сохраните файлы проекта.
Эксперимент. Убедитесь, что при запуске в левом верхнем углу формы появляется окно редактирования. ♦
9.3. Добавление свойств, методов и событий
Свойства, как и поля класса, являются атрибутом объекта. Но если поля являются простым хранилищем некоего значения, которое может быть прочитано и изменено, то со свойством связаны некоторые действия, осуществляемые при чтении и изменении его содержимого.
Добавление свойства происходит в три этапа.
Создание внутреннего поля класса для хранения значения свойства.
Описание и разработка методов доступа к значению свойства.
Описание свойства.
В классе TControl свойства Caption/Text, Parent и Hint определяются так:
TControl = class (TComponent)
private
FParent: TWinControl; {внутреннее поле свойства Parent}
FText: PChar; {внутреннее поле свойства Text/Caption}
FHint: string; {внутреннее поле свойства Hint}
function GetText: Tcaption; {метод чтения свойства Text/Caption}
function IsCaptionStored: Boolean;
function IsHintStored: Boolean;
procedure SetText(const Value: TCaption);
{метод записи свойства Text/Caption}
…
Protected
…
procedure SetParent{AParent: TWinControl); virtual;
property Caption: TCaption read GetText write SetText stored IsCaptionStored;
property Text: TCaption read GetText write SetText;
…
public
…
property Parent: TWinControl read FParent write SetParent;
…
published
property Hint: string read FHint write FHint stored IsHintStored;
…
end;
Объявление свойства имеет следующий синтаксис: property <имя свойства>: тип определители;
При объявлении свойства используется зарезервированное слово property, после которого указываются четыре ключевых фрагмента информации. Первый — имя свойства, этот идентификатор используется для ссылок на значение свойства. Таким образом, свойства получают внешний вид полей данных.
Каждое объявление свойства должно определять тип свойства, для этого используется символ двоеточие после имени свойства.
Для указания метода, который будет использоваться для осуществления выборки значения свойства, используется директива read. Метод должен быть функцией, чей возвращаемый тип является тем же самым, что и тип свойства.
Однако вместо метода доступа для чтения можно указать внутреннее поле хранения данных, как, например, при описании свойств Hint и Parent. Подобная форма записи приводит к тому, что значение свойства извлекается прямо из внутреннего поля данных.
За спецификацией метода чтения следует определитель метода записи, директива write определяет, какой метод будет использоваться для присвоения свойству значения. Метод должен быть процедурой, имеющей единственный параметр, тип которого должен совпадать с типом свойства.
При обращении к значению свойства происходит перенаправление на соответствующий метод. Например, оператор s : =Editl. Text; автоматически будет преобразован в оператор s : =Editl. GetText; а оператор Editl. Text: =' Test' — в оператор Editl.Text('Test').
Описание свойства должно содержать определитель read или write или сразу оба. Если описание свойства включает в себя только определитель read, то оно является свойством только для чтения. В свою очередь, свойство, чье описание включает в себя только определитель write, является свойством только для записи. При присвоении свойству, определенному с директивой только для чтения, какого-либо значения или при использовании в выражении свойства с директивой только для записи всегда возникает ошибка.
В отличие от внутренних полей хранения данных свойства не могут быть переданы в процедуру (или функцию) в качестве параметра переменной (параметр var), это объясняется тем, что свойство не существует в памяти.
Когда программист использует Инспектор объектов для изменения свойств формы или свойств компонентов, то результирующие изменения заносятся в файл формы. Файлы форм представляют собой файлы ресурсов Windows, и когда приложение запускается, то описание формы подгружается из этого файла. Для определения того, что должно сохраняться в файле формы, служат спецификаторы памяти — необязательные директивы stored, default и node-fault. Эти директивы влияют на информацию о типе во время выполнения, генерируемую для свойств published.
Директива stored управляет тем, будет или нет свойство действительно запоминаться в файле формы. За директивой stored должны следовать либо константы True или False, либо имя поля, имеющего тип Boolean, либо имя метода, у которого нет параметров, и возвращающего значение типа Boolean. Например,
property Hint: string read FHint write FHint stored IsHintStored;
Если свойство не содержит директиву stored, то оно рассматривается как содержащее ее с параметром True.
Директивы default и nodefault управляют значениями свойства по умолчанию. За директивой default должна следовать константа того же типа, что и свойство, например:
property Tag: Longint read FTag write FTag default 0 ;
Чтобы перекрыть наследуемое значение default без указания нового значения, используется директива nodefault. Директивы default и nodefault работают только с порядковыми типами и множествами, нижняя и верхняя границы которых лежат в промежутке от 0 до 31. Если такое свойство описано без директив default и nodefault, то оно рассматривается как с директивой nodefault. Для вещественных типов, указателей и строк значение после директивы default может быть только О, NIL и
(пустая строка) соответственно.
Когда Delphi сохраняет компонент, то просматриваются спецификаторы памяти published свойств компонента. Если значение текущего свойства отличается от default значения (или директива default отсутствует) и параметр stored равен True, то значение свойства сохраняется, иначе свойство не сохраняется.
Спецификаторы памяти не поддерживаются свойствами-массивами, а директива default при описании свойства-массива имеет другое назначение.