Основы создания компонентов
Выбор предка компонента
Создание заготовки компонента
Статические методы
Виртуальные методы
Динамические методы
Методы-сообщения
Замешенные методы
Абстрактные методы
Построение компонентов
Создание свойств компонента
Создание собственных редакторов свойств
Команды Default и NoDefault
Создание событий компонента
Пример создания нового события компонента
Создание методов компонента
Регистрация компонента в среде Kylix
Данная глава посвящена творческому процессу создания собственных компонентов. Мы рассмотрим различные способы создания новых компонентов, а именно - как создавать невизуальные и визуальные компоненты.
Основы создания компонентов
Итак, мы приступаем к процессу создания собственного визуального или невизуального
компонента. Для создания собственного компонента важно иметь представление о
библиотеке визуальных компонентов Kylix, об иерархии компонентов. Все это вам
уже знакомо.
Для чего же нужны новые компоненты? Зачем их создавать? Решение о создании новых
компонентов может быть принято по ряду причин, среди которых:
- разработка нового пользовательского интерфейса с дальнейшим использованием
его в других приложениях;
- создание принципиально нового класса, которого нет в стандартной библиотеке
Kylix;
- упрощение кода приложения путем введения новых компонентов;
- распространение своих компонентов среди других программистов;
- желание глубоко изучить среду Kylix, разобраться с тонкостями программирования.
Естественно, кроме названных причин, вы можете назвать множество собственных.
Создание компонентов по сложности практически не отличается от создания приложений.
Конечно, все зависит от сложности компонента. Но, если вы уже решились на создание
компонента, рекомендации будут следующими:
- определите для себя, какие действия должен выполнять компонент;
- разработайте краткий алгоритм, по которому будет работать компонент;
- разбейте всю конструкцию компонента на независимые части;
- предоставьте возможность дальнейшей разработки компонента (возможно, в будущем
вы захотите создать на его основе компонент-потомок);
- напишите код компонента (этот пункт разбивается на такие этапы):
Далее мы рассмотрим перечисленные выше этапы создания компонента.
Выбор предка компонента
Итак, вы уже знаете основные классы, имеющиеся в CLX Kylix. Ранее мы рассмотрели
базовые классы, которые могут являться предками вашего компонента (см. главу
7). Эти классы перечислены в табл. 19.1.
Таблица 19.1. Базовые классы CLX
Класс | Возможности класса |
TObject | Классы, предком которых является данный класс, не являются компонентами. Данный класс применяется при создании объектов, которые обычно являются предками для других компонентов |
TComponent | Применяется для создания невизуальных компонентов |
TGraphicControl | Применяется для создания не оконных компонентов, т. е. компонентов без дескриптора окна. Потомки данного класса размещаются в клиентской области своих родительских компонентов и не требуют системных ресурсов |
TWidgetControl | Применяется для создания компонентов, имеющих дескриптор окна. Данные компоненты являются компонентами оконного типа и могут содержать в себе другие компоненты |
TCustomControl | Данный класс является потомком TWinControl и дополняет его областью вывода (канвой). В этот класс добавлен метод Paint. Рекомендуется использовать такой класс для создания пользовательских оконных компонентов |
TCustomClassName | Библиотека визуальных компонентов содержит несколько классов, у которых не все свойства объявлены как published, т. е. доступны из других модулей, но на основе данных классов можно создавать классы-потомки, в которых можно объявлять данные свойства. Таким образом, разработчик может создать несколько идентичных классов на основе одного класса className и в каждом из этих классов определять необходимые свойства из набора предопределенных свойств |
TComponentName | Позволяет создавать компоненты-потомки, предками которых являются обычные компоненты или классы CLX Kylix. Таким образом, если перед разработчиком стоит задача расширить возможности какого-либо компонента Kylix, можно использовать данный класс |
Обратите внимание на то, что для правильного выбора класса-предка вам нужно очень хорошо ориентироваться в возможностях уже существующих в Kylix классов.
Создание заготовки компонента
Итак, вы выбрали класс-предок для вашего компонента. Теперь можно приступать
к созданию модуля вашего компонента. Создание модуля (заготовки) для нового
компонента можно выполнить путем вызова окна Kylix, которое называется экспертом
компонентов (Component Expert). Данное окно можно вызвать путем выбора в главном
меню Kylix пункта Соmponent/New Component. При этом появляется окно, изображенное
на рис. 19.1.
Рассмотрим данное окно. Итак, первое поле ввода Ancestor type предназначено
для ввода класса - предка для нового компонента. Данное поле ввода держит в
выпадающем списке все зарегистрированные классы библиотеки СLX. Предположим,
что мы будем создавать компонент, предком которого яется кнопка TButton. Для
этого выберем в выпадающем списке класс fButton. Следующее поле Class Name предназначено
для ввода имени нового класса. Пусть в нашем случае это будет новый класс TMyButton.
Заметьте, что по умолчанию Kylix заполняет это поле именем класса-предка с добавлением
порядкового номера (в нашем случае TButtoni). Следующее поле Palette Page показывает,
на какой вкладке палитры компонентов будет расположен новый компонент после
его регистрации. Оставим в этом поле значение, предлагаемое Kylix по умолчанию
samples. Следующие два поля Unit file name и Search path заполняются средой
Kylix самостоятельно, но разработчик может их изменить. Мы не будем этого делать
в нашем примере. В результате окно эксперта компонентов должно быть заполнено
так. как показано на рис. 19.2.
Рис. 19.1. Окно эксперта компонентов
Рис. 19.2. Заполненное окно эксперта компонентов
После заполнения полей данного окна нажимаем кнопку ОК, и Kylix автоматически создаст заготовку модуля вашего компонента. Модуль заготовки для нашего примера представлен в листинге 19.1.
Листинг 19.1. Модуль заготовки нового класса
unit QMyButton;
interface
uses
SysUtils, Types, Classes, QGraphics, QControls, QForms, QDialogs, QStdCtrls;
type
TMyButton = class(TButton)
private
{ Private declarations }
protected
{ Protected declarations }
public
{ Public declarations }
published
{ Published declarations }
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents (' Samples ', [TMyButton] ) ;
end;
end.
Итак, заготовка для нового компонента готова. Она не содержит никаких новых
свойств, методов и событий для нового компонента. Нужно отметить, |что данный
компонент уже имеет все свойства, события и методы, которые определены для класса
TButton.
Рассмотрим теперь основные типы методов компонента.
Все методы могут быть одного из нескольких типов: статические (static), виртуальные
(virtual), динамические (dynamic) или методы-сообщения (message). По умолчанию
методу присваивается статический тип.
Статические методы
Статические методы аналогичны обычным функциям или процедурам Kylix. Адрес такого
метода известен среде Kylix на стадии компиляции, поэтому Kylix производит статический
вызов метода во время выполнения программы. Статические методы работают быстрее
других методов, но не могут быть перегружены (overload).
Перегрузка метода подразумевает, что класс или компонент может содержать несколько
методов с одинаковым именем, но разными списками параметров
Статический метод может быть описан так:
type
TComponent = class
procedure MyProcedure;
end;
Здесь метод MyProcedure является статическим. Мы опустили в данном примере название базового класса TObject после слова class. По умолчанию компилятор создает наследников класса TObject.
Виртуальные методы
Виртуальные методы, в отличие от статических, поддерживают перегрузку, поэтому
вызов таких методов для среды Kylix намного сложнее (заранее неизвестен адрес
конкретного вызываемого метода). Для того чтобы решить эту проблему, Kylix строит
таблицу виртуальных методов (Virtual Method Table), благодаря которой компилятор
может определить адрес метода во время выполнения программы. Такая таблица содержит
виртуальные методы не только самого класса или компонента, но и его предков.
Естественно, хранение такой таблицы увеличивает расходы памяти, но вызов виртуальных
методов выполняется быстрее, чем вызов динамических методов.
Описание виртуального метода выглядит следующим образом:
type
TComponent = class
procedure MyProcedure; virtual;
end;
В данном случае метод MyProcedure - виртуальный.
Динамические методы
Динамические методы похожи на виртуальные, они также могут быть перегружены.
Основная разница между виртуальными и динамическими методами - в способе их
вызова. Если для виртуальных методов строится таблица виртуальных методов, то
каждому динамическому методу присваивается уникальное число-идентификатор, после
чего строится таблица динамических методов (Dynamic Method Table), в которую
заносится данное число, а также адрес метода. Еще одно отличие динамических
методов от виртуальных заключается в том, что таблицы динамических методов содержат
методы только одного компонента или класса (не включая его предков). Поэтому
существенно экономится память, но замедляется время работы, т. к. для поиска
адреса метода обычно приходится просматривать несколько таблиц динамических
методов.
Описание динамического метода может выглядеть так:
type
TComponent = class
procedure MyProcedure; dynamic;
end;
Методы-сообщения
Методы-сообщения не вызываются из программы непосредственно, как другие методы.
Этот тип методов предназначен для того, чтобы выполнить какие-либо действия
в ответ на сообщение операционной системы. В качестве примера рассмотрим описание
метода-сообщения:
type
TComponent = class
procedure MyProcedure(Var A: TMessage); message wm_MessageWindows;
end;
После служебного слова message ставится значение (в нашем случае m_Messagewindows),
которое определяет сообщение операционной системы, в ответ на которое будет
вызван метод MyProcedure.
Виртуальные и динамические методы могут быть замещенными (overriden) или абстрактными
(abstract).
Замещенные методы
Замещение методов предполагает передачу и изменение методов от компонента (класса)
предка компоненту (классу) наследнику. Как мы уже отмечали, только виртуальные
или динамические методы могут быть замещениями. Рассмотрим пример:
type
TComponentChild = class (TComponentParent)
procedure MyProcVirtual; override;
procedure MyProcDynamic; override;
end;
Применение служебного слова override после названия метода позволяет заместить
оригинал метода компонента предка методом компонента наследника. При этом замещение
происходит непосредственно в таблице виртуальных методов (или таблице динамических
методов). При использовании служебных слов virtual или dynamic вместо override
произойдет создание нового метода вместо замещения старого.
Замещение методов не работает со статическими методами - при замещении статического
метода новым произойдет простая замена метода родителя в потомке.
Абстрактные методы
Абстрактными методами называются такие методы, которые описаны внутри определения
класса или компонента, но не содержат никаких действии и никогда не вызываются.
Абстрактными могут быть виртуальные или динамические методы. Абстрактные методы
используются только в компонентах или классах-предках. Описание абстрактного
метода выглядит следующим образом:
procedure MyProcedure; virtual; abstract;
Примечание
Никогда не вызывайте на выполнение абстрактные методы, т. к. они не содержат
никаких команд, которые могли бы выполниться. Вызов абстрактного метода приведет
к генерации исключительной ситуации EAbstractError.
О создании свойств, методов и событий новых компонентов расскажет следующая глава этой книги.
Построение компонентов
Теперь дополним модуль - заготовку нового компонента всем необходимым свойствами,
событиями и методами. Создадим работоспособный компонент и зарегистрируем его
в среде Kylix. Затем рассмотрим, как можно инфицировать уже существующие компоненты
визуальной библиотеки компонентов Kylix.
Создание свойств компонента
Давление новых свойств в компонент осуществляется очень просто. Достаточно задать
поля и свойства, определив при этом их тип и доступ (чтение, запись). Пример
простого задания свойств в новый компонент едставлен в листинге 19.2.
Листинг 19.2. Пример создания свойств нового компонента:
TMyButton = class(TButton)
private
{ Private declarations }
FMyCount: Integer;
FStirngOfText: String;
protected
{Protected declarations }
public
{ Public declarations }
published
{ Published declarations }
property MyCount: Integer read FMyCount write FMyCount;
property StringOfText: String read FStringOfText write FStringOfText;
end;
В этом листинге мы задаем два новых поля для компонента TMyButton и определяем два новых свойства компонента, одно типа integer, другое - String. простоты значения данных свойств считываются и записываются в одноименные поля. Задание этих свойств будет гарантировать доступ к ним в окне инспектора объектов (благодаря описанию свойств в разделе published) и не требует написания дополнительных методов для доступа к свойствам.
Примечание
По взаимной договоренности принято названия полей начинать с буквы f (field
"поле"), а названия компонентов и любых объектов - с буквы t (type
- "тип").
Создание перечисляемых свойств компонента
К свойствам перечисляемого типа относятся такие свойства компонента, которые
при их редактировании в окне инспектора объектов вызывают раскрывающийся список,
содержащий возможные значения данного свойства
К числу таких свойств относятся Align, BorderStyle, Color и др. Для того чтобы
самостоятельно добавить в новый компонент перечисляемое свойство, необходимо
сначала определить новый перечисляемый тип, например:
TMyEnumType = (eFirst, eSecond, eThird);
После этого нужно определить поле компонента, которое будет хранить значение данного перечисляемого типа, затем определить свойство. Пример добавления перечисляемого типа в новый компонент TMyButton приведен в Листинге 19.3.
Листинг 19.3. Создание свойств перечисляемого типа
unit QMyButton;
interface
uses
SysUtils, Types, Classes, QGraphics, QControls, QForms, QDialogs, QStdCtrls;
type
TMyEnumType = (eFirst, eSecond, eThird);
type
TMyButton = class (TButton) private
{ Private declarations }
FMyEnum: TMyEnumType; protected
{ Protected declarations } public
{ Public declarations } published
{ Published declarations }
property MyEnumProp: TMyEnumType read FMyEnum write FMyEnum;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Samples', [TMyButton])
end;
end.
Таким образом, в окне инспектора объектов при изменении свойства MyEnumProp
будет выдан раскрывающийся список, содержащий три пункта:
eFirst, eSecond и eThird
Создание свойств-множеств в компоненте
Тип множества часто использовался в Object Pascal, и некоторые свойства компонентов
Kylix имеют данный тип. Когда вы используете свойство типа множество, вы должны
учитывать, что каждый элемент множества будет являться отдельным свойством,
имеющим логический тип в инспекторе объектов.
Для создания свойства-множества сначала зададим нужный тип:
TMySetTypeFirst = (poFirst, poSecond, poThird);
TMySetType = set of TMySetTypeFirst;
Первая строка задает перечисляемый тип TMySetTypeFirst, который определяет
диапазон множества. Вторая строка задает само множество TMySetType.
Пример добавления свойства-множества в компонент TMyButton приведен н листинге
19.4.
Листинг 19.4. Создание свойства-множества
unit QMyButton;
interface
uses
SysUtils, Types, Classes, QGraphics, QControls, QForms, QDialogs, QStdCtrls;
type
TMyEnumType = (eFirst, eSecond, eThird);
TMySetTypeFirst = (poFirst, poSecond, poThird);
TMySetType = set of TMySetTypeFirst;
type
TMyButton = class(TButton)
private
{ Private declarations }
FMyEnum: TMyEnumType;
FMyOptions: TMySetType;
protected
{ Protected declarations }
public
{ Public declarations }
published
{ Published declarations }
property MyEnumProp: TMyEnumType read FMyEnum write FMyEnum;
property MyOptions: TMySetType read FMyOptions write FMyOptions;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Samples', [TMyButton])
end;
end.
Для удобства мы не стали исключать определение перечисляемого свойства в компоненте
TMyButton.
Создание свойства-объекта в компоненте
Саждый компонент может содержать в себе свойство-объект. В качестве свойства-объекта
может выступать любой компонент или объект Kylix. Кроме того, свойствами-объектами
нового компонента могут быть новые компоненты или объекты, которые вы создали
самостоятельно. Важным условием является тот факт, что свойства-объекты должны
быть потомками класса TPersistent. Это необходимо для того, чтобы свойства объекта-свойства
отображались в окне инспектора объектов. Приведем пример создания свойства-объекта
в нашем компоненте TMyButton.
Для начала создадим произвольный новый объект, являющийся прямым потомком класса
TPersistent (листинг 19.5).
Листинг 19.5. Создание потомка TPersistent
type
TMyObject = class (TPersistent)
private
{ Private declarations }
FPropertyl:Real;
FProperty2:Char;
protected
{Protected declarations }
public
{ Public declarations }
procedure Assign (Source: TPersistent);
published
( Published declarations }
property Propertyl: Real read FPropertyl write FPropertyl;
property Property2: Char read FProperty2 write FProperty2;
end;
В качестве предка нового класса может выступать не только класс TPersistent, но и любой его потомок. В вышеприведенном листинге мы создаем новый класс TMyObject, в котором присутствуют два простых свойства - Property1 и Property2. Кроме того, в новый объект включена процедура Assign. Данная процедура необходима для обеспечения правильного доступа к свойству нашего будущего компонента TMyButton. Ниже приведен листинг 19.6, в котором мы добавляем в компонент TMyButton новое свойство-объект.
Листинг 19.6. Добавление свойства-объекта в компонент TMyButton;
type
TMyButton = class(TButton)
private
{ Private declarations }
FMyObject : TMyObject;
procedure SetMyObject (Value: TMyObject) ;
protected
{ Protected declarations }
public
{ Public declarations }
constructor Create (AOwner: TComponent) ;
override;
destructor Destroy; override;
published
{ Published declarations }
property MyObject: TMyObject read FMyObject write SetMyObject;
end;
Как вы можете видеть, мы добавляем в код нового компонента конструктор и деструктор
объекта.
Теперь осталось дописать конструктор и деструктор для компонента fyButton, в
которых необходимо, соответственно, создать и уничтожить, кроме компонента TmyButton,
объект TMyObject. Также нужно написать код эда SetMyObject, который будет помещать
новое значение в свойства объекта TMyObject. Ну и, конечно, написать код метода
Assign для объекта lyObject. Полная версия кода представлена в листинге 19.7.
Здесь, помимо описанного, приводится код ко всем этим методам.
Листинг 19.7. Полный листинг для нового компонента
unit QMyButton;
interface
uses
SysUtils, Types, Classes, QGraphics, QControls, QForms, QDialogs, QStdCtrls;
type
TMyObject = class (TPersistent)
private
{ Private declarations }
FPropertyl:Real ;
FProperty2:Char;
protected
{ Protected declarations }
public
{ Public declarations }
procedure Assign (Source: TPersistent);
published
{ Published declarations }
property Propertyl: Real read FPropertyl write FPropertyl;
property Property2: Char read FProperty2 write FPrcperty2;
end;
type
TMyButton = class(TButton)
private
{ Private declarations }
FMyObj ect:TMyObj ect;
procedure SetMyObject (Value: TMyObject);
protected
{ Protected declarations }
public
{ Public declarations }
constructor Create (AOwner: TComponent); override;
destructor Destroy; override;
published
{ Published declarations }
property MyObject: TMyObject read FMyObject write SetMyObject;
end;
procedure Register;
implementation
procedure Register; begin
RegisterComponents('Samples', [TMyButton]);
end;
procedure TtMyButton. SetMyObject (Value: TMyObject);
begin
if Assigned(Value) then FMyObject.Assign(Value);
end;
constructor TMyButton.Create(AOwner: TComponent);
begin
inherited Create (AOwner);
FMyObject:=TMyObj ect.Create;
end;
destructor TMyButton.Destroy;
begin
FMyObject.Free;
inherited Destroy;
end;
procedure TMyObject.Assign(Source: TPersistent) ;
begin
if Source is TMyObject then
begin
FProperty1:=TMyObject(Source).Propertyl;
FProperty2:=TMyObject(Source).Property2;
inherited Assign (Source);
end;
end;
end.
Обратите внимание на реализацию метода TMyobject.Assign. Здесь сначала выполняется проверка на то, правильный ли передается экземпляр объекта TMyobject. Если он правильный, то значения свойств (source) переносятся в поля FPropertyl и FFroperty2 объекта TMyButton. Результатом исполнения вышеприведенного кода будет новый компонент TmyButton, содержащий в себе свойство-объект TMyobject.
Создание свойства-массива в компоненте
Свойства компонента могут быть практически любого типа, которые поддерживает
язык Object Pascal. Некоторые свойства могут быть массивами. Яркими примерами
свойств такого типа являются TMemo.Lines и TDBGrid.Columns.. Такие свойства
требуют собственных редакторов. Мы пока остановимся на создании простого свойства,
которое представляет собой простой массив (о создании собственных редакторов
свойств читайте далее в этой главе). Создадим новый компонент TWeek, который
будет содержать два свойства: Month и Number. Свойство Month будет представлять
собой массив, который будет возвращать название месяца по переданному целому
числу от 1 до 12. Свойство Number - тоже массив, который возвращает число, соответствующее
названию месяца.
Итак, в листинге 19.8 приведен код компонента TWeek.
Листинг 19.8. Пример компонента TWeek
unit Week;
interface
uses
SysUtils, Types, Classes, QGraphics, QControls, QForms, QDialogs;
type
TWeek = class(TComponent)
private
{ Private declarations }
function GetMonthName (const AIndex: Integer): String;
function GetMonthNumber (const AMonthName: String): Integer;
protected
( Protected declarations }
public
{ Public declarations }
property MonthName [const AIndex: Integer]: String read GetMonthName ;
default;
property MonthNumber [const AMonthName: String]: Integer read
GetMonthNumber;
published
{ Published declarations }
end;
procedure Register;
implementation
const
MonthNames: array [1.. 12] of String [8]= ( 'Январь ', 'Февраль ', 'Март' ,
'Апрель ' , 'Май' , 'Июнь ' , 'Июль ' , 'Август' , 'Сентябрь ' , 'Октябрь '
, 'Ноябрь ' , 'Декабрь ' ) ;
function TWeek. GetMonthName (const AIndex: Integer): String; agin
if (AIndex<=0) or (AIndex>12) then raise Exception. Create ( 'Номер месяца
должен быть числом от 1 до 12')
else Result :=MonthNames [AIndex] ;
end;
function TWeek. GetMonthNumber (const AMonthName: String): Integer;
var
i: integer;
begin
Result :=0;
for i:=l to 12 do begin
if Uppercase (AMonthName )=UpperCase (MonthNames [i] ) then
Result :=i ;
end;
end;
procedure Register;
begin
RegisterComponents ( ' Samples ' , [TWeek] ) ;
end;
end.
Рассмотрим вышеприведенный код более подробно. Как вы можете видеть, свойства
типа массив объявляются вместе с индексами. Для свойства MonthName мы определили
индекс Aindex, а для свойства MonthNumber - индекс AMonthName. Для доступа к
свойствам такого типа необходимо использовать методы. Внутренние поля здесь
не используются. Если свойство типа массив многомерно, то свойство-массив объявляется
сразу с несколькими индексами. При вызове методов доступа к ним нужно передавать
параметры в том же порядке, в каком вы их указали в свойстве.
Функция GetMonthName возвращает строку, содержащую имя месяца, соответствующего
целому числу от 1 до 12, переданному в качестве параметра данной функции. В
случае передачи функции числа, не принадлежащего данному диапазону, будет сгенерировано
исключение командой raise (об исключениях читайте главу 9 книги).
Наконец, функция GetMonthNumber возвращает число от 1 до 12, которое соответствует
названию месяца, переданного в качестве параметра в данную функцию. В случае
если ни один месяц не соответствует названию, определенному массивом MonthNames,
результатом выполнения данной функции будет ноль.
Таким образом, если поместить на форму экземпляр компонента TWeek с именем weekl,
при выполнении строки
ShowMessage (Weekl.MonthName[5]);
будет выдано окно с сообщением Май.
Создание собственных редакторов свойств
Перед тем как создавать собственный редактор свойств компонента, рассмотрим
сначала имеющиеся в Kylix редакторы свойств. Их достаточно легко видеть в окне
инспектора объектов, когда вы пытаетесь изменить какое-либо свойство компонента.
Например, когда вы изменяете свойство Color для какого-либо компонента, вы видите
редактор свойства цвета. Важно заметить, что все свойства компонентов имеют
свои редакторы, даже такие простейшие свойства, как Caption, Height, Enabled
имеют свои редакторы свойств. Особенностью этих редакторов является то, что
компоненты "не знают", какие редакторы вы используете для изменения
их свойств. Это может навести на мысль использовать собственный редактор свойств
вместо заданного. Например, можно написать редактор свойства width, который
будет ограничен каким-либо числом.
Редакторы свойств имеют свою иерархию. Рассмотрим ее.
Базовым классом в иерархии редакторов свойств является TPropertyEditor.
Названия классов говорят сами за себя. Например, класс TCoiorProperty отвечает
за свойство цвета компонента, класс Tintegerproperty связан с теми свойствами,
которые имеют тип integer, и т. д. В листинге 19.9 приведен код, определяющий
базовый класс TPropertyEditor.
Листинг 19.9. Определение базового класса TPropertyEditor
TPropertyEditor = class(TBasePropertyEditor, IProperty)
private
FDesigner: IDesigner;
FPropList: PInstPropList;
FPropCount: Integer;
function GetPrivateDirectory: string;
protected
procedure SetPropEntry(Index: Integer; AInstance: TPersistent; APropInfo: PPropInfo);
override;
protected
function GetFloatValue: Extended;
function GetFloatValueAt (Index: Integer): Extended;
function GetInt64Value: Int64;
function GetInt64ValueAt(Index: Integer): Int64;
function GetMethodValue: TMethod;
function GetMethodValueAt(Index: Integer): TMethod;
function GetOrdValue: Longint;
function GetOrdValueAt(Index: Integer): Longint;
function GetStrValue: string;
function GetStrValueAt(Index: Integer): string;
function GetVarValue: Variant;
function GetVarValueAt(Index: Integer): Variant;
function GetlntfValue: Ilnterface;
function GetlntfValueAt(Index: Integer): Ilnterface;
procedure Modified;
procedure SetFloatValue(Value: Extended);
procedure SetMethodValue(const Value: TMethod) ;
procedure SetInt64Value(Value: Int64);
procedure SetOrdValue(Value: Longint);
procedure SetStrValue(const Value: string);
procedure SetVarValue(const Value: Variant);
procedure SetlntfValue(const Value: Ilnterface);
protected
{ IProperty }
function GetEditValue(out Value: string): Boolean;
function GetComponentValue: TComponent; virtual;
function Haslnstance(Instance: TPersistent): Boolean;
public
constructor Create(const ADesigner: IDesigner; APropCount: Integer);
override;
destructor Destroy; override
procedure Activate; virtual;
function AllEqual: Boolean; virtual;
function AutoFill: Boolean; virtual;
procedure Edit; virtual;
function GetAttributes: TPropertyAttributes; virtual;
function GetComponent(Index: Integer): TPersistent;
function GetEditLimit: Integer; virtual;
function GetName: string; virtual;
procedure GetProperties (Proc: TGetPropProc) ; virtual
function GetPropInfo: PPropInfo; virtual
function GetPropType: PTypelnfo;
function GetValue: string; virtual;
function GetVisualValue: string;
procedure GetValues(Proc: TGetStrProc); virtual;
procedure Initialize; override;
procedure Revert;
procedure SetValue(const Value: string); virtual;
function ValueAvailable: Boolean;
property Designer: IDesigner read FDesigner;
property PrivateDirectory: string read GetPrivateDirectory;
property PropCount: Integer read FPropCount;
property Value: string read GetValue write SetValue;
end;
Методы данного класса, приведенные ниже, можно переопределять для изменения
поведения редактора свойств.
- Метод Activate вызывается, когда данное свойство выбирается в окне инспектора
объектов.
- Метод AllEqual вызывается при выборе на форме более одного компонента.
- Метод Edit вызывается нажатием кнопки или двойным щелчком мыши на свойстве.
Данный метод может вызвать диалоговое окно для редактирования свойства, например,
как это происходит при редактировании свойства Font.
- Метод GetAttributes возвращает множество значений типа TProperyAttributes,
определяющих, каким образом свойство будет отображаться в окне инспектора объектов.
- Метод GetComponent предназначен для определения компонента по его номеру (параметр
Index) в случае, если на форме выбрано несколько компонентов одновременно.
- Метод GetEditLimit возвращает максимальное число символов в строке, которые
пользователь может ввести при редактировании свойства.
- Метод GetName предназначен для получения имени свойства. Данный метод целесообразно
изменять только в том случае, когда имя свойства отличается от имени, отображаемого
в окне инспектора объектов.
- Метод GetPropType применяется для определения указателя на информацию о типе
редактируемого свойства.
- Метод Getvalue возвращает значение свойства в виде строки.
- Метод Initialize вызывается при создании (инициализации) редактора свойств.
- Метод setvaiue применяется для установки значения свойства.
В большинстве случаев при создании нового редактора свойств нет необходимости
использовать в качестве класса-предка базовый класс TPropertyEditor. Часто разработчик
просто переделывает уже существующий для данного свойства редактор, переопределяя
некоторые его методы.
Рассмотрим в качестве примера переработанное свойство Hint, которое применяется
для показа всплывающей подсказки при задержании указателя мыши над компонентом.
В стандартном случае такая подсказка имеет всего одну строку. Попробуем сделать
свойство Hint многострочным. Листинг 19.10 показывает, как создать новый редактор
свойств THintProperty. В качестве класса-предка для данного редактора свойств
выберем редактор TStringProperty.
Листинг 19.10. Создание нового редактора свойств:
THintProperty = class(TStringProperty)
public
function GetAttributes: TPropertyAttributes; override;
function GetValue : String; override;
procedure Edit; override
end;
function THintProperty.GetAttributes: TPropertyAttributes;
begin
Result := inherited GetAttributes + [paDialog, paReadOnly];
end;
function THintProperty.GetValue : string;
var i : Byte;
begin
result:=inherited GetValue;
for i:=l to Byte(result[0]) do
if result[i]<#32 then result[i]:='>';
end;
procedure THintProperty.Edit; var
HintEditDlg : TStrEditDlg;
s : string;
begin
HintEditDlg:=TStrEditDlg.Create(Application);
with HintEditDlg do
try
Memo.MaxLength := 254;
s:=GetStrValue+#0;
Memo.Lines.SetText(@s[1] ) ;
UpdateStatus(nil);
ActiveControl := Memo;
If ShowModal = mrOk then begin
s:=StrPas(Memo.Lines.GetText);
if s[0]>#2 then Dec(Byte(s[0]),2);
SetStrValue(s);
end;
finally
Free;
end;
end;
Рассмотрим методы нового класса:
- функция GetAttributes добавляет к унаследованному множеству атрибуты paDialog
(при этом появляется кнопка в окне инспектора объектов) и paReadOnly (который
применяется для того, чтобы редактирование данного свойства было возможно только
через диалог);
- функция Getvaiue заменяет символы перевода каретки (#10) и переход на новую
строку (#13) на символ больше (>).
Наконец, процедура Edit применяется для вызова диалога ввода строк гплывающей
подсказки.
Для регистрации нового редактора нужно в интерфейсной части модуля поместить
объявление процедуры Register. После чего в части implementation модуля написать
саму процедуру регистрации (листинг 19.11).
Листинг 19.11. Процедура регистрации нового редактора свойств
procedure Register;
begin
RegisterPropertyEditor (Typelnfo(String), TControl, 'Hint',
THintProperty);
end;
Данная процедура позволяет привязать один и тот же редактор к свойствам, зависимости
от их названия или типа. Это определяется параметрами, которые передаются процедуре
RegisterPropertyEditor. Первый параметр определяет тип свойства (в нашем примере
это string). Второй параметр определяет класс компонента. Третий параметр позволяет
указать имя свойства, четвертый - имя редактора свойства.
Для того чтобы установить новый редактор свойств в Kylix, необходимо выполнить
следующие шаги:
1. Выбрать пункт меню Component/Install Components.
2. Нажать кнопку Add.
3. Указать имя подключаемого модуля.
После того как произойдет компиляция библиотеки, можно создать новую форму и
разместить на ней какой-либо компонент, после чего установите у этого компонента
свойство showHint в true и нажмите кнопку <...> в свойстве Hint. Вы увидите
на экране новый многострочный редактор для свойства Hint.
Команды Defaults NoDefault
Итак, мы уже умеем создавать свойства произвольного типа для собственного компонента.
Осталось заметить, что многим свойствам можно присвоить конкретное значение,
которое будет установлено по умолчанию. Для этого достаточно присвоить это значение
полю компонента, например:
FMyProperty := 10;
В результате этого при каждом добавлении компонента на форму свойство Myproperty
будет принимать значение 10.
Команды Default и NoDefault применяются для ускорения процесса загрузки формы
при работе приложения. Например,
property MyCount: Integer read EMyCount write FmyCount Default 0;
Данный код не присваивает значение 0 свойству MyCount. При выполнении вышеприведенного
кода команда default 0 означает следующее: если при сохранении формы, содержащей
компонент, значение свойства MyCount не будет равно нулю, то новое значение
сохранится в файле формы, иначе значение данного свойства не будет сохранено.
Примечание
Рекомендуется использовать команду Default во всех случаях, когда это возможно,
если вы хотите создать быстро работающее приложение.
Команда NoDefault предназначена для нейтрализации команды Default. Команда применяется для отмены команды Default компонентов-предков. Пример использования команды NoDefault:
TSecondComponent = class (TMyButton)
published
property MyCount NoDefault 0;
Создание событий компонента
Стандартные события компонентов CLX Kylix мы с вами уже рассматривали в предыдущих
главах. В настоящий момент нам предстоит дать четкое определение событию, а
также обработчику события.
Итак, событие - это любое действие, произошедшее благодаря операционной системе,
действиям пользователя, работе программы.
Событие можно "перехватить" и обработать с помощью программы-обработчика
события. Связь между событием и программой-обработчиком называется свойством-событием.
Таким образом, когда происходит какое-либо событие компонента, он может обработать
данное событие. Для этого сначала проверяется наличие кода обработки события.
Если такой код есть - он выполняется.
Рассмотрим в качестве примера такое часто возникающее событие, как нажатие левой
кнопки мыши ondick. Данное событие, как и многие другие, имеет так называемые
методы диспетчеризации событий (event-dispatching methods). Такие методы нужны
для того, чтобы определять, создан ли код обработки данного события для данного
компонента. Эти методы объявляются как защищенные (protected). Таким образом,
для свойства OnClick ределен метод диспетчеризации события Click (листинг 19.12).
Лигинг 19.12. Метод диспетчеризации события
TControl = class (TComponent)
private
FOnClick: TNotifyEvent;
protected
procedure Click; dynamic;
property OnClick:, TNotifyEvent read FOnClick write FOnClick;
end;
implementation
procedure TControl.Click;
begin
if Assigned (FOnClick) then FOnClick (Self);
end;
Обратите внимание: свойство OnClick имеет тип TNotifyEvent, который представляет собой процедуру с одним параметром sender типа TObject:
NotifyEvent = procedure (Sender: TObject) of object;
Иначе говоря, когда происходит вызов метода Click, осуществляется проверка,
ссылается ли FOnClick на какой-либо метод, и если ссылается, то роисходит вызов
этого метода.
Суть разобранного выше события в том, чтобы иметь представление, что задание
событий подразумевает написание кода, определяющего само событие, а также свойства
и метода диспетчеризации. Обработчик события пишется пользователем.
При возникновении какого-либо события операционная система передает приложению
не только информацию о наступлении данного события, но и некоторую дополнительную
информацию. В нашем примере при обработке события нажатия левой кнопки мыши
в приложение поступает информация о координатах внутри клиентской части компонента,
в которых произошло нажатие левой кнопки мыши. Например, код, приведенный в
листинге 19.13, выводит при наступлении события нажатия левой кнопки мышц на
форме координаты указателя мыши.
Листинг 19.13. Пример обработки события нажатия кнопки мыши:
procedure TForml.FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
Canvas. TextOut (X, Y, ' ('+IntToStr (X) + ', +IntToStr (Y)+ ')');
end;
Данный код мы вписали в обработчик события OnMouseDown формы Forml. Если теперь запустить программу на исполнение и пощелкать мышкой в разных частях формы, в тех местах формы, где происходит щелчок мыши, будут выведены координаты этих точек.
Пример создания нового события компонента
Попробуем теперь создать собственное событие. Для этого нужно сначала едиться,
что такого события нет в CLX Kylix. Предположим, возникла необходимость создания
события, которое генерируется каждые 30 секунд. Естетвенно, для такого случая
можно воспользоваться компонентом Timer, эрый расположен на вкладке System палитры
компонентов Kylix. Но допустим, что наш компонент должен иметь такое событие
для удобства работы с ним. Код для создания такого события представлен в листинге
19.14.
Листинг 19.14. Пример создания нового события
unit halfmin;
interface
uses
SysUtils, Types, Classes, QGraphics, QControls, QForms, QDialogs;
type
TTimeEvent = procedure (Sender: TObject; TheTime: TDateTime) of object;
THalfMinute = class (TComponent)
private
FTimer: TTimer;
FOnHalfMinute: TTimeEvent;
FOldSecond, FSecond: Word;
procedure FTimerTimer (Sender: TObject);
protected
procedure DoHalfMinute (TheTime: TDateTime); dynamic;
public
constructor Create (AOwner: TComponent); override;
destructor Destroy; override;
published
property OnHalfMinute: TTimeEvent read FOnHalfMinute write FOnHalf-
Minute;
end;
implementation
constructor THalfMinute.Create (AOwner: TComponent)
begin
inherited Create (AOwner);
if not (csDesigning in ComponentState) then begin
FTimer:=TTimer.Create(self) ;
FTimer.Enabled:=True;
FTimer.Interval:=500;
FTimer.OnTimer:=FTimerTimer;
end;
end;
destructor THalfMinute.Destroy;
begin
FTimer.Free;
inherited Destroy;
end;
procedure THalfMinute.FTimerTimer (Sender: TObject) ;
var
DT: TDateTime;
Temp: Word;
begin
DT:=Now;
FOldSecond:=FSecond;
DecodeTime (DT,Temp,Temp,FSecond,Temp);
if FSecond <> FOldSecond then
if ((FSecond=30) or (FSecond=0)) then
DoHalfMinute (DT); end;
procedure THalfMinute.DoHalfMinute(TheTime: TDateTime);
begin
if Assigned (FOnHalfMinute) then FOnHalfMinute (Self, TheTime);
end;
end.
Для проверки работоспособности вышеприведенного кода вы можете добавить еще одну процедуру для регистрации нового компонента с именем ThaifMinute, предварительно расположив в interface часть программы строку:
rocedure Register;
Ниже представлен код для регистрации компонента:
procedure Register;
begin
RegisterComponents('Samples', [THalfMinute]);
end;
Для просмотра работоспособности нового компонента, после его регистрации создадим новую форму и разместим на ней новый компонент. Добавим ia форму компонент Tedit, а затем - обработчик события OnHaifMinute формы (листинг 19.15).
Листинг 19.15. Обработчик нового события
procedure TForml.HalfMinutelHalfMinute(Sender: TObject;
TheTime: TDateTime);
begin
Editl.Text: = ('Время '+TimeToStr(TheTime)) ;
Editl.Refresh;
end;
В результате работы данной программы в компоненте Edit1 каждые 30 секунд будет выводиться текущее время.
Создание методов компонента
Добавление методов в новый компонент - операция несложная. Однако нужно обратить
внимание на некоторые особенности, которые в дальнейшем облегчат взаимодействие
пользователя с вашим компонентом.
Во-первых, необходимо, чтобы методы не были взаимозависимыми, т. е. каждый метод
должен быть самостоятельным и законченным. Во-вторых, метод не должен блокировать
компонент. И в-третьих, метод должен иметь имя, соответствующее выполняемым
действиям.
Как вы уже знаете, методы объявляются в секциях private, public и protected.
При создании нового метода важно учитывать, как он будет использоваться в дальнейшем,
сможет ли данный компонент быть предком для других компонентов, и, в зависимости
от этого, расположить методы в нужных секциях. Табл. 19.2 поможет вам при выборе
секций для методов компонента.
Таблица 19.2. Размещение методов компонента в различных секциях
Секция | Размещаемые методы |
Private | В данной секции лучше всего размещать те методы, которые не могут изменяться в компонентах-потомках. Данные методы не доступны вне данного компонента |
Protected | В этой секции размещают методы, которые будут доступны для изменения в компонентах-потомках |
Public | Данная секция предназначена для размещения методов, которые доступны любому пользователю компонента. Доступ полный во время работы приложения, но не во время разработки, т. е. данные методы недоступны в окне инспектора объектов |
Published | В этой секции размещаются свойства компонента, которые доступны во время разработки приложения в окне инспектора объектов |
Регистрация компонента в среде Kylix
Регистрация компонента необходима для размещения компонента в палитре компонентов.
При использовании эксперта компонентов для создания нового компонента Kylix
самостоятельно создает процедуру регистрации компонента в модуле-заготовке.
Создателю компонента в данном случае ничего не нужно делать, кроме следующих
шагов:
- выбрать пункт меню Component/Install Components;
- нажать кнопку Add;
- указать имя подключаемого модуля (естественно, предварительно нужно сохранить
модуль компонента).
После компиляции на выбранной вкладке палитры компонентов появится новый компонент.
Если же вы создаете компонент без использования эксперта компонентов, вам придется
самостоятельно дописывать процедуру регистрации компонента. В разделе interface
модуля компонента нужно дописать строку:
procedure Register;
А в разделе implementation - добавить процедуру регистрации. Например:
procedure Register/begin
RegisterComponent ('Samples', [TMyButton] );
end;
В результате, компонент с именем TMyButton будет размещен на вкладке Samples
палитры компонентов. Обратите внимание на значок, которым обозначается новый
компонент. Его можно поменять на другой. Для создания собственного значка можно
использовать любой редактор растровых изображений.
Воздайте значок для вашего компонента размером 24x24 пиксела. Данное изображение
сохраните в файле формата DCR. Имя файла - это имя вашero компонента, в котором
все буквы - заглавные. Например, для компонента TMyButton имя файла картинки
будет TMYBUTTON.DCR. Затем поместите файл картинки в ту папку, в которой находится
файл с модулем компонента. Перекомпилируйте модуль, и ваш компонент будет изображаться
в палитре компонентов вашим рисунком.
|
![]() |