Создание и использование пользовательских компонент в Delphi. 


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Создание и использование пользовательских компонент в Delphi.



Данная глава посвящена творческому процессу создания собственных компонентов. Мы рассмотрим различные способы разработки новых компонентов, научимся строить свои невизуальные компоненты.
Основы создания компонентов
Итак, мы приступаем к процессу создания собственного визуального или невизуального компонента. Для создания собственного компонента важно иметь представление о библиотеке визуальных компонентов Delphi, об иерархии компонентов. Все это мы уже рассматривали выше. Для чего же нужны новые компоненты? Ответ неоднозначный. Решение о создании но вых компонентов может быть принято по ряду причин, среди которых:
- разработка нового пользовательского интерфейса, с дальнейшим использованием его в других приложениях;
- создание принципиально нового класса, которого нет в стандартной библиотеке Delphi и среди элементов ActiveX;
- упрощение кода приложения, путем введения новых компонентов; - распространение своих компонентов среди других программистов;
- и, наконец, желание глубоко изучить Delphi, разобраться с тонкостями программирования.
Естественно, кроме упомянутых причин, вы можете назвать множество собственных. Создание компонентов по сложности практически не отличается от разрабокти приложений. Конечно, все зависит от сложности компонента. Но если вы уже решились на сотворение компонента, рекомендации будут следующими:
- определите для себя, какие действия должен выполнять компонент; - разработайте краткий алгоритм, по которому будет работать компонент;
- разбейте всю конструкцию компонента на независимые части;
- предоставьте возможность дальнейшей разработки компонента (возможно, в будущем вы захотите создать на его основе компонент-потомок);
- напишите код компонента (этот пункт разбивается на такие этапы):
выбор предка для вашего компонента;
создание заготовки (модуля) компонента;
создание свойств, событий и методов компонента;
отладка и тестирование;
регистрация компонента в среде Delphi;
создание справки для вашего компонента. Далее мы рассмотрим перечисленные выше этапы создания компонента.
Выбор предка компонента
Итак, вы уже знаете основные классы, имеющиеся в VCL Delphi. Ранее мы рассмотрели базовые классы, которые могут являться предками вашего компонента (см. главу 6). Эти классы перечислены в табл. 2.9.
Таблица 2.9. Базовые классы VCL

       
  Класс Возможности класса  
  TObject Классы, предком которых является данный класс, не являются компонентами. Класс TObject применяется при создании объектов, которые, обычно, являются предками для других компонентов  
  TComponent Применяется для создания невизуальных компонентов  
  TGraphicControl Применяется для создания не оконных компонентов, т. е. компонентов без дескриптора окна. Потомки данного класса размещаются в клиентской области своих родительских компонентов и не требуют системных ресурсов  
  TWinControl Применяется для создания компонентов, имеющих дескриптор окна. Данные компоненты являются компонентами оконного типа и могут содержать в себе другие компоненты  
  TCustomControl Этот класс является потомком TWinControl и дополняет его областью вывода (канвой). В данный класс добавлен метод Paint. Рекомендуется использовать настоящий класс для создания пользовательских оконных компонентов  
       
       
  TCustomClassName Библиотека визуальных компонентов содержит несколько классов, у которых не все свойства объявлены как published, т. е. доступные из других модулей, но на основе данных классов можно создавать классы-потомки, в которых и объявлять данные свойства. Таким образом, разработчик может создать несколько идентичных классов на основе одного класса ClassName и в каждом из этих классов определять необходимые свойства из набора предопределенных свойств  
  TComponentName Позволяет создавать компоненты-потомки, предками которых являются обычные компоненты или классы VCL Delphi. Таким образом, если перед разработчиком стоит задача расширить возможности какого-либо компонента Delphi, можно использовать данный класс  
       


Обратите внимание на то, что для правильного выбора класса-предка, вам нужно очень хорошо ориентироваться в возможностях уже существующих в Delphi классов.
Создание заготовки компонента
Итак, вы выбрали класс-предок для вашего компонента. Теперь можно приступать к созданию модуля вашего компонента. Создание модуля (заготовки) для нового компонента можно выполнить путем вызова окна Delphi, которое называется экспертом компонентов (Component Expert). Данное окно можно вызвать путем выбора в главном меню Delphi пункта Component/New Component (Компонент/Новый компонент). При этом появляется окно, изображенное на рис. 2.13.


Рис. 2.13. Окно эксперта компонентов
Рассмотрим данное окно подробнее. Итак, поле ввода Ancestor type предназначено для ввода класса-предка для нового компонента. Это поле ввода содержит в выпадающем списке все зарегистрированные классы библиотеки VCL. Предположим, что мы будем создавать компонент, предком которого является кнопка TButton. Для этого выберем в выпадающем списке класс TButton. Поле Class Name предназначено для ввода имени нового класса. Пусть в нашем случае это будет новый класс TMyButton. Заметьте, что по умолчанию Delphi заполняет это поле именем класса-предка с добавлением порядкового номера (в нашем случае TButtoni). Еще одно поле Palette Page показывает, на какой вкладке палитры компонентов будет расположен новый компонент после его регистрации. Оставим в этом поле значение, предлагаемое Delphi по умолчанию samples. Два следующих поля Unit file name и Search path заполняются средой Delphi самостоятельно, но разработчик может их изменить. Мы не будем этого делать в нашем примере. В результате окно эксперта компонентов должно быть заполнено так, как показано на рис. 2.14.


Рис. 2.14. Заполненное окно эксперта компонентов
После заполнения полей данного окна нажимаем кнопку ОК, и Delphi автоматически создаст заготовку модуля вашего компонента. Модуль заготовки для нашего примера представлен на листинге 2.7.

Листинг 2.7
unit MyButton;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls; 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. Мы уже кратко рассматривали процедуру создания нового класса (см. раздел "Объектно-ориентированное программирование" главы 1). Там же мы обсуждали основные типы свойств класса. Обратим теперь взор на основные типы методов компонента.
Все методы могут быть одного из нескольких типов: статические (static), виртуальные (virtual), динамические (dynamic) или методы-сообщения (message). По умолчанию, методу присваивается статический тип.
Статические методы
Статические методы аналогичны обычным функциям или процедурам Delphi. Адрес такого метода известен среде Delphi на стадии компиляции, поэтому Delphi производит статический вызов метода во время выполнения программы. Статические методы работают быстрее всех других методов, но не могут быть перегружены (overload).
Перегрузка метода подразумевает, что класс или компонент может содержать несколько методов с одинаковым именем, но разными списками параметров.
Статический метод может быть описан так:
type
TComponent = class
procedure MyProcedure;
end;
Здесь, метод MyProcedure является статическим. Мы опустили в данном примере название базового класса TObject после слова class. По умолчанию, компилятор создает наследников класса TObject.
Виртуальные методы
Виртуальные методы, в отличие от статических, поддерживают перегрузку, поэтому вызов таких методов для среды Delphi намного сложнее (заранее неизвестен адрес конкретного вызываемого метода). Для того чтобы решить эту проблему, Delphi строит таблицу виртуальных методов (Virtual Method Table), благодаря которой компилятор может определить адрес метода во время выполнения программы. Эта таблица содержит виртуальные методы не только самого класса или компонента, но и его предков. Естественно, хранение такой таблицы увеличивает расходы памяти, но вызов виртуальных методов выполняется быстрее, чем вызов динамических методов.
Описание виртуального метода выглядит следующим образом:
type
TComponent = class
procedure MyProcedure; virtual;
end;
В данном случае метод MyProcedure - виртуальный.
Динамические методы
Динамические методы похожи на виртуальные, они также могут быть перегружены. Основная разница между виртуальными и динамическими методами - в способе их вызова. Если для виртуальных методов строится таблица виртуальных методов, то каждому динамическому методу присваивается уникальное число-идентификатор, после чего строится таблица динамических методов (Dynamic Method Table), в которую заносится данное число, а также адрес метода. Еще одно отличие динамических методов от виртуальных заключается в том, что таблицы динамических методов содержат методы только одного компонента или класса (не включая его предков). Поэтому существенно экономится память, но замедляется время работы, т. к. для поиска адреса метода обычно приходится просматривать несколько таблиц динамических методов.
Описание динамического метода может выглядеть так:
type
TComponent = class
procedure MyProcedure; dynamic;
end;
Методы-сообщения
Методы-сообщения не вызываются из программы непосредственно, как другие методы. Этот тип методов предназначен для того, чтобы выполнить какие-либо действия в ответ на сообщение Windows. В качестве примера рассмотрим описание метода-сообщения:
type
TComponent = class
procedure MyProcedure(Var A: TMessage); message wm_MessageWindows;
end;
После служебного слова message ставится значение (в нашем случае wmjfessagewindows), определяющее сообщение Windows, в ответ на которое будет вызванан методMyProcedure.
Виртуальные и динамические методы могут быть замещенными (overriden) или абстрактными (abstract).
Замещенные методы
Замещение методов предполагает передачу и изменение методов от компонента (класса) предка компоненту (классу) наследнику. Как мы уже отметили, только виртуальные или динамические методы могут быть замещенными. Рассмотрим пример:
type
TComponentChild = class (TComponentParent)
procedure MyProcVirtual; override;
procedure MyProcDynamic; override;
end;
Применение служебного слова override после названия метода позволяет заместить оригинал метода компонента предка методом компонента наследника. При этом замещение происходит непосредственно в таблице виртуальных методов (или таблице динамических методов). При использовании
Служебных словvirtual или dynamic вместоoverride, произойдет просто создание нового метода вместо замещения старого.
Замещение методов не работает со статическими методами - при замещении статического метода новым произойдет простая замена метода родителя в потомке.
Абстрактные методы
Абстрактными могут быть виртуальные или динамические методы. Абстрактными методами называются такие методы, которые описаны внутри определения класса или компонента, но не содержат никаких действий и никогда не вызываются. Такие методы используются только в компонентах или классах-предках. Описание абстрактного метода выглядит так:
procedure MyProcedure; virtual; abstract;

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

В данной главе мы дополним модуль-заготовку нового компонента всем необходимым: свойствами, событиями и методами. Создадим работоспособный компонент и зарегистрируем его в среде Delphi. Затем мы рассмотрим, как можно создать справочную систему по своему компоненту, если вы хотите создавать компоненты для распространения их среди других программистов. Ну и, конечно, мы рассмотрим, как можно модифицировать уже существующие компоненты визуальной библиотеки компонентов Delphi.
Создание свойств компонента
Добавление новых свойств в компонент осуществляется очень просто. Достаточно задать поля и свойства, определив при этом их тип и доступ (чтение, запись). Пример простого задания свойств в новом компоненте представлен на листинге 2.8.

Листинг 2.8
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), а названия компонентов и любых объектов с буквы т (тип, type).

Создание перечисляемых свойств компонента
К свойствам перечисляемого типа относятся такие свойства компонента, которые при их редактировании в окне инспектора объектов вызывают выпадающий список, содержащий возможные значения данного свойства. К числу подобных свойств относятся Align, Borderstyle, color и др. Для того чтобы самостоятельно добавить в новый компонент перечисляемое свойство, необходимо сначала определить новый перечисляемый тип, например:
TMyEnumType = (eFirst, eSecond, eThird);
После этого нужно определить поле компонента, которое будет хранить значение данного перечисляемого типа, затем определить свойство. Пример добавления перечисляемого типа в новый компонент TMyButton приведен на листинге 2.9.

Листинг 2.9
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;

Таким образом, в окне инспектора объектов, при изменении свойства MyEnumProp, будет выдан выпадающий СПИСОК, Содержащий Три пункта: eFirst, eSecond И eThird (рис. 2.15).


Рис. 2.15. Перечисляемое свойство MyEnumProp в новом компоненте TMyButton
Создание свойств-множеств в компоненте
Тип множества часто фигурировал в Object Pascal, и некоторые свойства компонентов Delphi имеют данный тип. Когда вы используете свойство типа set, вы должны учитывать, что каждый элемент множества будет являться отдельным свойством, имеющим логический тип в инспекторе объектов.
Для создания свойства-множества сначала зададим нужный тип:
TMySetTypeFirst = (poFirst, poSecond, poThird); TMySetType = set of TMySetTypeFirst;
Первая строка задает перечисляемый тип TMySetTypeFirst, который определяет диапазон множества, а вторая строка - само множество TMySetType.
Пример добавления свойства-множества в компонент TMyButton приведен на листинге 2.10.

Листинг 2.10.
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;

Для удобства, мы не стали исключать определение перечисляемого свойства в компоненте TMyButton.
В результате, в окне инспектора объектов свойство-множество будет отображаться, как представлено на рис. 2.16.


Рис. 2.16. Свойство-множество MyOptions в новом компоненте TMyButton
Создание свойства-объекта в компоненте
Каждый компонент может содержать в себе свойство-объект. В качестве свойства-объекта может выступать любой компонент или объект Delphi. Кроме того, свойствами-объектами нового компонента могут быть новые компоненты или объекты, которые вы создали самостоятельно. Важным условием является тот факт, что свойства-объекты должны быть потомками класса TPersistent. Это необходимо для того, чтобы свойства объекта-свойства отображались в окне инспектора объектов. Приведем пример создания свойства-объекта в нашем компоненте TMyButton.
Для начала создадим произвольный новый объект, являющийся прямым потомком класса TPersistent (листинг 2.11).

Листинг 2.11
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, в котором присутствует два простых свойства - Propertyl и Property2. Кроме того, в новый объект включена процедура Assign. Данная процедура необходима для обеспечения правильного доступа к свойству нашего будущего компонента TMyButton. Ниже приведен листинг 2.12, в котором мы добавляем в компонент TMyButton новое свойство-объект.

Листинг 2.12
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. Полную версию кода представляет листинг 2.13. Здесь, кроме ранее описанного, приводится код ко всем этим методам.

Листинг 2.13
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;

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 TMyButton.SetMyObject (Value: TMyObject);
begin
if Assigned (Value) then FMyObject.Assign (Value);
end;

constructor TMyButton.Create (AOwner; TComponent);
begin
inherited Create (AOwner);
FMyOb j ect:=TMyObject.Create;
end;

destructor TMybutton.Destroy;
begin
FMyObject.Free;
inherited Destroy;
end;

procedure TMyObject.Assign (Source: TPersistent);
begin
if Source is TMyObject then
begin
FPropertyl:=TmyObject (Source).Property1;
FProperty2:=TmyObject (Source).Property2;
inherited Assign (Source);
end;
end;

Обратите внимание на реализацию метода TMyObject.Assign. Здесь сначала выполняется проверка на то, что передается правильный экземпляр объекта TMyObject. Если он правильный, то значения свойств (Source) Переносятся в поляFPropertyl и FProperty2 объекта TMyButton. Результатом исполнения вышеприведенного кода будет новый компонент TMyButton, содержащий в себе свойство-объект TMyObject. В окне инспектора объектов это будет выглядеть, как представлено на рис. 2.17.


Рис. 2.17. Свойство-объект MyObject в новом компоненте TMyButton
Создание свойства-массива в компоненте
Свойства компонента могут быть практически любого типа, которые поддерживает язык Object Pascal. Некоторые свойства могут быть массивами. Яркими примерами свойств такого типа являются тмето. Lines, TDBGrid. columns и др. Подобные свойства требуют собственных редакторов. Мы пока остановимся на создании простого свойства, которое представляет из себя массив (о создании собственных редакторов свойств читайте далее в этой главе). Создадим новый компонент Tweek, содержащий два свойства: Month и Number. Свойство Month будет представлять собой массив, возвращающий название месяца по переданному целому числу от 1 до 12. Свойство Number - тоже массив, который возвращает число, соответствующее названию месяца.
Итак, на листинге 2.14 приведен код компонента TWeek.

Листинг 2.14
unit Week; interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;
type
TWeek = class(TComponent)
private
{ Private declarations }
function GetMonthName (const AIndex: Integer): String;
function GetMonthNumber (const AMonthNarae: 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;
begin
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:=1;
end;
end;
procedure Register;
begin
RegisterComponents('Samples', [TWeek]);
end;
end.

Рассмотрим вышеприведенный код более подробно. Как вы можете видеть, свойства типа Array объявляются вместе с индексами. Для свойства MontnName мы определили индекс Aindex, а для свойства MonthNumber - индекс AMonthName. Для доступа к свойствам такого типа необходимо использовать методы. Внутренних полей здесь нет. Если свойство типа Array многомерно, то свойство-массив объявляется сразу с несколькими индексами. При вызове методов доступа к ним нужно передавать параметры в том же порядке, в каком вы их указали в свойстве.
Функция GetMonthName возвращает строку, содержащую имя месяца, соответствующего целому числу от 1 до 12, переданному в качестве параметра данной функции. В случае передачи функции числа, не принадлежащему данному диапазону, будет сгенерировано исключение командой raise (об исключениях читайте главу 2).
Наконец, функция GetMonthNumber возвращает число от 1 до 12, которое соответствует названию месяца, переданного в качестве параметра в данную функцию. В случае если ни один месяц не соответствует названию, определенному массивом MonthNames, результатом выполнения данной функции будет ноль.
Таким образом, если поместить на форму экземпляр компонента TWeek с именем weeki, при выполнении строки ShowMessage (Weekl.MonthName[5]}; будет выдано окно с сообщением Май.
Создание собственных редакторов свойств
Перед тем как создавать собственный редактор свойств компонента, рассмотрим сначала имеющиеся в Delphi редакторы свойств. Их достаточно легко видеть в окне инспектора объектов, когда вы пытаетесь изменить какое-либо свойство компонента. Например, когда вы изменяете свойство Color для того или иного компонента, вы видите редактор свойства цвета. Важно заметить, что все свойства компонентов имеют свои редакторы, даже такие простейшие свойства, как Caption, Height, Enabled. Особенностью этих редакторов является то, что компоненты "не знают", какие редакторы вы используете для изменения их свойств. Это может навести на мысль взять собственный редактор свойств вместо предопределенного. Например, можно написать редактор свойства width, который будет ограничен каким-либо числом.
Редакторы свойств имеют свою иерархию. Рассмотрим ее.
Базовым классом в иерархии редакторов свойств является TPropertyEditor. Названия классов говорят сами за себя. Например, класс TColorProperty отвечает за свойство цвета компонента, класс TlntegerProperty связан с теми свойствами, которые имеют тип integer и т. д. На листинге 2.15 приведен код, определяющий базовый класс TPropertyEditor.

Листинг 2.15
TPropertyEditor = class
private
FDesigner: TFormDesigner;
FPropList: PInstPropList;
FPropCount: Integer;
constructor Create(ADesigner: TFormDesigner; APropCount: Integer);
function GetPrivateDirectory: string;
procedure SetPropEntry(Index: Integer; AInstance: TComponent;
APropInfo: PPropInfo};
protected
function GetPropInfо: PPropInfo;
function GetFloatValue: Extended;
function GetFloatValueAt(Index: Integer): Extended;
function GetMethodValue: TMethod;
function GetMethodValueAt(Index: Integer): TMethod;
function GetOrdValue: Longint;
function GetOrdValueAt(Index: Integer): Longint;
function GetStrValue: string;
function GetStrValueAt(Index: Integer): string;
procedure Modified;
procedure SetFloatValue(Value: Extended);
procedure SetMethodValue(const Value: TMethod);
procedure SetOrdValue(Value: Longint);
procedure SetStrValue(const Value: string);
public
destructor Destroy; override;
procedure Activate; virtual;
function AllEqual: Boolean; virtual;
procedure Edit; virtual;
function GetAttributes: TPropertyAttributes; virtual;
function GetComponent(Index: Integer): TComponent;
function GetEditLimit: Integer; virtual;
function GetName: string; virtual;
procedure GetProperties(Proc: TGetPropEditProc);virtual;
function GetPropType: PTypelnfo;
function GetValue: string; virtual;
procedure GetValues(Proc: TGetStrProc); virtual;
procedure Initialize; virtual;
procedure SetValue(const Value: string); virtual;
property Designer: TFormDesigner 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 вызывается при создании (инициализации) редактора свойств.
Метод setvalue применяется для установки значения свойства.
В большинстве случаев, при создании нового редактора свойств нет необходимости использовать в качестве класса-предка базовый класс TPropertyEditor. Часто разработчик просто переделывает уже существующий для данного свойства редактор, переопределяя некоторые его методы.
Рассмотрим в качестве примера переработанное свойство Hint, которое применяется для показа всплывающей подсказки при задержании указателя мыши над компонентом. В стандартном случае такая подсказка имеет всего одну строку. Попробуем сделать свойство Hint многострочным. Нижеприведенный листинг 2.16 показывает, как создать новый редактор свойств THintProperty. В качестве класса-предка для данного редактора свойств выберем редактор TStringProperty.

Листинг 2.16
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(resultt0]) 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 модуля написать саму процедуру регистрации (листинг 2.17).

Листинг 2.17.
procedure Register;
begin
RegisterPropertyEditor (Typelnfо(String), TControl, 'Hint', THintProperty);
end;

Данная процедура позволяет привязать один и тот же редактор к свойствам, в зависимости от их названия или типа. Это определяется параметрами, которые передаются процедуре RegisterPropertyEditor. Первый параметр определяет тип свойства (в нашем примере - это string), второй параметр - класс компонента. Третий параметр позволяет задать имя свойства. Четвертый параметр указывает имя редактора свойства.
Для того чтобы установить новый редактор свойств в Delphi, нужно выполнить следующие шаги:
1. Выбрать пункт меню Options/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;
Данный код не присваивает значение о свойству MyCount. При выполнении вышеприведенного кода команда Default о означает следующее: если при сохранении формы, содержащей компонент, значение свойства MyCount не будет равно нулю, то новое значение сохранится в файле формы, иначе значение данного свойства не будет сохранено.

Примечание
Рекомендуется использовать команду Default во всех случаях, когда это возможно, если вы хотите создать быстро работающее приложение.

Команда NoDefault предназначена для нейтрализации команды Default. Команда применяется для отмены команды Default компонентов-предков. Пример использования команды NoDefault:
TSecondComponent = class (TMyButton)
published
property MyCount NoDefault 0;
Создание событий компонента
Стандартные события мы с вами уже рассматривали в предыдущих главах. В настоящий момент нам предстоит дать четкое определение событию, а также обработчику события.
Итак, событие - это любое действие, произошедшее благодаря операционной системе, действиям пользователя, работе программы.
Событие можно "перехватить" и обработать с помощью программы-обработчика события. Связь между событием и программой-обработчиком называется свойством-событием. Таким образом, когда происходит какое-либо событие компонента, он может обработать данное событие. Для этого сначала происходит проверка наличия кода обработки события. Если подобный код есть - он выполняется.
Рассмотрим в качестве примера такое часто возникающее событие, как нажатие левой кнопки мыши Onclick. Данное событие, как и многие другие, имеет так называемые методы диспетчеризации событий (event-dispatching methods). Эти методы нужны как раз для того, чтобы определять, создан ли код обработки произошедшего события для данного компонента. Эти методы объявляются как защищенные (protected). Таким образом, для свойства Onciick определен метод диспетчеризации события click (листинг 2.18).

Листинг 2.18
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;
Листинг 2.19
procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
Canvas.TextOut(X, Y, '('+IntToStr(X)+', '+IntToStr(Y)+')');
end;


Рис. 2.18. Результат обработки события OnMouseDown
Пример создания нового события компонента
Попробуем теперь создать собственное событие. Для этого нужно сначала убедиться, что такого события нет в VCL Delphi. Предположим, возникла необходимость создания события, которое возникает каждые 30 секунд. Естественно, для этого случая можно воспользоваться компонентом Timer, который расположен на вкладке System палитры компонентов Delphi. Но, предположим, что наш компонент должен иметь такое событие для удобства работы с ним. Код для создания события представлен на листинге 2.20.

Листинг 2.20
unit halfmin; interface
uses
Windows, Messages, SysUtils, Classes,Graphics, Controls, Forms,
Dialogs,
ExtCtrls;
type
TTimeEvent = procedure (Sender: TObj'ect; 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.

Для проверки работоспособности вышеприведенного кода вы можете добавить еще одну процедуру для регистрации нового компонента с именем ThalfMinute, предварительно добавив в interface-часть программы строку:
procedure Register;
Ниже представлен код для регистрации компонента:
procedure Register;
begin
RegisterComponents('Samples', [THalfMinute]);
end;
Для просмотра работоспособности нового компонента после его регистрации создадим новую форму и разместим на ней новый компонент. Добавим на форму компонент TEdit. Затем добавим обработчик события onHaifMinute для формы (листинг 2.21).

Листинг 2.21
procedure TForml.HalfMinutelHalfMinute(Sender: TObject; TheTime: TDateTime);
begin
Editl.Text: = ('Время '+TimeToStr(TheTime)); Editl. Refresh;
end;

В результате работы данной программы в компоненте Editl будет выводиться текущее время каждые 30 секунд.
Создание методов компонента
Добавление методов в новый компонент - операция несложная. Однако нужно обратить внимание на некоторые особенности, которые в дальнейшем облегчат взаимодействие пользователя с вашим компонентом.
Во-первых, необходимо, чтобы методы не были взаимозависимыми, т. е. каждый метод должен быть самостоятельным и законченным. Во-вторых, метод не должен блокировать компонент. И, в-третьих, метод должен иметь имя, соответствующее выполняемым действиям.
Как вы уже знаете, методы объявляются в секциях private, public и protected. При создании нового метода важно учитывать, как он будет использоваться в дальнейшем, сможет ли данный компонент быть предком для других компонентов, и, в зависимости от этого, разместить методы в нужных секциях. Табл. 2.10 поможет вам при выборе секций для методов компонента.
Таблица 2.10. Размещение методов компонента в различных секциях



Поделиться:


Последнее изменение этой страницы: 2017-02-21; просмотров: 858; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 18.190.219.65 (0.07 с.)