Построение
компонентов
Теперь дополним модуль
— заготовку нового компонента всем необходимым: свойствами, событиями и методами.
Создадим работоспособный компонент и зарегистрируем его в среде Kylix. Затем
рассмотрим, как можно модифицировать уже существующие компоненты визуальной
библиотеки компонентов Kylix.
Создание свойств компонента
Добавление новых свойств
в компонент осуществляется очень просто. Достаточно задать поля и свойства,
определив при этом их тип и доступ (чтение, запись). Пример простого задания
свойств в новый компонент представлен в листинге 19.2.
Листинг 19.2. Пример
создания свойств нового компонента
MyButton = class(TButton) private
{ Private declarations }
FMyCount: Integer;
FStirngOfText: String; protected
{ Protected declarations } public
{ Public declarations } published
{ Published declarations }
property MyCount: Integer read FMyCouht 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
(рис 19.3).
Рис.
19.3. Перечисляемое свойство MyEnumProp
в новом компоненте
TMyButton
Создание свойств-множеств
в компоненте
Тип множества часто использовался
в 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 ЕМуЕnum write FMyEnum;
property MyOptions: TMySetType read FMyOptions write FMyOptions; end;
procedure Register; implementation
procedure Register; begin
RegisterComponents('Samples', [TMyButton]); end;
end.
Для удобства мы не стали
исключать определение перечисляемого свойства в компоненте TMyButton
.
В результате в окне инспектора
объектов свойство-множество будет отображаться как показано на рис. 19 4
Рис.
19.4. Свойство-множество MyOptions
в новом компоненте TMyButton
Создание свойства-объекта
в компоненте
Каждый компонент может
содержать в себе свойство-объект. В качестве свойства-объекта может выступать
любой компонент или объект Kylix. Кроме того, свойствами-объектами нового компонента
могут быть новые компоненты
или объекты, которые вы создали самостоятельно. Важным условием является тот
факт, что свойства-объекты должны быть потомками класса TPersistent.
Это необходимо для того, чтобы свойства объекта-свойства отображались в окне
инспектора объектов. Приведем пример создания свойства-объекта в нашем компоненте
TMyButton
.
Для начала создадим произвольный
новый объект, являющийся прямым потомком класса TPersistent
(листинг
19.5).
Листинг 19.5. Создание
потомка TPersistent
type
TMyObject = class (TPersistent) private
{ Private declarations }
FProperty1:Real;
FProperty2:Char; protected
{ Protected declarations } public
{ Public declarations }
procedure Assign (Source: TPersistent); published
{ Published declarations }
property Property1: Real read FProperty1 write FProperty1;
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;
Как вы можете видеть, мы
добавляем в код нового компонента конструктор и деструктор объекта.
Теперь осталось дописать
конструктор и деструктор для компонента TMyButton
, в которых необходимо,
соответственно, создать и уничтожить, кроме компонента TmyButton
,
объект TMyObject
. Также нужно написать код метода SetMyObject
,
который будет помещать новое значение в свойства объекта TMyObject
.
Ну и, конечно, написать код метода Assign
для объекта TMyObject
.
Полная версия кода представлена в листинге 19.7. Здесь, помимо ранее описанного,
приводится код ко всем этим методам.
Листинг 19.7. Полный
листинг для нового компонента
unit QMyButton;
interface
uses
SysUtils, Types, Classes, QGraphics, QControls, QForms, QDialogs, QStdCtrls;
type
TMyObject = class (TPersistent) private
{ Private declarations }
FProperty1:Real;
FProperty2:Char; protected
{ Protected declarations }
public
{ Public declarations }
procedure Assign (Source: TPersistent); published
{ Published declarations }
property Property1: Real read FProperty1 writhe FProperty1;
property Property2: Char read FProperty2 write FProperty2; end;
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; procedure Register;
implementation
procedure Register; begin
RegisterComponents('Samples', [TMyButton]); end;
procedure TMyButton.SetMyObject(Value: TMyObject); begin
if Assigned(Value) then FMyObject.Assign(Value); end;
constructor TMyButton.Create(AOwner: TComponent); begin
inherited Create (AOwner); FMyObject:=TMyObject.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).Property1; FProperty2: = TMyObject (Source) .Property2; inherited Assign (Source); end; end;
end.
Рис.
19.5. Свойство-объект MyObject
в новом компоненте
TMyButton
Обратите внимание на реализацию
метода TMyObject. Assign
. Здесь сначала выполняется проверка на
то, правильный ли передается экземпляр объекта TMyObject
. Если
он правильный, то значения свойств (Source
) переносятся в поля
FProperty1
и FProperty2
объекта TMyButton
.
Результатом исполнения вышеприведенного кода будет новый компонент TmyButton
,
содержащий в себе свойство-объект TMyObject
. В окне инспектора
объектов это будет выглядеть так, как представлено на рис. 19.5.
Создание свойства-массива
в компоненте
Свойства компонента могут
быть практически любого типа, которые поддерживает язык Object Pascal. Некоторые
свойства могут быть массивами. Яркими примерами свойств такого типа являются
TMemo
. Lines
и TDBGrib.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 GetMonthNurnber (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[l..12] of String[8],*('Январь','Февраль','Март', 'Апрель','Май','Июнь','Июль','Август','Сентябрь','Октябрь','Ноябрь', 'Декабрь');
function TWeek.GetMonthName(const AIndex: Integer): String; begin
if (AIndex<=0) or (AIndex>12) then raise Exception.Create('Номер месяца должен быть числом от 1 до 12')
else Result;=MonthNames [Alndex3]; 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
с именем Week1
,
при выполнении строки
ShowMessage (Weekl.MonthName[5]);
будет выдано окно с сообщением
Май (рис. 19.6).
Рис.
19.6. Результат работы компонента TWeek
Создание собственных
редакторов свойств
Перед тем как создавать
собственный редактор свойств компонента, рассмотрим сначала имеющиеся в Kylix
редакторы свойств. Их достаточно легко видеть в окне инспектора объектов, когда
вы пытаетесь изменить какое-либо свойство компонента. Например, когда вы изменяете
свойство Color
для какого-либо компонента, вы видите редактор свойства
цвета. Важно заметить, что все свойства компонентов имеют свои редакторы, даже
такие простейшие свойства, как Caption, Height, Enabled
имеют свои
редакторы свойств. Особенностью этих редакторов является то, что компоненты
"не знают",
какие редакторы вы используете для изменения их свойств. Это может навести на
мысль использовать собственный редактор свойств вместо заданного. Например,
можно написать редактор свойства Width
, который будет ограничен
каким-либо числом.
Редакторы свойств имеют
свою иерархию. Рассмотрим ее.
Базовым классом в иерархии
редакторов свойств является TPropertyEditor
. Названия классов говорят
сами за себя. Например, класс TColorProperty
отвечает за свойство
цвета компонента, класс TIntegerProperty
связан с теми свойствами,
которые имеют тип Integer
, и т. д. В листинге 19.9 приведен код,
определяющий базовый класс TPropertyEditor
.
Листинг 19.9. Определение
базового класса TPropertyEditor
TPropertyEditor = class(TBasePropertyEditor, IProperty) private
FDesigner: IDesigner;
FPropList: PInstPropList;
FPropCount: Integer;
function GetPnvateDirectory: 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 GetIntfValue: IInterface;
function GetIntfValueAt(Index: Integer): IInterface;
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 SetIntfValue(const Value: IInterface); protected { IProperty } function GetEditValue(out Value: string): Boolean;
function GetComponentValue: TComponent; virtual;
function HasInstance(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: TPrppertyAttributes; 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: PTypeInfo;
function GetValue: string; virtual;
function GetVisualValue: string;
procedure GetValues(Proc: TGetStrProc); virtual;
procedure Initialize; override;
procedure Revert;
procedure SetValue(const Value: string); virtual;
functipn 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
.Get Attributes
возвращает множество значений типа TProperyAttributes
, определяющих,
каким образом свойство будет отображаться в окне инспектора объектов.GetComponent
предназначен для определения компонента по его номеру (параметр Index
)
в случае, если на форме выбрано несколько компонентов одновременно.GetEditLimit
возвращает максимальное число символов в строке, которые пользователь может
ввести при редактировании свойства.GetName
предназначен для получения имени свойства. Данный метод целесообразно изменять
только в том случае, когда имя свойства отличается от имени, отображаемого
в окне инспектора объектов.GetPropType
применяется для определения указателя на информацию о типе редактируемого
свойства.GetValue
возвращает значение свойства в виде строки. Initialize
вызывается при создании (инициализации) редактора свойств.SetValue
применяется для установки значения свойства. В большинстве случаев при
создании нового редактора свойств нет необходимости использовать в качестве
класса-предка базовый класс 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
(который применяется для того, чтобы редактирование данного свойства было
возможно только через диалог);GetValue
заменяет символы перевода каретки (#10) и переход на новую строку (#13) на
символ больше (>). Наконец, процедура
Edit
применяется для вызова диалога ввода строк всплывающей подсказки.
Для регистрации нового
редактора нужно в интерфейсной части модуля поместить Объявление Процедуры Register
.
После чего В части Implementation
модуля написать саму процедуру
регистрации (листинг 19.11).
Листинг 19.11. Процедура регистрации нового редактора свойств
procedure Register; begin
RegisterPropertyEditor (TypeInfо(String), TControl, 'Hint',
THintProperty); end;
Данная процедура позволяет
привязать один и тот же редактор к свойствам, в зависимости от их названия или
типа. Это определяется параметрами, которые передаются процедуре RegisterPropertyEditor
.
Первый параметр определяет тип свойства (в нашем примере это String
).
Второй параметр определяет класс компонента. Третий параметр позволяет указать
имя свойства, четвертый — имя редактора свойства.
Для того чтобы установить
новый редактор свойств в Kylix, необходимо выполнить следующие шаги:
1. Выбрать пункт меню Component/Install
Components.
2. Нажать кнопку Add.
3. Указать имя подключаемого
модуля.
После того как произойдет
компиляция библиотеки, можно создать новую форму и разместить на ней какой-либо
компонент, после чего установите у этого компонента свойство ShowHint
в true
и нажмите кнопку <...> в свойстве Hint
.
Вы увидите на экране новый многострочный редактор для свойства Hint
.
Команды Default и
NoDefault
Итак, мы уже умеем создавать
свойства произвольного типа для собственного компонента. Осталось заметить,
что многим свойствам можно присвоить конкретное значение, которое будет установлено
по умолчанию. Для этого достаточно присвоить это значение полю компонента, например:
FMyProperty := 10;
В результате этого при
каждом добавлении компонента на форму двойство MyProperty
будет
принимать значение 10.
Команды Default
и NoDefault
применяются для ускорения процесса загрузки формы при
работе приложения. Например,
property MyCount:
Integer read FMyCount write FmyCount Default 0;
Данный код не присваивает
значение 0 свойству MyCount
. При выполнении вышеприведенного
кода команда default 0 означает следующее: если при сохранении формы, содержащей
компонент, значение свойства MyCount
не — будет равно нулю, то
новое значение сохранится в файле формы, иначе значение данного свойства не
будет сохранено.
Примечание
Рекомендуется использовать команду
Default
во всех случаях, когда это возможно, если вы хотите создать быстро работающее приложение.
Команда NoDefault
предназначена для нейтрализации команды Default
. Команда применяется
для отмены команды Default
компонентов-предков. Пример использования
команды NoDefault
:
TSecondComponent
= class (TMyButton)
published
property
MyCount NoDefault 0;