Построение компонентов

Теперь дополним модуль — заготовку нового компонента всем необходимым: свойствами, событиями и методами. Создадим работоспособный компонент и зарегистрируем его в среде 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-09-1.jpg

Рис. 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-09-2.jpg

Рис. 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-09-3.jpg

Рис. 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-09-4.jpg

Рис. 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;

Методы данного класса, приведенные ниже, можно переопределять для изменения поведения редактора свойств.

В большинстве случаев при создании нового редактора свойств нет необходимости использовать в качестве класса-предка базовый класс 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;

Рассмотрим методы нового класса:

Наконец, процедура 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;

Hosted by uCoz