    Создание компонентов 

    Дэвид Пауэл 

    Компоненты представляют собой строительные блоки большинства приложений 
Delphi. Возможность щелкнуть на компоненте и поместить его в форму не является 
чем-;; то уникальным для сред визуальной разработки. Но лишь некоторые языки 
предоставляют средства для создания пользовательских компонентов в пределах 
одной среды и одного языка. Delphi как раз и принадлежит к этому 
малочисленному кругу избранных. С точки зрения пользователя, компонент 
является "черным ящиком". Пользователей не заботит, как компонент выполняет 
свои задачи, главное — чтобы он делал то, чего от него ожидают. С точки зрения 
создателя компонента, компонент — это экземпляр класса. Вы можете поймать себя 
на желании использовать термины компонент и класс как взаимозаменяемые, но это 
некорректно в общем случае. Компонент— это некий объект, который можно 
опустить на форму и управлять им с помощью средств IDE Delphi. И хотя класс 
представляет собой структуру объекта, которая существует как часть языка 
Object Pascal, все компоненты являются классами, но не все классы являются компонентами. 
    Палитра компонентов Delphi содержит более 130 компонентов, которые 
используются в широком диапазоне приложений. Но на определенном этапе своей 
работы вы попадете в ситуацию, когда не окажется компонента, который бы мог 
делать то, что вы хотите, либо есть компонент, поведение которого почти 
совпадает с вашими представлениями, но все-таки он нуждается в некоторой 
модификации, чтобы полностью соответствовать вашим потребностям. Именно тогда 
вы и поймете, что настало время создать свой собственный компонент. 
    Чтобы создать компонент, выполните следующие действия. 
    1.	Выберите класс предка. 
    2.	Создайте структуру компонента. 
    3.	Выполните тестирование компонента. 
    4.	Создайте пиктограмму для этого компонента. 
    5.	Добавьте созданный компонент в палитру компонентов. 

    ГЛАВА 16 

    Что собой представляют пользовательские компоненты 

    Здесь рассматривается процесс написания компонентов. 

    Когда родителей выбирают 

    В зависимости от своих потребностей вы можете наследовать компонент из 
существующего элемента управления или из базового класса компонентов Delphi. 
    Структура компонентов 
    Вы узнаете, как добавить свойства, методы и события. 

    Последние штрихи 

    Отладка и инсталляция компонентов, а также создание пиктограмм. 

358

    Исходный код библиотеки визуальных компонентов Delphi (Visual Component 
Library, или VCL) является бесценным ресурсом, в который часто заглядывают 
начинающие создатели компонентов. Если вам когда-либо захочется узнать, как 
компоненту удается делать то, что он делает, откройте исходный файл и 
удовлетворите свое любопытство. Если у вас нет исходного кода VCL, получите 
его в фирме Borland. 

    Правильный выбор предка 

    При создании компонента самым важным (и одновременно первым) шагом 
является выбор правильного класса компонента, от которого вы наследуете свой 
собственный компонент. Delphi позволяет наследовать компонент из любого 
класса, который ей известен (это классы, которые уже определены в системной 
библиотеке или были добавлены в палитру компонентов). 
    При выборе класса предка вы можете пойти любым из двух путей. Если вы 
создаете совершенно новый компонент, вам придется наследовать его из базового 
класса компонента. А если вам нужно изменить функции существующего компонента, 
наследуйте его из существующего класса компонента. 

    Базовые классы компонентов 

    Delphi предоставляет пять стандартных базовых классов, от которых можно 
наследовать новый компонент. В табл. 16.1 приведены эти пять классов и 
описано, что они должны обеспечить для своего компонента. 

    Таблица 16.1. Базовые классы компонентов Delphi 

    Базовый класс VCL 

    Описание 
    TObject	He является компонентом. Это базовый класс для всех классов 
Delphi. Его можно использовать в качестве базового класса для компонента, с 
которым не требуется взаимодействия в процессе разработки. 
Компоненты этого типа^н^модт^ер^дамтьП?т^кд^.д>еде IDjE 
    TComponent	Прекрасная стартовая площадка для невизуальных компонентов. 
Компоненты этого типа могут передавать потоки в среде IDE                      
    TGraphicControl 
    Содержит метод Paint ()   и рисует в клиентской области своего родителя. 
Не имеет дескриптора окна, поэтому ие^может^юл^чуг^^^^	___             __      
    TWinControl 
    Базовый класс для всех "оконных" компонентов. Содержит дескриптор_окна и 
может получить ___	фокус        _____________    _______   ____~~_____       ___ 
    TCustomControl      Сочетание классов TGraphicControl и TWinControl. 
Работает с дескриптором окна и содержит метод Paint (). Большинство 
визуальных^юльзовательских компонентов наследуется от этого класса	~", 

    Класс TComponent вводит следующие свойства: Owner, Name, Tag, 
ComponentState и Components. Свойство Components представляет собой массив, 
содержащий список всех компонентов, которыми владеет этот компонент. 
Стандартный деструктор для класса TComponent просматривает этот список и 
освобождает все принадлежащие ему компоненты. Классу TComponent также 
известно, кдк-Налг^эДГЬ^самого себя (т.е. сохранить значения своих свойств) в 
DFM-файл, когда проект закрывается или сохраняется.	~~ 
    Класс TControl не приведен в табл. 16.1, поскольку обычно он не наследуется. 
    Классы TGraphicControl и TWinControl произошли непосредственно от класса 
TControl, устанавливающего в своем защищенном разделе множество свойств, 
которые в классах-потомках стали открытыми или реализованными. Вот неполный 
список представленных свойств: Left, Top, Width, Height, Align, Visible, 
Enabled, Cursor и On-Click. Полный перечень свойств можно получить из 
исходного кода VCL или воспользовавшись броузером объектов (Object Browser). 
    Класс TGraphicControl содержит виртуальный метод Paint (), а также 
свойство Canvas. Он используется в основном для компонентов, которым нужно 
отобразить информацию или графику, но не нужны ресурсы, связанные с 
дескриптором окна. 
    Класс TWinControl вводит свойства, которые необходимы для оконных 
элементов управления. К ним относятся: Handle, TabOrder, TabStop, CtlSD, 
OnEnter и др. Полный перечень свойств можно получить из исходного кода VCL или 
воспользовавшись броузером объектов. 

    Глава 16. Создание компонентов 359 

    Класс TCustomControl сочетает возможности рисования класса TGraphicControl 
с аспектами оконных элементов управления класса TWinControl. Это класс, из 
которого в качестве примера мы будем наследовать пользовательский компонент. 

    Настройка существующих компонентов 

    Если Delphi предоставляет компонент, который почти отвечает вашим 
требованиям, вы можете остановить свой выбор на нем, чтобы наследовать от него 
компонент своей мечты, а не начинать создание с нуля. Наследовать компонент от 
существующего компонента можно двумя способами: либо непосредственно из 
существующего компонента, либо от прямого предка существующего компонента. Вы 
могли бы наследовать свой компонент, например, от класса TEdit или 
TCustomEdit. Все функции класса TEdit реализованы в классе TCustomEdit, а 
класс TEdit просто публикует многие свойства, объявленные в его предке. Не 
существует жесткого правила насчет того, какие классы следует выбирать для 
наследования. Неплохо бы придерживаться правила, которое советует наследовать 
свой компонент от потомка, если вы хотите добавить функции, или от прямого 
предка, если нужно убрать некоторые функции. В любом случае вам поможет 
исходный код VCL. 

    Использование шаблонов компонентов 

    Если вы хотите создать новый элемент управления, чтобы просто изменить 
стандартный параметр существующего элемента управления (например, размер, 
цвет, шрифт и т.д.) или добавить стандартный обработчик событий, рассмотрите 
возможность использования шаблонов компонентов (Component Templates'), а не 
создания нового компонента. Шаблоны компонентов создаются путем выбора одного 
или нескольких компонентов формы с последующим выполнением команды 
Component^Create Component Template главного меню Delphi. При этом компоненты 
и весь связанный с ними код (через обработчики событий) сохранятся в виде 
составной части палитры компонентов. Код шаблонов компонентов хранится в 
двоичном файле DELPHI32. ОСТ, который расположен в каталоге De],phi3\BIN. 
Когда вы выберете шаблон компонента и поместите его в форму, Delphi вставит в 
нее сохраненную информацию шаблона и разрешит любые конфликты, связанные с 
именами, которые могут встретиться в существующем коде и компонентах. 

    Создание структуры компонента 

    Компонент состоит из модуля, содержащего определение класса, которое 
описывает характеристики вашего нового компонента. Простейший способ создания 
этого стартового модуля — использовать эксперта компонентов (Component 
Expert), показанного на рис. 16.1. Эксперт компонентов можно активизировать, 
выбрав из главного меню Delphi команду File^New, в результате чего откроется 
диалоговое окно хранилища объектов (Object Repository), а затем выбрав вкладку 
Component. Того же результата можно достичь при выборе из главного меню 
команды Component^New Component. 

    Рис. 16.1. Эксперт компонентов Delphi 

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

    360 Часть IV. Разработка компонентов 

    После того как эксперт компонентов создаст базовую структуру компонента, 
ваш компонент получит в наследство от своего класса-предка все его функции. Но 
ведь вы не собирались ограничить свое детище лишь наследованием функций, не 
так ли? Теперь настало время засучить рукава и добавить несколько новых 
свойств, методов и событий, чтобы превратить эту структуру в компонент, 
который вы создали в своем воображении. 
    Создадим пользовательский компонент TEverything, который будет наследован 
от класса TCustomControl. Разместим его на странице палитры Using Delphi 
библиотеки компонентов (Component Library). Если вы введете в эксперт 
компонентов данные, показанные на рис. 16.1, он создаст код, содержащийся в 
листинге 16.1. Этот файл можно найти на дополнительно распространяемой дискете. 

    Листинг 16.1. \UDELPHI3\CHP16\LIST16-1. Базовая структура компонента для 

TEverything	j unit  EvryThng; 
    interface 
    uses 
    Windows,   Messages,    SysUtils,    Classes,    Graphics,    Controls,    
Forms,    Dialogs,
    type 
    TEverything = class(TCustomControl) private 
    { Скрытые объявления. } protected 
    { Защищенные объявления. } public 
    { Открытые объявления. } published 
    { Публикуемые объявления. } end; 
    procedure Register; implementation 
    procedure Register; begin 
    RegisterComponents('UD3', [TEverything]); end; 
    end. 


    Создание свойств 

    Свойства являются интерфейсом с внутренними полями данных вашего 
компонента. Они используются для чтения и записи значений во внутренние поля 
данных и из них и не предоставляют прямого доступа к ним со стороны 
пользователя компонента. Чтобы скрыть информацию, большинство внутренних полей 
данных объявляется в разделе private. 
    Внутренние поля данных обычно объявляются в разделе private компонента и 
начинаются с буквы F. Например, свойство Color может получить доступ к 
внутреннему полю данных с именем FColor. 
    I Совет 1 

    Типы свойств 

    Свойства могут создаваться с использованием нескольких типов данных, а 
именно: Simple, Enumerated, Set, Ob j ect и Array. Разные типы свойств 
по-разному отображаются в окне инспектора объектов и имеют уникальные 
редакторы свойств. В полном объеме редакторы свойств рассматриваются в 
следующей главе, а пока представьте себе редактор свойств в виде некоторого 
механизма, который позволяет пользователю изменять значения свойства в окне 
Object Inspector. 
    Вы можете повысить степень доступности существующих свойств в компоненте, 
повторно объявив их в разделах компонента public или published. 

    Глава 16. Создание компонентов	361 


    Создание простых свойств 

    Простые свойства (Simple) включают числовые, символьные и строковые типы 
данных. Они отображаются в окне инспектора объектов и могут изменяться без 
использования специального редактора свойств. Компоненты Delphi довольно часто 
используют {^очислённыё (integer) и строковые (String) свойства. Такие 
свойства, как Left, Top, Width и Height, имеют тип 

    Integer, a Caption, Text и Name являются свойствами строкового типа. 

    Назначение листинга 16.2 состоит в добавлении к нашему компоненту трех 
свойств (по одному для каждого из простых типов) и размещении их в разделе 
published, поэтому они отображаются в окне инспектора объектов. Файл листинга 
можно найти на допол!1ите11ъТю^а^гТроТтраняё1й(Ж11искете". 

    Листинг 16.2. \UDELPHi3\CHPl6\LiSTl6-2. Компонент TEverything со 
свойствами простого типа 

    TEverything =  class(TCustomControl] private 
    {   Закрытые  объявления.   } 
    FNumber     ^/'integer; 
    FChar	:! char;^> 
    FString     :\ string;"*  , protected 
    {   Защищенные, объявления, public 
    {   Открытые  объявления.   } published 
    {   Публикуемые  объявления 
    property NumberProp:^integer  read FNumber write  FNumber; 
    property  CharProp:   char   read  FChar  write   FChar; 
    property  StringProp:   string   read  FString  write   FString; end; 

    Свойства NumberProp, CharProp и StringProp теперь отображаются в окне 
инспектора объектов с действующими по умолчанию значениями 0, #0 и пустой 
строкой соответственно. Поскольку в программном коде не были заданы значения, 
действующие по умолчанию, все они принимают значения с "нулевой" природой. Для 
целого типа это нуль, для символьного —- null или_#0, а для строкового — 
пустая строка. О присвоении свойствам действующих по умолчанию значений речь 
пойдет в следующем разделе. Свойства получают доступ к внутренним полям данных 
для каждого из своих типов данных. Если бы этот компонент в данный момент был 
добавлен в палитру компонентов, то в окне инспектора объектов он выглядел бы 
так, как на рис. 16.2. 

    Рис. 16.2. Компонент TEverything с наследованными и добавленными простыми свойствами 


    Создание перечислимых свойств 

    Перечислимые свойства (Enumerated) включают предопределенные перечислимые 
типы, логические (Boolean) типы и перечислимые типы, определенные 
пользователем. Перечислимые свойства представлены в окне инспектора объектов в 
виде раскрывающегося списка. Пользователь может циклически проходить по всем 
значениям набора, дважды щелкая на значении, отображаемом инспектором 
объектов, либо после щелчка на направленной вниз стрелке может выбрать нужное 
значение прямо из списка. Delphi использует перечислимые свойства для таких 
свойств, как Color, Вог-derStyle, FormStyle и Cursor. 
    По соглашению большинство перечислимых наборов Delphi предваряют имена 
своих элементов двухбуквенным префиксом, который показывает, какому 
перечислимому типу принадлежат эти элементы. В табл. 16.2 приведены упомянутые 
четыре свойства перечислимого типа вместе с некоторыми из их возможных значений. 

    362 Часть IV. Разработка компонентов 


    Таблица 16.2. Свойства перечислимого типа Delphi 

    Перечислимый тип 

    BorderStyle 

    Значение 
    Color	clBlack, clYellow, clMenu, clWindow и clBtnFace. Полный список 
приведен в __________________файле GRAPHICS.PAS__________________________________ 
    Cursor	crDef ault, crNone, crArrow, crNo, crAppStart и crHelp. Полный 
список приведен в файле CONROLS.PAS 
    bsNone, bsSingle, bsSizeable, bsDialog, bsToolWindow и bsSizeToolWin 

    FormStyle 
    fsNormal, fsMDIChild, f sMDIForm, fsStayOnTop 

    Прежде чем добавить несколько перечислимых свойств к компоненту 
TEverything, необходимо объявить свой собственный новый перечислимый тип. В 
данном разделе будет определен перечислимый тип TEnumProp, который состоит из 
шести элементов. Это выполняется следующим образом: 

    TEnumProp =   (epZero,   epOne,   epTwo,   epThree,   epFour,   epFive); 

    Теперь добавим три перечислимых свойства к компоненту TEverything, причем 
первое имеет дзедопре-деленный тип, второе — логический (Boolean), а третье — 
тип, определяемый пользователем. Назовем их ColorProp, BooleanProp и EnumProp 
соответственно. Компонент TEverything будет выглядеть так, как в листинге 
16.3. Файл листинга находится на дополнительн о распространяемой дискете. 

    Листинг 16.3. \UDELPHl3\CHP16\LIST16-3.  Компонент TEverything со 
свойствами перечислимого типа 

    TEnumProp =   (epZero,   ербпе,   epTwo,   epThree,   epFour,   epFive); 
    TEverything  =  class(TCustomControl) private 
    (   Скрытые   объявления.    } 
    FColor        :   TColor; 
    FBoolean   :   boolean; 
    FEnumProp:   TEnumProp; protected 
    {   Защищенные   объявления.    } public 
    {   Открытые  объявления.    } published 
    {   Публикуемые  объявления.   } 
    property  ColorProp:   TColor   read JTColor  write   FColor; 
    property BooleanProp:   boolean  read  FBoolean write   FBoolean; 
    property EnumProp:   TEnumProp  read  FEnumProp write  FEnumProp; end; 

    Если бы компонент TEverything в данный момент был добавлен в палитру 
компонентов, то в окне инспектора объектов он выглядел бы так, как на рис. 
16.3. Обратите внимание на то, что в раскрывающемся списке инспектора объектов 
перечислены значения перечислимого типа не в том порядке, в котором они были 
заданы при определении, а по алфавиту. 

    Рис. 16.3. Компонент TEverything с добавленными свойствами перечислимого типа 


    Глава 16. Создание компонентов 363 


    Создание свойств типа "множество" 

    Свойства типа "множество" (Set) включают стилевые множества языка Pascal. 
В окне инспектора объектов их можно узнать по заключенным в квадратные скобки 
элементам и знаку плюс (+), расположенному слева от имени свойства. 
Пользователь "может модифицировать значения", принадлежащие множеству, 
непосредственно редактируя их между квадратными скобками или щелкая на имени 
свойства. Щелчок на имени свойства приводит к его расширению в окне инспектора 
объектов до перечисления всех элементов множества с указанием значения True 
или False, которое сообщает, включено ли данное значение в множество. Delphi 
использует свойства типа Set для таких свойств, как Borderlcons и Options. По 
соглашению имена элементов множества совпадают с именами элементов 
перечислимого типа. 
    Чтобы создать свойство типа множество, необходимо сначала создать 
множество. Для этого можно перечислимый тип, созданный в предыдущем разделе, 
превратить в множество, как показано ниже. 
    TEnumProp  =   (epZero,   epOne,   epTwo,   epThree,   epFour,   epFive) ; 
TSetProp =  set  of TEnumProp; 
    Теперь для компонента TEverything вы можете создать свое собственное 
свойство типа Set с именем SetProp. Компонент TEverything будет теперь 
выглядеть так, как в листинге 16.4. 

    Листинг 16.4. \UDELPHi3\CHPl6\LiSTi6-4. Компонент TEverything со 
свойствами типа "множество' 

    TEnumProp  =   (epZero,   epOne,   epTwo,   epThree",   epFour,   epFivej ;" 
    TSetProp = set  of TEnumProp; 
    TEverything  =  class(TCustomControl) private 
    {   Скрытые  объявления.   } 
    FSetProp   :   TSetProp; protected 
    {   Защищенные объявления.   } public 
    {   Открытые  объявления.   } 
    constructor  Create(AOwner:   TComponent);   override; 
    destructor   Free; published 
    {   Публикуемые  объявления.   } 
    property SetProp:   TSetProp read  FSetProp write  FSetProp; end; 

    Если бы компонент TEverything был добавлен в палитру компонентов, то в 
окне инспектора объектов он выглядел бы так, как на рис. 16.4. 
    Размер публикуемых (published) свойств типа "множество" ограничен 32 
элементами. Если вы попытаетесь опубликовать множество с большим количеством 
элементов, получите сообщение об ошибке, а именно — Error (38) : Size of 
published set 'property name' is >4 bytes. Если вам очень нужно множество с 
более чем 32 элементами, создайте свойство, работающее только во время 
выполнения (runtime-only property). 
    I Совет I 

    Рис. 16.4. Компонент TEverything с добавленным свойством типа "множество" 


    364 Часть IV. Разработка компонентов 


    Создание свойств типа "объект" 

    Свойства типа "объект " (Ob j ect) включают отдельные классы и объекты, 
которые_на самом деле являются свойст-вами_других компонентов. Объектные 
свойства в окне инспектора объектов помечаются знаком плюс (+) слева от имени 
свойства или кнопкой с многоточием (...), расположенной в столбце значения 
свойства. При просмотре объектного свойства в окне инспектора объектов Delphi 
отображает список всехлубликуемых свойств, которые принадлежат классу этого 
объектного свойства. Щелчок на кнопке с многоточием приведет_к открытию 
редактора пользовательских свойств для данного типа объектного свойства. Для 
поддегшки. штоков,в окно инспектора объектов и из него свойство объект должно 
быть потомком класса TPersistent. Delphi использует свойства объект для таких 
свойств, как Font, Hor-zScrollBarиVertScrollBar. 
    Чтобы создать свойство типа объект для компонента TEverything, нужно 
сначала создать класс, являющийся либо прямым потомком класса TPersistent, 
либо потомком класса, производного от TPersistent. Для простоты выведем наш 
класс непосредственно из класса TPersistent, как показано в листинге 16.5. 

    Листинг 16.5. \UDELPHi3\CHP16\LlST16-5. Объявление класса TObjectProp 

    TObjectProp =  class(TPersistent) private 
    FMylntl   :   integer; 
    FMyInt2   :   integer; 
    FMylntS   :   integer; piblic 
    property  МуРгорЗ:   integer   read  FMyInt3  write   FMylntS; published 
    property  MyPropl:   integer   read  FMylntl  write   FMylntl; 
    property MyProp2:   integer  read  FMyInt2  write  FMyInt2; end; 

    Класс TObjectProp имеет два публикуемых (объявленных в разделе published) 
целочисленных свойства, которые будут отображены в окне инспектора объектов, и 
одно общедоступное (объявленное в разделе public) целочисленное свойство, 
которое не будет отображено в окне инспектора объектов. Поскольку класс 
TObjectProp является свойством типа объект, внутреннее поле данных, в котором 
хранится значение этого свойства, должно быть реализовано во время создания 
компонента, чтобы сохранить допустимую ссылку на объект. Можно создать 
экземпляр объекта TObjectProp путем переопределения конструктора для 
oacca~TEverything и создания экземпляра класса TObjectProp после того, как 
будет вызван наследованный конструктор. Сначала, как показано ниже, необходимо 
вызвать наследованный метод Create, чтобы класс TEverything был корректно 
инициализирован перед тем, как вы начнете им манипулировать. 

    constructor  TEverything.Create(AOwner:   TComponent); begin 
    inherited Create(AOwner); 
    FObjectProp   := TObjectProp.Create; end; 

    Коль вы создаете класс TObjectProp в своем конструкторе Create, вам 
необходимо найти способ его освобождения при разрушении компонента 
TEverything. Вы можете уничтожить объект, который был реализован в 
конструкторе, путем переопределения деструктора Destroy. Причем освобождение 
вашего объектного свойства должно произойти до вызова наследованного метода 
Destroy, поскольку после его вызова компонент TEverything больше не будет 
допустимым объектом. 

    Destructor  TEverything.Destroy; begin 
    FObjectProp.Free; 
    inherited Destroy; end; 

    В листинге 16.6 демонстрируется объявление типа для компонента TEverything 
с объектным свойством и с переопределенными конструктором и деструктором. 

    Листинг 16.6. \UDELPHi3\CHPl6\LiSTl6-6. Создание свойств типа "объект" 

    class;(TPersistentj 
    TObjectProp =  class(TPe: private 
    FMylntl   :   integer; FMyInt2   :   integer; 


    Глава 16. Создание компонентов 365 


    FMyInt3 : integer/public 
    property МуРгорЗ: integer read FMylntS write FMyInt3; ..published 
    property MyPropl: integer read FMylntl write FMylntl; 
    property MyProp2: integer read FMyInt2 write FMyInt2; end; 
    TEverything = class(TCustomControl) private 
    { Скрытые объявления. } 
    FObjectProp: TObjectProp; protected 
    { Защищенные объявления. } public 
    { Открытые объявления. } 
    constructor Create(AOwner: TComponent); override; destructor Destroy; override; 
    published_ 
    {   Публикуемые объявления.   } 
    property ObjectProp:   TObjectProp  read FObjectProp write  FObjectProp; end; 

    Если бы сейчас компонент TEverything был добавлен в палитру компонентов, 
то в окне инспектора объектов он выглядел бы так, как на рис. 16.5. Обратите 
внимание, что, как и ожидалось, в развернутом свойстве ObjectProp присутствует 
два публикуемых свойства (MyPropl и МуРгор2), а общедоступное свойство МуРгорЗ 
отсутствует. Кроме того, отсутствует и обещанна5пснопка с многоточием в поле 
значения для свойства ObjectProg, поскольку для класса TObj ectProg не 
зарегистрирован редактодсдойсш. 

    Рис. 16.5. Компонент TEverything со свойством типа "объект" 


    Создание свойств типа "массив" 

    Свойства типа "массив" (Array) представляют собой свойства, к которым 
можно получить доступ, как будто они являются обычными массивами языка Object 
Pascal. Свойства массив могут быть массивами любого типа, определенного в 
языке Object Pascal. Тем не менее они отличаются от массивов Object Pascal по 
двум основным аспектам. 
    •   Массивы Object Pascal могут иметь только целочисленное значение 
индекса, в то время как свойства тип массив могут индексироваться и строковым значением. 
    •   Элементы массивов Object Pascal хранятся в памяти последовательно, 
поэтому вы можете легко "прогуляться" по массиву с помощью указателя, изменяя 
позицию в памяти, на которую ссылается указатель. А свойства типа массив могут 
получать доступ только к одному элементу за один раз.	———————— 
    Свойства типа массив в окне инспектора объектов помечаются кнопкой с 
многоточием (...), расположенной в столбце значения свойства. Delphi 
использует этот тип для таких свойств, как Lines, Items и Sections. 
Публикуемые свойства типа массив нельзя непосредственно редактировать в окне 
инспектора объектов. Они требуют применения пользовательского редактора свойств".* 
    Др,льзовательские"редакторы свойств рассмотрены в следующей,славе, а пока 
нам нужно создать динамические (runtime) свойства типа массив для демонстрации 
этого типа свойств. Сначала создадим массив TArrayProp, в котором хранятся 
значения от одного до пяти, причем эти значения имеют строковый тип. 

    TArrayProp = array [1...5] of string[7]; 


    366 Часть IV. Разработка компонентов 

    Затем следует создать внутреннее_пол?;^|дьг??АггауРг^р^<?ГО2?е„имеет тип 
TArrayProp. Проинициализируем строковые значения для этогсГсвоиства в 
конструкторе Create класса TEverything следующими значениями: one, two, three, 
four и five. И, наконец, создадим свойство ArrayPropInt, которое будет иметь 
тип массива с целочисленным значением индекса. Свойство ArrayPropInt получает 
доступ к внутреннему полю данных ГАггауРгорле??з_некоторый 
jwemod_dpcm^n?jHjBOjBrjaii]aeT^jrjoKOBoe значение своего 
целдч.и^слешого^индекса. (Методы доступа рассматриваются"™" 'жё^в разделе 
"Создание процедур Get и Set"'.) Создадим также свойство ArrayPropStr, которое 
будет иметь тип массива со строковым значением индекса. Свойство ArrayPropStr 
также получает доступ к внутреннему полю данных FAr-rayProp через некоторый 
метод доступа и возвращает целое значение своего строкового индекса, если он определен. 

    property ArrayPropInt[Index:   integer]:   string  read GetArrayPropInt; 
property ArrayPropStr[Index:   string]:   integer read GetArrayPropStr; 

    В листинге 16.7 демонстрируется полный код компонента TEverything со 
свойствами типа массив. Заметьте, что свойства ArrayPropInt и ArrayPropStr 
перечислены в общедоступном (public) разделе класса. Вы также должны обратить 
внимание на переопределенный конструктор Create, в котором инициализируются 
строки FArrayProp. Почему функции GetArrayPropInt и GetArrayPropStr записаны в 
таком виде, будет разъяснено ниже, а пока достаточно сказать, что они ведут себя 
так, как описано выше. 

    ; Листинг 16.7. \UDELPHl3\CHP16\LlST16-7. Компонент TEverything со 
свойствами типа "массив" 

    unit EvryThng; 
    interface 
    uses 
    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs; 
    type 
    TArrayProp = array [1..5] of string[7]; 
    TEverything = class(TCustomControl) private 
    { Скрытые объявления. } 
    FArrayProp : TArrayProp; 
    function GetArrayPropInt(plndex: integer): string; 
    function GetArrayPropStr(plndex: string): integer; protected 
    { Защищенные объявления. } public 
    { Открытые объявления. } 
    constructor Create(AOwner: TComponent); override; 
    property ArrayPropInt[Index: integer]: string read GetArrayPropInt; 
    property ArrayPropStr[Index: string]: integer read GetArrayPropStr; published 
    { Публикуемые объявления. } end; 
    procedure Register; 
    implementation 
    constructor TEverything.Create (AOwner: TComponent); begin 
    // Инициализация строковых значений для FArrayProp. 
    FArrayProp[1] := 'one'; 
    FArrayProp[2] := 'two'; 
    FArrayProp[3] := 'three'; 
    FArrayProp[4] := 'four'; 
    FArrayProp[5] := 'five'; end; 
    function TEverything.GetArrayPropInt(plndex: integer): string; begin 
    result := 'unknown'; 


    Глава 16. Создание компонентов	367 


    // Если индекс plndex определен в массиве FArrayProp, // установим 
результат равным строковому значению, if plndex in [1..5] then 
    result := FArrayProp[plndex]; end; 
    function TEverything.GetArrayPropStr(plndex: string): integer; var 
    x : integer; begin 
    result := -1; 
    // Выполнение цикла для массива FArrayProp // и поиск совпадения значений 
для plndex. for x := 1 to 5 do 
    // Использование функции Uppercase, чтобы 
    // не привязываться к регистру символов. 
    if Uppercase(FArrayProp[x]) = Uppercase(plndex) then 
    begin 
    result := x; // Выход из цикла FOR. exit; end; end; 
    procedure Register; begin 
    RegisterComponents('UD3', [TEverything]); end; 
    end. 


    Команды свойств 

    Команды свойств представляют собой команды, которые располагаются в коде 
вместе с объявлением свойства и указывают компилятору на определенное 
обращение со свойством. В этой главе рассмотрены все шесть_команд CBoflcre.Pelphi. 
    •	Default 
    •	NoDefault 
    •	Default для свойств типамассив 
    Я	Stored 
    •	Index 
    в	Dispid 


    Команда Default 

    Когда вы создаете свойства, они первоначально имеют нулевое значение. 
Свойства типа integer имеют значение нуль, свойства string —- пустую строку, 
свойства char — значение, равное #0, свойства типа set — пустое множество и 
т.д. Когда Delphi сохраняет значения свойств для файла формы (с расширением 
DFM), она сравнивает текущее значение свойства с его значением по умолчанию 
(Default). Если они не совпадают, Delphi сохраняет текущее значение, а в 
случае несовпадения — нет, например: 
    property NumberProp:   integer  read  FNumber write  FNumber  default   5; 
property EnumProp:   TEnumProp  read  FEnumProp write  FEnumProp default  
epOne; property SetProp:   TSetProp  read  FSetProp write  FSetProp default   [epOne]; 
    Если значение свойства NumberProp равно 5, Delphi не сохраняет значение 
для NumberProp. 

    ПРЕДОСТЕРЕЖЕНИЕ 
    Команда Default не устанавливает действующее по умолчанию значение для 
свойства в окне инспектора объектов. Она используется только для того, чтобы 
определить, сохранять ли значение, когда свойство направляется в DFM-файл. 
    Если для свойства не указано никакого значения Default, Delphi всегда 
сохраняет текущее значение. 

    368	Часть IV. Разработка компонентов 


    Команда NoDefault 

    Команда NoDefault используется для информирования Delphi о том, что для 
свойства не существует никакого значения, действующего по умолчанию. Обычно 
она применяется для наследованных свойств, которые имели заданное по 
.умолчанию значение. Переобъявление унаследованного свойства с помощью команды 
NoDefault заставляет DelpriT56-ращаться с этим свойством так, как если бы оно 
не имело заданного по умолчанию значения, например 

    property NumberProp NoDefault; 

    Если бы свойство NumberProp было наследованным свойством с действующим по 
умолчанию значением, то сейчас это значение перестало бы существовать. 

    Команда Default для свойств типа "массив" 

    Команду Default можно использовать вместе со свойствами типал/ассыв. В 
этом случае она информирует Delphi о том, что данный массив является для 
компонента действующим по умолчанию свойством типа массив. Если к свойству 
массив ArrayPropInt компонента TEverything добавить команду Default, как в 
строке кода 

    property ArrayPropInt[Index:   integer]:   string  read GetArrayPropInt;   Default; 

    и объявить переменную с именем Everything типа TEverything, то можно 
получить доступ к переменной Everything следующим образом:	J 

    Caption   := Everything[3]; 

    Эта строка устанавливает заголовок равным строке "three". Любой компонент 
может иметь только одно действующее по умолчанию свойство типа массив. 

    Команда Stored 

    Команда Stored используется для того, чтобы сообщить Delphi, сохранять ли 
значение свойства. За командой Stored может стоять ключевое слово true, false 
или функция, которая возвращает логическое значение. По умолчанию Delphi 
сохраняет значения всех публикуемых свойств, например: 
    property NumberPropl:	integer  read  FNuml	write  FNuml	stored true; 
property NumberProp2:	integer  read  FNum2	write  FNum2	stored  false/property 
NumberProp3:	integer   read  FNum3	write   FNum3	stored  Fund; 
    В приведенных примерах значение свойства NumberPropl сохраняется всегда, 
значение свойства NumberProp2 — никогда, а значение свойства NumberPropS — 
только в случае, если функция Fund возвращает значение true. 

    Команда Index 

    Команда Index используется в связи с методами доступа свойства. Она 
описана ниже, в разделе "Создание процедур Get и Set". 

    Команда Dispid 

    Команда Dispid используется для того, чтобы присвоить какому-нибудь 
свойству значение Dispatch ID. Она описана в главе 20, "Автоматизация OLE". 

    Чтение и запись значений свойств 

    Как было отмечено ранее, свойства предоставляют интерфейс с 
в^ЩР2}ними_полями данных. Одной из функциональных составляющих этого 
интерфейса является 1спооо^Н2ст^лГ?Ш113?ли_?,аписи в эти внутренние поля 
данных. Delphi предоставляет возможность определить характер взаимодействия с 
вашими внутренними полями данных через ключевые слова Read и Write. Вы можете 
читать и писать непосредственно во внутренние поля данных или создавать методы 
доступа для получения и установки значений внутренних полей данных. 
    Можно создать свойства, предназначенные только для чтения или только для 
записи, объявив их с помощью только метода Read или только метода Write соответственно. 

    Прямой доступ к скрытым (private) переменным 

    Чтение и запись во внутренние поля данных является простейшим методом 
изменения значения свойства. В листинге 16.2 вы видели три простых типа 
свойства, определенные следующим образом: 
    property NumberProp:   integer  read  FNumber write  FNumber; property 
CharProp:   char  read  FChar write  FChar; property StringProp:   string  
read FString write  FString; 


    Глава 16. Создание компонентов	369 

    Каждое из этих трех свойств непосредственно управляет внутренними полями 
данных компонента TEverything путем чтения и записи в скрытые (private) 
переменные. Однако предоставление пользователю вашего камвдн?щапрямого доступа 
к^нутренщш.ШДЯМЛанных не всегда^желательно. Некоторые значения свойств могут 
зависеть от других значении вТгутренних полей данных или от состояния 
компонента в данный момент времени. Такие типы свойств нуждаются в специальных 
методах доступа. 

    Создание процедур Get и Set 

    Методы доступа представляют собой функции и процедуры, позволяющие читать 
и записывать значения свойств. Функции используются для чтения значения 
свойства, и их имена по соглашению начинаются со слова "Get". Функции чтения 
не имеюглгадамедров и возвращают значение того же типа' что и у Данного 
свойства. Пр?цеду_ры используются для записи значения свойства и по соглашению 
начинаются со слова "Set". Процедуры записи имеют один пащметр того же типа, 
что и у данного свойства. Параметр может передаваться по ссылке или по 
значению и будет содержать новое значение для этого свойства. Для иллюстрации 
вышеизложенного создадим свойство, которое обладает методом доступа для чтения 
и записи: property 

    BooleanProp: Boolean read GetBooleanProp write SetBooleanProp; 

    Функция Read может выглядеть следующим образом. 

    function TEverything.GetBooleanProp: boolean; 
    begin   . 
    result := false/if (Estate = 0) then result := true; 
    end; 
    А процедура Write может иметь следующий вид. 
    procedure  TEverything.SetBooleanProp(var pBool: boolean) ; begin 
    if pBool  then Estate:=  0; else 
    Estate:=  1; end; 

    В предыдущем примере свойство BooleanProp используется для установки 
внутреннего поля данных, которое не имеет типа Boolean. И в самом деле, вам 
даже не нужна скрытая переменная FBooleanProp, поскольку свойство BooleanProp 
получает значение из переменной Estate. 
    Существует два^^клюделия из правила, по которому функции Read не имеют 
параметров. Этими исключениями являются свойства типа массив и команда свойств Index. 
    Свойства массив для манипуляции своими внутренними полями данных должны 
использовать методы доступа. Для свойств этого типа методы Read и Write имеют 
параметры, равные их типам индексов и расположенные в том же порядке. Другими 
словами,"если свойство массив имеет целочисленный индекс, оба метода доступа 
будут иметь один целочисленный параметр. Если же это свойство имеет один 
целочисленный индекс, за которым следует строковый индекс, методы доступа 
будут иметь два параметра: целочисленный (первый по порядку) и 'строковый, и 
т.д. В качестве примера описанного исключения рассмотрите методы доступа, 
объявленные выше, в листинге 16.7. 
    .Команда свойств Index добавляет функции Read целочисленный параметр, 
равный целому значению, которое следует за командой index в определении 
свойства. Рассмотрим, например, следующие строки. 

    property IntPropl:   integer  index  1   read GetlntProp write  
SetlntProp; property IntProp2:   integer  index  2  read GetlntProp write  SetlntProp; 

    Обратите внимание на то, что оба свойства используют одни и те же методы 
доступа. Если бы это были обычные методы доступа, вы были бы вправе 
поинтересоваться, как эти методы сообщают, j; какому именно свойству они 
предполагают получить доступ. Вот здесь и вступает в игру волшебство команды 
Index. Ведь в каждом методе участвует целочисленный параметр, который~й 
гТоказывает, к какому свойству предполагается доступ. Сами методы доступа 
GetlntProp и SetlntProp определяются следующим образом. 

    function TEverything.GetlntProp(plndex:   integer):   integer; begin 
    case plndex  of 
    1:   result   := FlntPropl; 2:   result   :=  FIntProp2; end; 


    370 Часть IV. Разработка компонентов 

    end; 
    procedure TEverything.SetlntProp(plndex: integer; pValue: integer); begin 
    case plndex of 
    1: FlntPropl := pValue;     * 2: FIntProp2 := pValue; end; end; 


    Значения инициализации 

    Существует два метода, доступных для инициализации внутренних полей 
данных. Первый состоит в переопределении конструктора Create, а второй — в 
переопределении метода Loaded.	*          "—————•— 

    Переопределение конструктора Create 

    Переопределение конструктора Create предоставляет возможность 
инициализировать внутренние поля данных пользовательскими значениями по 
умолчанию. В отличие от применения команды свойства Default, инициализация 
значений в конструкторе Create на самом деле выполняет установку значения 
внутреннего поля данных. В представленном выше разделе "Создание свойств типа 
"объект'"' рассматривался пример переопределения конструктора для реализации 
свойства объекта. Правило, которого следует придерживаться при переопределении 
конструктора Create, состоит в вызове наследованного конструктора Create до 
выполнения каких-либо других действий, связанных с инициализацией. Вряд ли вам 
когда-либо придется инициализировать какие-нибудь объекты до инициализации 
наследованного объекта.	~ " 

    Переопределение метода Loaded 

    Переопределение метода Loaded дает возможность инициализировать внутренние 
поля после создания компонента, но до его отображения в форме. Для понимания 
важности метода Loaded необходимо понять последовательность событий, 
происходящих, когда ваш компонент направляется в форму из DFM-файла. Этот 
процесс можно представить в виде следующих этапов.	" " 
    1.   Компонент реализуется, т.е. создается экземпляр компонента. 
    2.   Внутренние поля данных инициализируются нулями или значениями, 
заданными в переопределенном конструкторе Create. 
    3.	Из ВШнфайла считываются сохраненные данные компонента. 
    4.	Вызывается метод Loaded. 
    5.	Выполняется отображение компонента. 

    ПРЕДОСТЕРЕЖЕНИЕ	/                      \ 

    При переопределении метода Loaded всегда вызывайте наследованный метод 
Loaded первым, чтобы гарантировать, что все наследованные свойства 
инициализируются корректно. 
    Для демонстрации вышеизложенного в следующем примере переопределяется 
метод Loaded компонента TEverything и его свойство Color устанавливается 
равным clRed, если в свойстве IntegerProp было сохранено значение 5, в 
противном случае устанавливается зеленый цвет, т.е. свойство Color 
устанавливается равным clGreen. Это действие нельзя выполнить в конструкторе 
Create, так как значение, сохраненное в DFM-файле для свойства IntegerProp, в 
констщктрре является недоступным. Этот код приведен в листинге 16.8, а файл 
можно найти на дополнительно распространяемой дискете."" 

    Листинг 16.8. \UDELPHi3\CHPl6\LiSTl6-8. Компонент TEverything с 
переопределенным методом Loaded       ! 

    unit Evryfhng; 
    interface 
    uses 
    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs; 


    Глава 16. Создание компонентов	371 


    type 
    TEverything = class(TCustomControl) private 
    { Скрытые объявления. } 
    FInteger  : integer; protected	* 
    { Защищенные объявления. } 
    procedure Loaded; override; public 
    ( Открытые объявления. } published 
    { Публикуемые объявления. } 
    property IntegerProp: integer read FInteger write FInteger; end; 
    procedure Register; implementation 
    procedure TEverything.Loaded; begin 
    inherited Loaded; 
    if IntegerProp = 5 then 
    Color := clRed else 
    Color := clGreen; end; 
    procedure Register; begin 
    RegisterComponents('UD3', [TEverything]) ; end; 
    end. 


    Создание методов 

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

    Типы методов 

    Методы компонентов не отличаются от обычных методов Object Pascal. У вас 
есть возможность создания одного из пяти типов методов: стандартных, 
виртуальных, динамических, сообщений или абстрактных. Стандартные и 
динамические методы рассмотрены в главе 4, "Object Pascal: углубленное 
изложение", а методам сообщений уделяется внимание в следующей главе 
    . Сейчас же мы непосредственно переходим к виртуальным и абстрактным 
методам, поскольку они напрямую связаны с построением компонентов. 
    Создание виртуальных методов 
    Виртуальные методы обеспечивают возможность расширять компоненты. 
Аналогично тому, как вы время от времени будете наследовать некоторые 
компоненты VCL Delphi, кто-нибудь другой захочет наследовать компоненты, 
созданные вами. Помните, что только виртуальные и динамические методы могут 
быть переопределены, поэтому вы должны сделать как можно больше виртуальных 
методов доступными для ваших пользователей, не давая при этом им возможности 
изменить назначение своего компонента. 


    372	Часть IV. Разработка компонентов 


    Создание абстрактных методов 

    Абстрактные методы обычно содержатся в компонентах базовых классов и 
служат в_ качестве заглушек для методов, которые должны быть реализованы в 
компонентах-потомках. Вызов абстрактного метода стопроцентшГприводит к 
генерации ошибки времени выполнения (error 210), как показано в следующем примере. 

    type 
    TCustomGrid = class(TCustomControl) 
    protected 
    procedure DrawCell(ACol, ARow: Longint; ARect: TRect; 
    AState: TGridDrawState); virtual; abstract; 
    end; 

    TCustomGrid, базовый класс, от которого наследуются все сетки Delphi, 
объявляет виртуальный абстрактный метод DrawCell. Метод DrawCell вызывается 
методом Paint для получения изображения каждой ячейки сетки. Класс TCustomGrid 
не реализует метод DrawCell, чтобы наследованные от него 
классы_могли..прррд?РвЬШЭТ.ь_яче.йки в.своей собственной^никальной^яанейе. 
Хорошими примерами в этом смысле могут служить классы TDrawGrid и 
TString-Grid: оба они произошли от класса f CusfomS"rld и переопределяют и 
реализуют этот метод. 
    Компилятор Delphi выводит предупреждающее сообщение, если вы пытаетесь 
реализовать класс, который     J содержит абстрактные методы. Однако это не 
должно вас удерживать от создания экземпляра этого класса. *     Вызов 
абстрактного метода генерирует динамическую ошибку 210.         "	"""*" 

    Область видимости методов 

    Область видимости методов зависит от того, в каком разделе объявления 
класса находятся объявления методов. Методы, объявленные в разделе компонента 
private, доступны только внутри модуля, который содержит объявление этого 
класса. Методы, объявленные в разделе компонента protected, видны только для 
класса, в котором они объявлены, а также в производных классах. Методы, 
объявленные в разделе компонента public, видны любому пользователю компонента. 
Принято помещать методы доступа в раздел private, методы базового класса— в 
раздел protected, а методы базовых функций (методы, используемые всеми 
экземплярами этого компонента), к которым вы допускаете всех пользователей,— в 
раздел public. В раздел компонента published методы помещать нельзя— такая 
честь оказывается только?врйства\1^	" 
    Можно расширить^область видимости существующих методов в разделе 
компонента protected путем их переопре-      г\ деления в разделе pub lie.	" 

    Создание событий 

    В ваших приложениях и компонентах событие возникает как результат 
отправления операционной системой сообщения о том, что произошло некоторое 
действие. Такие действия могут быть результатом непосредственного 
взаимодействия с пользователем, например нажатия клавиши или щелчка на кнопке, 
либо результатом действия системы, когда изменяется, например, палитра или 
переполняется таймер. С точки зрения пользователей, они могут "выловить" эти 
события путем создания обработчиков событий во вкладке Events окна инспектора 
объектов. Но пользователь компонента не видит, что для каждощ 
рй)аб9тчикасрб^шш_йущест;вует метод отправления уведомления о событии. Этот 
метод и является настоящим уведомителем о том, что событие произошло. Он 
обрабатывает событие и проверяет, назначил ли пользователь соответствующий 
обработчик события. Если обработчик назначен, метод отправки уведомления о 
событии вызывает обработчика события. 
    По соглашению имена обработчиков событий начинаются с префикса On, а 
методы отправления уведомлений о событиях носят то же имя, но без префикса On. 
Хорошим примером может служить метод отправления уведомления о событии Click и 
обработчик события OnClick. 

    Структура событий 

    Методы отправления уведомлений о событиях являются процедурными методами, 
которые вызываются в случае, когда компонент получает сообщение о том, что 
произошло событие, воздействующее на ваш компонент. Большинство методов 
отправления уведомлений объявляются виртуальными (virtual) и защищенными 
(protected), поэтому они могут переопределяться компонентами-потомками, но не 
пользователем. Обработчики событий, или свойства событий (как они иногда 
называются), представляют собой свойства, которые выполняют чтение и запись в 
указатели на метод. Обработчики событий обычно объявляются в разделе 
компонента published, поэтому они доступны для инспектора объек

    Глава 16. Создание компонентов	373 

тов, за исключением базовых классов, которые объявляют их в разделе 
protected, поэтому классы-потомки могут при необходимости делать их доступными 
для пользователя. Рассмотрим следующий код, взятый из класса TControl, 
содержащегося в исходном коде VCL. 

    TMouseEvent  = procedure   (Sender:   TObject;   Button:   TMouseButton; 
    Shift:   TShiftState;   X,   Y:   Integer)   of object; TMouseMoveEvent  = 
 procedure   (Sender:   TObject;   Shift:   TShiftState; 
    X,   Y:   Integer)   of  object; TNotifyEvent  = procedure   (Sender:   
TObject)   of  object; 
    TControl  = class(TComponent) private 
    JjpnMouseDown:   TMouseEvent; FOnMouseMove:   TMouseMoveEvent; FOnMouseUp: 
  TMouseEvent; FOnClick:   TNotifyEvent; FOnDblClick:   TNotifyEvent; 
    protected 
    property OnClick: TNotifyEvent read FOnClick write FOnClick; property 
OnDblClick: TNotifyEvent read FOnDblClick write FOnDblClick; property 
OnMouseDown: TMouseEvent read FOnMouseDown write FOnMouseDown; property 
OnMouseMove: TMouseMoveEvent read FOnMouseMove write FOnMouseMove; property 
OnMouseUp: TMouseEvent read FOnMouseUp write FOnMouseUp; 
    end; 

    Когда Delphi видит пyбликyeмoeJCJвc^cд^вpJJГJ^Kl^?oгJ^мяeтcя указателем 
на_метод, она автоматически помещает его во вкладку Events инспектора 
объектов. Обратите внимание на то, что каждое событие может работать только с 
указателем надетйд, а_не с указателем на функцию. Именно поэтому вы не можете 
назначить стандартную процедуру как "обрЗботчик событий. Каждое" свойство 
выполняет чтение и запись в «срытую'(private) переменную того же самого типа 
указателя на метод, с которым объявляется событие. 

    Переопределение стандартных событий 

    Наверное, вы испытываете искушение сначала создать стандартный обработчик 
событий и автоматически назначить его свойству события, чтобы изменить 
поведение какого-нибудь компонента, пока пользователь не назначил одного из 
своих собственных обработчиков событий этому свойству события. При 
возникновении такой ситуации заданное вами поведение заменяется стандартным. 
Чтобы реально изменить поведение существующего события, вы должны 
переопределить метод компонента отправления уведомления о событии. При 
переопределении этого метода следует указать, когда вызывается наследованное 
поведение, а когда — обработчик событий. Для примера создайте свой собственный 
компонент кнопки, который после щелчка на этой" кнопке йзменя-~ет~ёе заголовок 
на текущее время. Для этого переопределите метод отправления уведомления о 
событии Click, как показано в листинге 16.9. Этот файл находится на 
дополнительно распространяемой дискете. 

    Листинг 16.9. \UDELPHl3\CHP16\LIST16-9. КомпонентTTimeButton с 
переопределенным методом отправления уведомления о событии 

    TTimeButton = class(TButton) protected 
    procedure Click; override; end; 
    procedure TTimeButton.Click; begin 
    inherited Click; 
    Caption := DateTimeToStr(Now); end; 

    Обратите внимание на то, что наследованный метод Click вызывается до 
изменения заголовка. Это означает, что любые обработчики событий, которые 
пользователь сможет подключить к событию OnClick, будут активизироваться до 
изменения надписи на кнопке. Если бы вы захотели, чтобы ваш код отработал 
первым, вы могли бы вызвать наследованный метод после своего кода. 

    374	Часть IV. Разработка компонентов 


    Создание событий 

    События создаются в два этапа. Сначала вы должны создать метод отправления 
уведомления о событии, который может, в свою очередь, вызвать обработчика 
события. Затем необходимо позволить своему компоненту отвечать на действие, 
активизируемое вашим методом отправления уведомления о событии. Большинство 
пользовательских событий создается для реагирования на компонент, получающий 
определенное сообщение. Обработка сообщений рассматривается в следующей главе. 
Но выше было показано, как объявить свойство события и скрытую переменную 
обработчика события. Следующие строки демонстрируют стандартный код для метода 
отправления уведомления о событии. 

    procedure TSomeObject.EventDispatch; begin 
    //  Предположим,   FOnEventHandler  имеет   тип  TNotifyEvent. 
    // Выполнение  некоторой  обработки  кода. 
    if Assigned(FOnEventHandler)   then FOnEventHandler(Self); 
    //  Выполнение  некоторого  кода,   если  это  необходимо. end; 

    Предыдущий фрагмент кода типичен для методов отправления уведомления о 
событиях. Обычно после возникновения события выполняется некоторая обработка 
кода. Затем происходит вызов обработчика события, если пользователь 
позаботился о его назначении. После этого выполняется еще некоторый код. Все 
методы отправления уведомления о событиях различаются в зависимости от функций 
конкретного события. Но это уже относится к другой области разработки 
компонентов, где исходный код VCL просто бесценен. Изучив исходный код, вы 
поймете, в какой момент обработки событий вызывается обработчик события, и это 
поможет вам определить, что создать: метод уведомления о событии или просто 
обработчик события с помощью инспектора объектов.	*~~ -——-•—— " 

    Тестирование компонента 

    Для тестирования компонента есть два метода. Первый заключается в 
добавлении вашего нового компонента в палитру компонентов с последующим его 
помещением в новое приложение. Второй метод состоит в построении маленького 
приложения, которое создает экземпляр вашего компонента во время выполнения 
приложения. Оба метода имеют как преимущества, так и не достатки. Первый 
позволяет проверить, как компонент ведет себя и в период разработки, и во 
время работы, и при этомлелыщсждаёГвас писать код для реализации, и 
инициализации,камдонента. Но в этом случае приходится перекомпилировать пакет 
вашего компонента при каждом внесении в него изменений. Используя второй 
метод, можно проверить поведение компонента в процессе работы приложения, но у 
вас нет возможности отобразить его в окне инспектора объектов. 
    Сначала~66спользуемся вторым методом, чтобы получить обобщенное 
представление о работе компонента и посмотреть, как выполняются его основные 
функции. Затем перейдем к первому методу, чтобы понять, как ведет себя 
компонент в процессе разработки с точки зрения пользователя. Первый метод 
описан в завершающем разделе данной главы, "Регистрация компонента". Сейчас же 
остановимся на втором методе. 
    Типичное приложение, разрабатываемое для тестирования компонента, состоит 
из формы с несколькими кнопками. Первая кнопка используется для инсталляции 
пользовательского компонента, вторая освобождает компонент, следующая 
устанавливает некоторые свойства, еще одна вызывает некоторое событие и т.д. 
При инсталляции компонента во время выполнения важно ^не_забыть установить 
свойство компонента Parent. Если вы имеете дело с визуальным элементом 
управления, то вам, возможно, придется~также установить свойства, 
обозначающие, где и как этот элемент управления отображается (свойства Top, 
Left, Enabled и Visible). Листинг 16.10 служит наглядным примером того, как 
может выглядеть приложение для тестирования компонента TEverything. Этот файл 
находится на дополнительно распространяемой дискете. 

    Листинг 16.10. \UDELPHI3\CHP16\LIST16-10. Приложение для тестирования 
компонента TEverything	! unit  Unitl; 

    interface 
    uses 
    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; 


    Глава 16. Создание компонентов	375 

    type 
    TForml = class(TForm) 
    Buttonl: TButton; 
    Button2: TButton; 
    Button3: TButton; 
    Button4: TButton; 
    procedure ButtonlClick(Sender: TObject); 
    procedure Button2Click(Sender: TObject); 
    procedure ButtonSClick(Sender: TObject); 
    procedure Button4Click(Sender: TObject); private 
    ( Скрытые объявления. } public 
    { Открытые объявления. } end; 
    var 
    Forml: TForml; 
    implementation uses EvryThng; {$R *.DFM} 
    var 
    Everything : TEverything; 
    procedure TForml.ButtonlClick(Sender: TObject); begin 
    // Создание экземпляра компонента Everything. Everything := 
TEverything.Create(Self); // Инициализация свойств компонента Everything, with 
Everything do begin Parent := Self; Left := 20; Top := 20; Height := 40; Width 
:= 100; Color := clBlue; NumberProp := 5; end; end; 
    procedure TForml.Button2Click(Sender: TObject); begin 
    if Assigned(Everything) then 
    Caption := IntToStr(Everything.NumberProp); end; 
    procedure TForml.Button3Click(Sender: TObject); begin 
    if Assigned(Everything) then Everything.color := clRed; end; 
    procedure TForml.Button4Click(Sender: TObject); begin 
    EveryThing.Free; end; 
    end. 


    376 Часть IV. Разработка компонентов 


    Создание пиктограммы компонента 

    Когда Delphi добавляет ваш компонент в палитру компонентов, она ищет 
пиктограмму в OCR-файле (Delphi Component Resource), имя которого совпадает с 
именем модуля вашего компонента. OCR-файл — это просто переименованный 
файл^е-_?У_Е595_(кЕЗ-файл). Если Delphi не находит OCR-файла, то для 
представления вашего компонента в палитре компонентов она использует 
стандартную пиктограмму. Внутри OCR-файла необходимо создать 16- или 
256-цветный растр размером 24x24 пикселя. Рекомендуется создавать 16-цветный 
растр, поскольку 256-цветный ужасно выглядит на машине, способной отображать 
только 16 цветов, а на компьютере с 256 цветами вы находитесь во власти 
загруженной в текущий момент цветовой палитры. 
    Имя растрового изображения должно совпадать с именем класса вашего 
компонента, набранном заглавными буквами. Чтобы создать пиктограмму для 
компонента TEverything, выполните следующие действия. 
    1.   Откройте редактор изображений (Image Editor), выбрав команду Tools^lmacje^Editor. 
    2.   В редакторе изображений выберите команду File^New^Delphi Component 
Resource File. 
    3.   Откройте диалоговое окно New Bitmap, выбрав команду 
Resource^NewoBitmap. Тем самым вы создадите растровое изображение Bitmapl. 
    4.	Установите ширину и высоту растра Bitmapl  равными 24, а количество 
цветов — 16 или 256. 
    5.	Дважды щелкните на растре Bitmapl и создайте растровое изображение для 
своего компонента. 
    6.	Переименуйте растр Bitmapl в TEVERYTHING. 
    7.	Выберите команду File^Save As и введите EvryThng. OCR. 
    Если вы не сохранили OCR-файл в одном каталоге с PAS-файлом вашего 
компонента, перепишите его в этот каталог. 
    Когда Delphi отображает пиктограммы в палитре компонентов, она использует 
цвет нижнего левого пикселя как прозрачный цвет для вашей пиктограммы. Все 
элементы в этом цвете вашего растра становятся прозрачными и, таким образом, 
"исчезают". Delphi использует значение clOlive для фона и прозрачного цвета 
всех своих пиктограмм. 
    I Совет 1 

    Регистрация компонента 

    В ранних версиях Delphi компоненты инсталлировались в палитру компонентов 
путем компиляции в библиотеку компонентов DLL. В Delphi 3,6и6лиод?кд 
компонентов (Component Library.) заменена пакетами, как было описано в главе 
15, "Работа с пакетами". На рис. 16.1 были представлены эксперт компонентов 
Delphi и все элементы, необходимые для создания базовой структуры 
пользовательского компонента. С помощью щелчка на кнопке Install эксперта 
компонентов можно определить, в какой пакет времени разработки вы хотели бы 
инсталлировать свой компонент. Если такого пакета еще не существует, создайте 
его, как показано на рис. 16.6. 

    •', Into easting package   Wo new package 
    Rename:     ]UDelphi3dpk 
    ?(owse... 
    ?>esctiption:  I Examples Ira SE Using Delphi 3 
    Cancel 
    ИФ 

    Рис. 16.6. Диалоговое окно Install 

    Если вы не выберете пакет для инсталляции своего компонента во время 
создания, можете выбрать из главного меню Delphi команду ComponentOlnstall 
Component, чтобы получить аналогичный результат. Инсталлированный компонент 
автоматически появится в палитре компонентов, полностью готовый к работе. 
    ? 

    Глава 16. Создание компонентов 377 


    Резюме 

    В этой главе рассмотрены основы создания компонентов и заложен фундамент 
для создания пользовательских компонентов в Delphi. Вы узнали, что собой 
представляет компонент и получили понятие об иерархии базовых классов VCL. 
Теперь вы знаете, как выбрать правильного предка, создать свои собственные 
свойства и события, выполнить отладку компонента и добавить свой компонент в 
палитру компонентов Delphi. Четкое понимание этих основ является залогом 
успешного применения более сложных методов написания компонентов, рассматриваемых 
в следующей главе. 
    Например, создадим пользовательский компонент, который инкапсулирует класс 
Delphi TApplication. TAppli-cation — это объект, который создается "за сценой" 
и является частью каждого приложения Delphi. Он обладает многочисленными 
свойствами и событиями, которые позволяют управлять многими аспектами вашего 
приложения. Поскольку Delphi не использует визуального компонента TApplication, 
вам придется манипулировать его свойствами и событиями программным путем. Это 
часто выполняется в событии OnCreate для главной формы любого приложения. 
    Создадим невизуальный пользовательский компонент TApplicationComp, который 
позволит устанавливать свойства и обработчики событий в период разработки. 
Поскольку это невизуальный компонент, будем наследовать наш компонент из 
класса TComponent. Объявим свойства и события идентичными свойствам и событиям 
класса TApplication и назначим свойства и события нашего компонента свойствам и 
событиям класса TApplication в методе Loaded. С помощью навыков, приобретенных 
при изучении этой главы, вы сможете создать структуру компонента, как показано 
в листинге 16.11. 


    ЛИСТИНГ 16.11. \UDELPHI3\CHP16\LIST16-11. Пользовательский КОМПОНенТ TApplicationComp 

    unit AppCompU; interface 
    uses 
    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs; 
    type 
    TApplicationComp = class(TComponent) private 
    { Скрытые объявления. } 
    FAllowTesting: Boolean; 
    FHint: string; 
    FHintColor: TColor; 
    FHintPause: Integer; 
    FHintShortPause: Integer; 
    FHintHidePause: Integer; 
    FShowHint: Boolean; 
    FShowMainForm: Boolean; 
    FUpdateFormatSettings: Boolean; 
    FUpdateMetricSettings: Boolean; 
    FOnActivate: TNotifyEvent; ' FOnDeactivate: TNotifyEvent; 
    FOnException: TExceptionEvent; 
    FOnldle: TIdleEvent; 
    FOnHelp: THelpEvent; 
    FOnHint: TNotifyEvent; 
    FOnMessage: TMessageEvent; 
    FOnMinimize: TNotifyEvent; 
    FOnRestore: TNotifyEvent; 
    FOnShowHint: TShowHintEvent; protected 
    { Защищенные объявления. } 
    procedure Loaded; override; public 
    { Открытые объявления. } published 
    { Публикуемые объявления. } 
    // Свойства. 


    378	Часть IV. Разработка компонентов 


    property AllowTesting: Boolean read FAllowTesting write FAllowTesting; 
    property Hint: string read FHint write FHint; 
    property HintColor: TColor read FHintColor write FHintColor; 
    property HintPause: Integer read FHintPause write FHintPause; 
    property HintShortPause: Integer read FHintShortPause write FHintShortPause; 
    property HintHidePause: Integer read FHintHidePause write FHintHidePause; 
    property ShowHint: Boolean read FShowHint write FShowHint; 
    property ShowMainForm: Boolean read FShowMainForm write FShowMainForm; 
    property UpdateFormatSettings: Boolean read FUpdateFormatSettings 
    write FUpdateFormatSettings; property UpdateMetricSettings: Boolean read FUpdateMetricSettings 
    write FUpdateMetricSettings; 
    // События. 
    property OnActivate: TNotifyEvent read FOnActivate write FOnActivate; 
property OnDeactivate: TNotifyEvent read FOnDeactivate write FOnDeactivate; 
property OnException: TExceptionEvent read FOnException write FOnException; 
property Onldle: TIdleEvent read FOnldle write FOnldle; property OnHelp: 
THelpEvent read FOnHelp write FOnHelp; property OnHint 
    : TNotifyEvent read FOnHint write FOnHint; property OnMessage: 
TMessageEvent read FOnMessage write FOnMessage; property OnMinimize: 
TNotifyEvent read FOnMinimize write FOnMinimize; property OnRestore: 
TNotifyEvent read FOnRestore write FOnRestore; property OnShowHint: 
TShowHintEvent read FOnShowHint write FOnShowHint; end; 
    procedure Register; implementation 
    procedure TApplicationComp.Loaded; begin 
    // Назначение свойств. 
    if FAllowTesting <> Application.AllowTesting then 
    Application.AllowTesting := FAllowTesting; if FHint <> '' then 
    Application.Hint := FHint; if FHintColor <> Application.HintColor then 
    Application.HintColor := FHintColor; if FHintPause <> 
Application.HintPause then 
    Application.HintPause := FHintPause; if FHintShortPause <> 
Application.HintShortPause then 
    Application.HintShortPause := FHintShortPause; if FHintHidePause <> 
Application.HintHidePause then 
    Application.HintHidePause := FHintHidePause; if FShowHint <> 
Application.ShowHint then 
    Application.ShowHint := FShowHint; if FShowMainForm <> 
Application.ShowMainForm then 
    Application.ShowMainForm := FShowMainForm; if FUpdateFormatSettings <> 
Application.UpdateFormatSettings then 
    Application.UpdateFormatSettings := FUpdateFormatSettings; if 
FUpdateMetricSettings <> Application.UpdateMetricSettings then 
    Application.UpdateMetricSettings := FUpdateMetricSettings; 
    // Назначение событий. 
    if Assigned(FOnActivate)then 
    Application.OnActivate := FOnActivate; if Assigned(FOnDeactivate)then 
    Application.OnDeactivate := FOnDeactivate; if Assigned(FOnException)then 
    Application.OnException := FOnException; if Assigned(FOnldle)then 
    Application.Onldle := FOnldle; if Assigned(FOnHelp)then 
    Application.OnHelp := FOnHelp; 


    Глава 16. Создание компонентов	379 


    if Assigned(FOnHint)then 
    Application.OnHint := FOnHint; if Assigned(FOnMessage)then 
    Application.OnMessage := FOnMessage; if Assigned(FOnMinimize)then 
    Application.OnMinimize := FOnMinimize; if Assigned(FOnRestore)then 
    Application.OnRestore := FOnRestore; if Assigned(FOnShowHint)then 
    Application.OnShowHint := FOnShowHint; end; 
    procedure Register; begin 
    RegisterComponents('UD3', [TApplicationComp]) end; 
    end. 


    380 Часть IV. Разработка компонентов 

