    Сложные компоненты 

    ГЛАВА 17 

    Дэвид Пауэл 

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

    Еще о свойствах 

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

    Диагностика состояния компонента 

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

    Свойства и методы 

    Продолжим изучение важнейших свойств и методов, доступных в базовых 
классах компонентов. 

    Компоненты для работы с данными 

    Как "научить" пользовательские элементы управления работать с базами данных. 

    Сообщения 

    Как реагировать на сообщения Windows и как создать свои собственные сообщения. 

    Редакторы свойств 

    Изучите процесс создания редакторов свойств. 

    Редакторы компонентов 

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

381


    Таблица 17.1. Значения свойства ComponentState 

    Флаг_________Состояние компонента______ ___ _______ __ 

    csAncestpr	Устанавливается, если компонент был внесен в форму предка и 
если определено свойство csDesigning
    csDesigning____Указывает на режим разработки, т.е. когда форма находится в 
работе в процессе разработки csDestroing_ Указывает на состояние 
компонента перед разрушением 
    csFixups	Устанавливается, если компонент связан с компонентом другой 
формы, которая еще не загружена. Сбрасывается, когда все 
адресные ссылки разрешаются 
    csLoading    ___  Означает загрузку из объекта файловой системы 
csReading	Означает чтение значений свойства из потока 
    csUpdating	Устанавливается, когда компонент обновляется для отображения 
изменений в форме предка и если установлено свойство csAncestor 
    csWriting	Означает запись значений свойства в поток 
    Для того чтобы узнать, не находится ли ваш компонент в определенном 
состоянии, нужно проверить, установлен ли соответствующий признак в свойстве 
ComponentState. Например, следующий код поможет установить, находится ли 
компонент в режиме разработки. 

    if   (csDesigningi.ji....ComponentState}   then 
    ShowM^ssage (Tplease  register  this  Component'}; 

    Диагностика состояния элемента управления 

    Базовый класс компонента TControl вводит свойство типа множество, которое 
позволяет определить текущее состояние элемента|управления во время работы 
приложения. Свойство ControlState определяется следующим образом. 
    TControlState  =  set  of   (csLButtonDown,   csClicked,   csPalette,   
csReadingState, csATrg"nm?TTtTJe"eded,   csFocusing,   csCreating,   
csPaintCopy) ; 
    Эти флаги состояния специфичны для элементов управления (потомков класса 
TControl) и работают в дополнение к флагам состояния свойства ComponentState. 
В табл. 17.2 кратко описан каждый флаг состояния. 

    Таблица 17.2. Значения свойства ControlState 

    Флаг	Значение                     __     _______________________________ 

    csLButtonDown	Была нажата и не отпущена левая кнопка мыши. Устанавливается 
для всех событий, использующих нажатие левой кнопки мыши____________________________ 
    csClicked	Аналогичен значению флага csLButtonDown, но устанавливается 
только в случае, если 
    свойство ControlStyle  содержит флаг csClickEvents, означающий, что 
события. v     использующие нажатие левой кнопки мыши, 
интер^п^ети^уютс^как.цхёлчки   __ 
    csPalette	Любым элементом управления или одним из его предков было 
получено сообщение 
    WM_PALETTCHANGED
    csReadingState	Элемент управления читает свое состояние из потока 
    csAlignmentNeeded __ Элемент управления требует выравнивания__________ ___ 
    csFocusing	Приложение обрабатывает сообщения передачи фокуса. Это не 
гарантирует, что данный элемент управления получит фокус, но'предотвратит 
рекурсивные вызовы 
    csCreating	Элемент управления и/или его владелец и вложенные элементы 
управления находятся в стадии создания. Этот флаг снимается по завершении 
процесса создания элементов управления 
    csPaintCopy	Создается копия данного элемента управления. При этом должен 
быть установлен флаг 
    csReplicatable свойства ControlStyle 


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


    Установка стиля элемента управления 

    Свойство,_Coatrol?tyle представляет собой множество флагов стиля элемента 
управления, которые используются для определения характеристик конкретного 
элемента управления. Обычно Е^м_п?идется устанавливать или изменять свойство 
ControlStyle в конструкторе компонента Create. Как правило, стиль элемента 
управления фиксируется по окончании создания и больше не изменяется. Но вы 
можете программным путем изменить стиль элемента управления во время 
разработки (в редакторе свойств или редакторе компонентов) либо в процессе 
работы приложения. 
    Свойство ControlStyle определяется как свойство типа TControlStyle. 
Определение типа TControlStyle выглядит следующим образом. 
    TControlStyle  =  set  of   (csAcceptControls,   csCaptureMouse,   csDesignlnteractive, 
    csClickEvents,   csFramed,   csSetCaption,   csOpaque,   csDoubleClicks, 
csFixedWidth,   csFixedHeight,   csNoDesignVisible,   csReplicatable, 
csNoStdEvents,   csDisplayDraglmage); 
    Свойство ControlStyle введено в базовый класс TControl и инициализируется 
в конструкторе путем включения следующих флагов. 
    FControlStyle   :=   [csCaptureMouse,   csClickEvents,   csSetCaption,   
csDoubleClicks]; В табл. 17.3 перечислены все флаги стиля и их влияние на 
внешний вид и поведение элемента управления. 

    Таблица 17.3. Значения свойства ControlStyle 

    Флаг	Значение 

    csAcceptControls	Элемент управления становится родителем любого элемента 
управления, опущенного на него во время разработки 
    csCaptureMouse	Элемент управления перехватывает события мыши 
    csDesignlnteractive	Элемент управления в период разработки интерпретирует 
щелчки правой кнопкой мыши как щелчки левой кнопкой                            
    csClickEvents	Элемент управления может получать щелчки мышью и отвечать на них 
    csFramed	Для отображения элемента управления используются трехмерные эффекты 
    csSetCaption	Элемент управления должен изменить свой заголовок, чтобы он 
совпадал со свойством Name, если заголовок не будет явно установлен равным 
какому-нибудь другому значению 
    csOpaque	Элемент управления полностью заполняет клиентскую область 
    csDoubleClicks	Элемент управления может получать сообщения о двойных 
щелчках мышью и отвечать на них. В npOTHBH?Mjcjj?jaejHj?jjHbie щелчки 
интегзпгзетируются как обычные 
    csFixedWidth	Ширина элемента управления не изменяется 
    csFixedHeight	Высота элемента управления не изменяется 
    csNoDesignVisible	Элемент управления не виден во время разработки 
    csReplicatable	Элемент управления ^ожет быть_помещен на компонент DBCtrlGrid 
    csNoStdEvents	Игнорируются такие стандартные события, как щелчки мышью и 
нажатия клавиш. Используйте эту установку, если ваш код не нуждается в реакции 
на эти события, и приложени?будет работать^ыехрее» 
    csDisplayDraglmage	Элемент управления может выводить изображение из списка 
изображений при его пе-    /^ ретаскивании. Используйте эту установку, если 
ваш элемент управления способен           4 корректно обрабать1вать_список_из?бр_ажений 
    Если вы наследуете класс от существующего элемента управления, чтобы 
обработать события OnClick и Совет 1   OnDblClick, а ваш элемент управления 
по-прежнему не^реагирует на упомянутые события, то, прежде всего, проверьте 
свойство ControlStyle, чтобы узнать, не сброшены ли флаги csClickEvents и csDoubleClicks. 

    Глава 17. Сложные компоненты 383 


    Методы, заслуживающие особого внимания 

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

    Метод Loaded 

    Метод Loaded был описан в главе 16, "Создание компонентов". Этот метод 
позволяет в процессе разработки управлять значениями свойств компонента после 
того, как он был прочитан из DFM-файла, и до его отображения. Это дает 
возможность инициализировать скрытые (private) и открытые (public) поля 
данных, которые могут зависеть от сохраненного значения другого свойства. 

    Метод Notification 

    Метод Notification является виртуальным методом, который введен 
компонентом TComponent и вызывается средой IDE Delphi каждый раз, когда 
компонент помещается в форму или удаляется из нее. Метод Notification 
определяется следующим образом: 

    procedure Notification(AComponent:   TComponent;   Operation:   
TOperation);   virtual; 

    Метод Notification имеет два параметра. Первым является компонент, который 
добавляется в форму или удаляется из нее, а второй представляет собой 
выполняемую операцию: либо oplnsert, либо opRemove. Этот метод важен для 
компонентов, которые ссылаются на другие компоненты формы как на одно из своих 
свойств. Рассмотрим, к примеру, метод Notification для компонента TBatchMove. 

    procedure TBatchMove.Notification(AComponent:   TComponent;   Operation:   
TOperation); begin 
    inherited Notification(AComponent;   Operation); 
    if Operation =  opRemove  then 
    begin 
    if  Destination  =  AComponent   then  Destination   :=  nil; if  Source = 
AComponent  then  Source   := nil; end; end; 

    Здесь сначала вызывается наследованный метод Notification, а затем 
проверяется тип операдии на совпадение с opRemove. В случае совпадения 
выполняется проверка на предмет того, является ли удаляемый компонент тем же 
компонентом, который используется для наборов данных Source или Destination. 
При подтверждении этого факта соответствующее свойство устанавливается равным nil. 

    Метод FreeNotification 

    Метод FreeNotification является статическим методом, введенным классом 
TComponent. Он принимает один параметр типа TComponent и гарантирует, что 
компонент, переданный как параметр, извещается через метод Notification об 
освобождении вызванного компонента. 
    Можно добиться аналогичного результата в случае, когда ваш компонент 
вызывает метод FreeNotification другого компонента и передает себя в качестве 
параметра. При освобождении другого компонента ваш компонент уведомляется 
через метод Notification. 
    Метод FreeNotification определяется следующим образом. 

    procedure  FreeNotification(AComponent:   TComponent); 

    Метод компонента Notification автоматически вызывается в случае добавления 
или удаления любого компонента формы, в которой расположен ваш компонент. Если 
ваш компонент зависит от компонента, который размещен в другой форме, ваш 
компонент не будет автоматически уведомлен об удалении другого компонента, 
если, конечно,~вы в явном виде не велите другому компоненту уведомить вас об 
этом. Этот момент зачастую не учитывался при создании элементов управления для 
работы с данными в случаях, когда компонент TDataSource размещался в модуле 
DataModule. Далее в этой главе, в разделе "Создание элемента управления 
данными", приведен пример использования метода FreeNotification с такими 
элементами управления. 

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


    Метод DestroyWnd 

    Метод DestroyWnd впервые был введен классом TWinControl и является частью 
процесса разрушения компонента. Он отвечает за освобождение любого контекста 
устройства, связанного с элементом управления, и уничтожение дескриптора окна 
(Window Handle). Этот метод вызывается до деструктора компонента Destroy. Если 
при выполнении кода в переопределенном деструкторе Destroy вы получите сообщение 
об ошибке с заявлением о TOMJ.HTO "Window has_nq parent" (т.е. у окна нет родителя), 
значит, вы совершаете действия, касающиеся действительного дескриптора окна и ваш 
код должен выполняться в методе DestroyWnd. При переопределении этого метода вам 
стоит выполнять свой код до вызова наследованного метода DestroyWnd. Чтобы лучше 
представить себе все вышеизложенное, рассмотрите следующий код из класса TCustomEdit. 

    procedure  TCustomEdit.DestroyWnd; begin 
    FModified   := Modified;   w    ЙМ9 
    inherited  DestroyWnd; .^x    {j^	' '''" end; 
    function TCustomEdit.GetModified:   Boolean; begin 
    Result   :=  FModified; 
    if HandleAllocated  then 
    Result   :=  SendMessage(Handle,   EM_GETMODIFY,   0,    0)   <>   0; end; 

    Переопределенный метод DestroyWnd устанавливает внутреннее поле данных 
FModified равным свойству Modified. Теперь метод чтения GetModif ied для 
свойства Modified вызывает функцию SendMessage, используя дескриптор окна 
элемента управления поля ввода. В данном случае любое действие необходимо 
выполнять, пока дескриптор окна все еще может разрушать и единственным 
возможным местом для этого является данный метод. 

    Метод GetChildren 

    Метод GetChildren представляет собой динамический метод, который впервые 
был объявлен в классе ТСотро-nent и является частью процесса передачи потоков 
любого компонента. Метод GetChildren определяется следующим образом. 
    procedure  GetChildren(Proc:   TGetChildProc;   Root:   TComponent);   
dynamic; TGetChildProc  = procedure(Child:   TComponent}   of  object; 
    Если вы пишете компонент, который наследован от одного из базовых классов 
компонентов, и этот компонент является классом контейнера, переопределите 
метод GetChildren таким образом, чтобы компоненты, опущенные на ваш элемент 
управления во время разработки, сохранялись в DFM-файле. Метод GetChildren 
принимает два параметра, один из которых, Ргос, сам является процедурой, а 
второй — Root — имеет тип TComponent. Метод GetChildren необходим для перебора 
всех элементов управления или компонентов, которым он владеет, и вызова 
процедуры Ргос для каждого дочернего компонента. Параметр Root используется 
далеко не всегда — все зависит от ситуации. В качестве примера рассмотрите код 
для компонента TMenuItem. 

    procedure  TMenuItem.GetChildren(Proc:   TGetChildProc;   Root:   
TComponent); var 
    I:   Integer/begin 
    for  I   :=  0  to  Count  -  1  do 
    Proc(Items[I] ) ; end; 

    В процедуре GetChildren компонента TMenuItem выполняется цикл по всем 
элементам свойства Items и вызывается процедура Ргос с параметром Items. При 
этом каждый элемент свойства Items направляется в поток DFM-файла. В этой 
ситуации компоненту TMenuItem необходимо направить в поток все элементы 
свойства Items, поэтому он не беспокоится о параметре Root для указании 
владельца каждого элемента, поскольку знает, что сам является владельцем всех 
элементов меню. Если бы это был класс контейнера, то нужно было бы в качестве 
параметра Root указать значение свойства Owner каждого компонента. Если вы 
наследуете элемент управления из существующего компонента, а не из базового 
класса, вам редко придется переопределять этот метод. 

    Глава 17. Сложные компоненты	385 
    13 Использование Delphi 3. Специальное издание 


    Обработка сообщений 

    Сообщения представляют собой интерфейс между средой Windows и вашим 
компонентом._Полу_ченное сообщение означает, что где-то произошло действие. 
Чаще всего компоненты, будь они стандартные или определенные пользователе^ 
обязаньГбтвечать на сообщения Windows. Большинство стандартных сообщений 
Windows определено в исходном файле. WINDOWS. PAS, причем многие из них уже 
реализованы в базовых классах компонентов. Но время от времени вам придется 
обрабатывать сообщения, помещенные в очередь, или же возникнет необходимость в 
создании своих собственных (определенных пользователем) сообщений. 

    Обработка сообщений Windows 

    Когда приложение получает сообщение Windows, фактически оно получает 
запись, которая определяется следующим образом. 

    PMsg = ЛТМзд; 
    TMsg = packed record 
    hwnd: HWND; 
    message: UINT; 
    wParam: WPARAM; 
    IParam: LPARAM; 
    time: DWORD; 
    pt: TPoint; end; 

    Поле hwnd является дескриптором окна элемента управления, для которого 
предназначено сообщение. Поле message представляет собой предопределенное 
значение— константу номера сообщения (эти константы определены в исходном 
файле MESSAGES. PAS). Поля wParam и IParam содержат данные, характерные для 
конкретного сообщения. Поле time хранит время, когда Windows обработала 
сообщение, и, наконец, поле pt определяет координаты указателя мыши во время 
выдачи сообщения. 
    Во^внутреннем цикле обработки сообщений Delphi (Message Loop) из очереди 
сообщений (Message Queue) выбираются сообщения и оформляются в виде элементов 
типа TMessage, который определяется следующим образом. 

    PMessage =  "TMessage; TMessage  =  record; Msg:   Cardinal; case   
Integer  of 0:    ( 
    WParam:   Longint; LParam:   Longint; Result:   Longint); 1:    ( 
    WParamLo: Word; WParamHi: Word; LParamLo: Word; LParamHi: Word; ResultLo: 
Word; ResultHi: Word); end; 

    В файле MESSAGES. PAS определено много записей для конкретного типа 
сообщений, размер которых совпадает с размером типа TMessage. 
    Для обработки сообщения Windows необходимо создать процедуру, которая 
принимает один параметр типа TMessage (или одну из других упомянутых выше 
записей типа TMessage). Кроме того, в конце объявления метода вы должны 
указать ключевое слово message и константу сообщения. Обычно процедуре 
присваивают имя, которое совпадает с константой сообщения, но без символа подчеркивания. 
    Например, в раздел интерфейса следует поместить следующее объявление. 

    TEverything  =  class(TCustomControl) 
    public 
    procedure WMChar(var Message: TWMChar); message WM_CHAR; end;        ^-^	•**•——ч 


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

    А в разделе реализации должно содержаться объявление, аналогичное следующему. 

    procedure TEverything.WMChar(var Message:   TWMChar); begin 
    MessageBeep(0); 
    inherited; end; 

    Обратите внимание на то, что это объявление во многом напоминает 
переопределенный виртуальный метод, поскольку на самом деле message WM_CHAR 
является_,аинамическим методом, таблица динамических методов которого содержит 
константы сообщений. В.отличие^от BjjPjy^bjbixjMieTOflOBj здесь вам не нужно 
указывать, какой наследованный метод необходимо вызвать, и беспокоиться по 
поводу задания каких-либо параметров. В_ данном примеречключевое слово 
ij}]^??itecl_raна^е^'п^еда^ь^до,§]дандербр.9Д9т,чйКУ сообщений предка".	" " "———"~~~ 
    Некоторые сообщения Windows ожидают ответа от "оконного" элемента 
управления. Именно для этого и предназначено поле Result в сообщении типа 
TMessage. Поле Result заполняется соответствующими данными. 

    Обработка сообщений, определенных пользователем 

    Иногда пользователю необходимо определить свои собственные сообщения. В 
Delphi для создаваемых пользователем сообщений^твояятся значения,_лежа.1щ\е; 
в^ интер_вале_от wm_User + 151 до $7FFF. Сообщения должны создаваться 
только~с~помощью жестко кодируемых констант, которые предназначены для 
внутреннего использования приложением, если, конечно, у вас нет абсолютной 
уверенности в том, что все другие существующие приложения Windows не будут 
использовать те же значения. Последнее утверждение звучит довольно 
категорично, но прием-передача сообщений организована так, что при отправлении 
сообщений "оконным" элементам управления последние реагируют на номера 
сообщений. При одновременной работе нескольких приложений нужно избегать 
ситуации, когда ваше приложение могло бы получить фальшивое сообщение или 
отослать такое сообщение другим приложениям. 
    Если вам нужно отсылать пользовательские сообщения между приложениями, 
используйте вызов функции API Register-WindowMessage, чтобы зарегистрировать 
свое пользовательское сообщение в операционной системе. Вызов API возвратит 
константу сообщения. К вызову API должны прибегнуть все приложения, работа 
которых связана с данным сообщением. Первое приложение (источник сообщения) 
зарегистрирует сообщение, а все остальные приложения (приемники сообщения) 
получат зарегистрированный номер сообщения. Функция RegisterWindowMessage 
определяется следующим образом. 

    UINT RegisterWindowMessage( LPCTSTR  Ipsz     //  Адрес   строки   
сообщения. ); 

    Функция RegisterWindowMessage принимает один параметр, который 
представляет собой строку с завершающим нулем. Эта строка содержит уникальное 
имя сообщения, которое вы хотите зарегистрировать. Если это сообщение еще не 
зарегистрировано в Windows, то состоится его регистрация, и Windows 
зарезервирует константу сообщения, лежащую в диапазоне от $СООО до $7FFF и 
возвратит ее приложению. Если сообщение уже зарегистрировано, Windows просто 
возвратит константу сообщения. После регистрации сообщение остается 
зарегистрированным до конца сеанса работы Windows. 

    Элементы управления данными 

    Элементы управления данными создаются аналогично другим пользовательским 
компонентам с одним исключением, связанным с использованием объекта связи с 
данными (data-link object). Этот объект обрабатывает взаимодействие между 
компонентом и классом TDataSource. Существует несколько типов элементов 
управления данными. Одни из них могут толькб'чйтать да 
    нные из набора данных, другие— читать и писать, а третьи способны 
управлять несколькими записями (например, компонент DBCtrlGrid). Базовым для 
всех объектов связи с данными является класс TDataLink. В табл. 17.4 
перечислены потомки класса TDataLink. 

    Таблица 17.4. Классы типа TDataLink 

    Класс	Назначение 

    TGr idDataLink	Используется для задания набора данных для компонента DBGrid 
    TMasterDataLink	Используется для задания главного набора в отношении главный/подчиненный 
    TFieldDataLink	Используется для задания набора данных для элементов 
управления данными, которые манипулируют только одним полем________________________________ 

    Глава 17. Сложные компоненты	387 


    Окончание табл. 17.4 

    Класс	Назначение 

    TNavDataLink	Используется для задания набора данных для компонента TDBNavigator 
    TDataSourceLink	Используется для задания набора данных для компонентов 
DBLookupComboBox и 
    DBLookupListBox 
    TListSourceLink	Используется для задания искомого набора данных для 
компонентов DBLookupComboBox и DBLookupListBox 
    TDBCtrlGridLink	Используется для задания набора данных для компонента 
DBCtrlGrid TQueryDataLink                     Используется для задания набора 
данных свойства DataSource  компонента TQuery 
    THTTPDataLink	Используется для задания набора данных в элементе управления 
работой в Internet TdataSetTableProducer 


    Важные свойства и методы класса TDataLink 

    Базовый класс TDataLink содержит несколько важных открытых (public) 
свойств и методов, наследуемых всеми дочерними объектами связи с данными. Их 
рассмотрению и посвящен этот раздел. 

    Метод UpdateRecord 

    Метод UpdateRecord используется для уведомления объекта связи с данными о 
том, что ему необходимо обновить значения своих данных в соответствующем 
элементе управления. И только вам как создателю компонента решать, когда у 
вашего компонента может возникнуть такая необходимость. Этот метод вызывается, 
когда элемент управления данными теряет фокус. 

    Свойство Active 

    Свойство Active предназначено только для чтения и используется для 
определения, активен ли набор данных, к которому присоединен объект связи с данными. 

    Свойство ActiveRecord 

    Свойство ActiveRecord используется для считывания или установки номера 
текущей записи в задействованном наборе данных. Под этим номером активной 
записи отнюдь не подразумевается номер текущей записи в базе данных. Он 
применяется только дл?_бу^[еризации набора данных (например, в компоненте 
TDBGrid в рабочий набор данных входит набор видимых строк, и свойство 
ActiveRecord определяет строку, на которой расположена текущая запись). 

    Свойство BufferCount 

    Свойство BufferCount используется для считывания или установки числа 
буферизируемых записей. Например, в компоненте TDBGrid свойство BufferCount 
устанавливается для задания количества одновременно отображаемых строк в 
элементе управления. Обычно оно используется в сочетании со свойством 
RecordCount для получения информации о количестве доступных записей по 
сравнению с реальным количеством записей. 

    Свойство DataSet 

    Свойство DataSet возвращает ссылку на компонент TDataSet, к которому 
привязан объект связи с данными через свойство DataSource. 

    Свойство DataSource 

    Свойство DataSource возвращает ссылку на компонент TDataSource, к которому 
привязан объект связи с данными. 

    Свойство DataSourceFixed 

    Свойство DataSourceFixed определяет, можно ли изменить свойство DataSource. 

    Свойство Editing 

    Свойство Editing определяет, находится ли свойство DataSource в режиме 
редактирования (Edit). 

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


    Свойство Readonly 

    Свойство Readonly определяет, можно ли перевести объект связи с данными в 
режим редактирования. 

    Свойство RecordCount 

    Свойство RecordCount содержит количество записей, которые буферизируются в 
текущий момент объектом связи с данными. 

    Важные свойства и методы класса TFieldDataLink 

    Класс TFieldDataLink принадлежит к наиболее популярным объектам связи с 
данными. Он является прямым потомком класса TDataLink и определен в файле 
DBCTRLS.PAS. В этом разделе рассматриваются некоторые из самых значительных 
открытых свойств и методов класса TFieldDataLink. 

    Метод Edit 

    Функция Edit используется для перевода поля данных в режим редактирования. 
В случае успешного завершения перевода функция возвращает значение true, 
означающее, что данное поле находится (либо уже находилось) в режиме Edit. 
Если же набор данных предназначен только для чтения или не содержит допустимых 
данных, функция Edit возвращает значение false. 

    Метод Modified 

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

    Метод Reset 

    Метод Reset заставляет объект связи с данными обновидъ_свои_значения из 
компонента TDataSource. При этом все сделанные в процессе работы изменения 
будут утрачены. 

    Свойство CanModify 

    Свойство CanModify определяет, может ли объект связи с данными 
модифицировать значения своих данных. Это свойство возвращает значение false, 
если данный объект связи с данными или набор данных находится в режиме только 
для чтения. 

    Свойство Control 

    Свойство Control привязывает объект связи с данными к элементу управления 
данными в форме. Это свойство используется Delphi для передачи фокуса элементу 
управления данными, когда в программе встречается ошибка, относящаяся к полю, 
с которым связан элемент управления данными. 

    Свойство Editing 

    Свойство Editing определяет, находится ли свойство DataSource в режиме редактирования. 

    Свойство Field 

    Свойство Field предназначено только для чтения и содержит ссылку на 
компонент TField, к которому привязан объект связи с данными. 

    Свойство FieldName 

    Свойство FieldName используется для считывания или установки имени поля, к 
которому привязан объект связи с данными. 

    Событие OnActiveChange 

    Событие OnActiveChange происходит в случае, когда изменяется свойство 
Active набора данных, к которому привязан объект связи с данными. 

    Глава 17. Сложные компоненты	389 


    Событие OnDataChange 

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

    Событие OnEditingChange 

    Событие OnEditingChange происходит в случае, когда свойство DataSource 
изменяет свое состояние редактирования. Оно может использоваться вашим 
элементом управления для изменения внешнего представления данных. 

    Событие OnUpdateData 

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

    Создание элемента управления данными 

    Процесс создания элемента управления данными предусматривает выполнение 
следующих пяти этапов. 
    1.   Создание пользовательского компонента. 
    2.   Создание объекта класса TDataLink как члена пользовательского компонента. 
    3.   Создание публикуемых (published) свойств DataField и DataSource. 
    4.   Создание обработчиков событий объекта связи с данными OnDataChange и OnUpdateData. 
    5.   Создание обработчика сообщения cm_GetDataLink. 
    Рассмотрим кнопочный компонент, который заменяет свой заголовок значением 
из поля базы данных. Если в поле содержатся данные (или если оно не является 
пустым), кнопка будет активна, в противном случае — заблокирована. В 
приведенном ниже примере показано, как переопределить методы Create и Destroy 
компонента TButton, чтобы создать экземпляр и освободить объект связи с 
данными. Кроме того, вы увидите, как создаются методы чтения и записи для 
публикуемых свойств DataField и DataSource. И, наконец, здесь демонстрируется 
создание метода DataChange в качестве обработчика события OnDataChange. 
Листинг 17.1 поможет вам получить представление о том, как должен выглядеть 
этот компонент. 

    !   ЛИСТИНГ 17.1. \UDELPHI3\CHP17\DBBTN.PAS. КоМПОНвНТ ДЛЯ рабОТЫ С 
ДанНЫМИ TButton 

    unit DBBtn; 
    interface 
    uses 
    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, 
StdCtrls, DB, DBCtrls; 
    type 
    TDBButton = class(TButton) private 
    { Скрытые объявления. } 
    FDataLink : TFieldDataLink; 
    function GetDataField: string; 
    function GetDataSource: TDataSource; 
    procedure SetDataField(const Value: string); 
    procedure SetDataSource(Value: TDataSource); 
    procedure DataChange(Sender: TObject); protected 
    { Защищенные объявления. } public 
    { Открытые объявления. } 
    // He забудьте переопределить методы Create и Destroy! 
    constructor Create(AOwner: TComponent); override; 
    destructor Destroy; override; published 


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


    { Публикуемые объявления. } 
    property DataField: string read GetDataField write SetDataField; property 
DataSource: TDataSource read GetDataSource write SetDataSource; end; 
    procedure Register/implementation 
    constructor TDBButton.Create(AOwner: TComponent); begin 
    // Первым вызывается наследованный метол Create. 
    inherited Create(AOwner); 
    FDataLink := TFieldDataLink.Create(Self); 
    FDataLink.OnDataChange := DataChange; end; 
    destructor TDBButton.Destroy; begin 
    // Последним вызывается наследованный метод Destroy. 
    FDataLink.OnDataChange := nil; 
    FDataLink.Free; 
    inherited Destroy; end; 
    function TDBButton.GetDataField: string; begin 
    Result := FDataLink.FieldName; end; 
    function TDBButton.GetDataSource: TDataSource; begin 
    Result := FDataLink.DataSource; end; 
    procedure TDBButton.SetDataField(const Value: string); begin 
    FDataLink. FieldName := Valuer-end; 
    procedure TDBButton.SetDataSource(Value: TDataSource); begin 
    FDataLink.DataSource := Value; end; 
    procedure TDBButton.DataChange(Sender: TObject); begin 
    if FDataLink.DataSource.State = dsBrowse then if FDataLink.Field.AsString 
<> '' then begin Caption := FDataLink.Field.AsString; Enabled := true; end 
else begin 
    Caption := '(Empty)'; Enabled := false; end; end; 
    procedure Register; begin 
    RegisterComponents('UD3', [TDBButton]); end; 
    end. 


    Глава 17. Сложные компоненты	391 


    Г Совет 1 
    Если вы создаете элемент управления данными, который может редактировать 
значения поля данных, вам придется обрабатывать событие OnUpdateData. 
    Сообщение cm_GetDataLink является внутренним сообщением компонента, 
которое возвращает экземпляр объекта связи с данными. Ссылка на объект связи с 
данными необходима только в том случае, если ваш элемент управления будет 
использоваться в компоненте DBCtrlGrid (не забудьте также установить флаг 
csReplicatable в свойстве ControlStyle). 

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

    Редакторы свойств во время разработки формируют интерфейс между свойствами 
вашего компонента и инспектором объектов. При размещении в форме вашего 
компонента Delphi автоматически создает редактор свойств для каждого 
публикуемого свойства компонента. Когда пользователь компонента щёлкает на 
каком-нибудь его свойстве в окне инспектора объектов, Delphi автоматически 
активизирует редактор для выбранного свойства. С целью сохранения основного 
качества Delphi как системы быстрой разработки приложений редакторы свойств 
должны быть интуитивно понятными и простыми в применении. Вы уже видели в 
действии некоторые стандартные редакторы свойств Delphi в предыдущей главе, 
в которой рассматривались типы свойств. Редактор свойств может быть таким же 
простым, как строка ввода или комбинированный список, но может выглядеть и 
как отдельное диалоговое окно. 
    Чтобы создать редактор свойств, необходимо выполнить следующие действия. 
    1.	Выбрать правильного предка. 
    2.	Переопределить необходимые методы. 
    3.	Зарегистрировать редактора свойств. 

    Выбор правильного предка 

    Чтобы создать своего собственного редактора свойств, прежде всего следует 
ознакомиться с доступными в Delphi стандартными редакторами свойств, 
большинство из которых объявлено в файле DSGNINTF. PAS, расположенном в 
каталоге \SOURCE\TOOLSAPI. 
    В табл. 17.5 перечислены базовые стандартные редакторы свойств, 
используемые в Delphi. С целью удовлетворения специфических потребностей 
различных свойств некоторые из редакторов свойств наследованы от классов 
родителей. Например, свойство Boolean имеет перечислимый тип, но вместо 
использования редактора TEnumProperty Delphi использует редактор 
TBoolProperty, который был наследован от редактора TEnumProperty и 
модифицирован для специфических нужд свойства Boolean. 

    j Таблица 17.5. Стандартные редакторы свойств Delphi	; 

    Класс	Тип свойства 

    TPropertyEditor         Базовый редактор свойств для всех редакторов свойств 
    TIntegerProperty        Все целочисленные свойства и диапазоны (Byte, 
Word, Integer, Longlnt, 1..., 20 и т.д.) 
    TCharProperty	Все свойства и диапазоны типа Char (Char, 'A'..., 'Z' и т.д.) 
    TEnumProperty_____Все перечислимые свойства________________________________________ 
    TFloatProperty	Все свойства вещественного типа (Float, Single, Double  и т.д.) 
    TStringProperty         Все строки и подтипы строк (String, ShortString, 
String [20], AnsiString и т.д.) 
    TSetProperty	Все свойства типа "множество" 
    TClassProperty____Все свойства типа "объект"_____________________________ 
    TMethodProperty         Все свойства типа "метод" 
    TcomponentProperty    Все свойства типа TComponent 


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


    Переопределение необходимых методов 

    Все_|>едакторы свойств являются потомками класса TPropertyEditpr и имеют 
?бщие виртуальные методы, которые были переопределены дляГсоздания редакторов 
свойств, учитывающих уникальные характеристики каждого типа свойства. Методы 
GetName и GetValue вызываются для получения имени свойства и его строкового 
значения соответственно. Метод SetValue используется в случае, когда 
пользователь изменяет значение свойства. Метод Edit вызывается, когда 
пользователь дважды щелкает на свойстве или щелкает на кнопке с многоточием, а 
метод GetValues (не путайте с методом GetValue) используется, когда 
пользователь раскрывает комбинированный список. Метод GetProp-erties будет 
вызван, когда пользователь захочет получить вложенные свойства некоторого свойства. 
    Теперь рассмотрим методы редактора свойств, которые можно и нужно 
переопределить, чтобы учесть специфические нужды свойства вашего компонента. 

    Метод Activate 

    Метод Activate вызывается каждый раз, когда свойство выбирается в окне 
инспектора объектов. С его помощью можно выполнить специальную инициализацию, 
которая должна происходить каждый раз, когда свойство становится выбранным, 
или перевести компонент в определенное состояние, пока данное свойство 
остается активным. Если вы предполагаете, что ваше свойство будет содержать 
флаги paMultiSelect и paSubProperties метода GetAttributes, Delphi категорически 
потребует, чтобы они были действительны до вызова метода Activate (они должны 
быть установлены в конструкторе Create). 

    procedure  Activate;   virtual; 


    Метод AIIEqual 

    Метод AIIEqual вызывается, когда пользователь выбирает больше одного 
компонента формы, и только в том случае, когда метод GetAttributes содержит 
флаг paMultiSelect. Если метод AIIEqual возвращает значение True, вызывается 
метод GetValue, в противном случае свойство отображает пустое значение. Этот 
метод позволяет определить, совпадает ли значение вашего свойства со 
значениями других компонентов множественного выбора. 

    function AIIEqual:   Boolean;   virtual; Например, рассмотрите функцию 
AIIEqual редактора свойства String. 
    function  TStringProperty.AIIEqual:   Boolean; var	"——~~   *—* I:   
Integer; V:   string; begin 
    //  Результат  по умолчанию устанавливается равным False. 
    Result   :=   False; 
    //  Если  выбрано  более   одного   компонента. 
    if  PropCount  >  1  then 
    begin 
    // Получение строкового значения этого свойства. V := GetStrValue; 
    -> for I := 1 to PropCount - 1 do 
    t,   // Цикл по всем компонентам с проверкой их строковых значений. t   11 
Если однсГиз значений~"не совпадает, выполняется выход из метода. 
    If GetStrValueAt(I) <> V then Expend; 
    // Если цикл отработал до конца, значит, все значения равны. Result := 
True;	"'  •-—•——-end; 


    Метод Edit 

    Вызов метода Edit происходит, когда пользователь дважды щелкает на 
свойстве или щелкает на кнопке с многоточием (...) в окне инспектора объектов. 
Обычно метод Edit активизирует диалоговое окно определенного вида для 
редактирования конкретного свойства (например, диалоговое окно Font, Color или 
Picture). По умолчанию метод Edit класса TPropertyEditor заменяет_сврйство 
следующим значением в списке GetValues. Если метод GetValues возвращает пустой 
результат, метод Edit ничего не выполняет. "" 
    procedure  Edit;   virtual; 


    Глава 17. Сложные компоненты	393 

    Рассмотрим, например, метод Edit редактора свойства Font. Обратите 
внимание на то, что диалоговое окно Font отображается внутри блока try. . 
.finally и освобождается в разделе finally. Это пример_хорошего стиля 
программирования, которого вам следовало бы придерживаться в своем редакторе 
свойств/

    procedure TFontProperty.Edit; var 
    FontDialog: TFontDialog; begin 
    // Реализация диалога Font. 
    FontDialog := TFontDialog.Create(Application); 
    try 
    // Установка выбора шрифта в диалоговом окне равным текущему значению свойства 
    FontDialog.Font := TFont(GetOrdValue); 
    FontDialog.HelpContext := hcDFontEditor; 
    FontDialog.Options := FontDialog.Options + [fdShowHelp, fdForceFontExist]; 
    if FontDialog.Execute then 
    // Если пользователь выберет шрифт, измените свойство Font. 
    SetOrdValue(Longint(FontDialog.Font)); ' finalTy""~" " 
    // Освобождение ДИЗЛОРЗ Font. 
    FontDialog.Free; end; 
    end; 


    Метод GetAttributes 

    Метод GetAttributes возвращает множество TPropertyAttributes, которое 
определяет характеристики редактора свойств. Эти признаки атрибутов имеют 
большре^влияниена внешнее представление вашего свойства в окне инспектора 
объектов. Именно от них зависит, будет ли ваше свойство представлено 
раскрывающимся списком, будут ли элементы этого списка отсортированы, можно ли 
отобразить в качестве редактора свойства диалоговое окно, можно ли отобразить 
вложенные свойства, относится ли ваше свойство к свойствам с множественным 
выбором и т.д. 

    function  GetAttributes:   TPropertyAttributes;   virtual; 

    Класс TPropertyAttributes объявлен как множество значений, перечисленных в 
табл. 17.6. Возможно, вам захочется Тте*р~е"определить метод GetAttributes для 
вашего редактора свойств, чтобы добавить или удалить флаги стиля из 
родительского класса редакторов свойств. Атрибуты редактора свойств можно 
изменять в любое время (и даже в процессе выполнения приложения). Исключение 
составляют лишь флаги paMultiSelect и paSubProperties. 

    • Таблица 17.6. Множество значений класса TPropertyAttributes 

    Флаг__________Значение____________________________________________ 

    paValueList	Означает, что редактор свойства может возвращать перечислимый 
список значений для данного свойства. Вызывает появление в окне инспектора 
объектов кнопки раскрывающегося списка        
    paSubProperties         Означает, что редактор свойства имеет вложенные 
свойства, которые будут отображены в окне инспектора объектов в отдельной 
форме под главным свойством 
    paDialog	Означает, что редактор свойства активизирует диалоговое окно 
редактирования, когда вызывается метод Edit. Вызывает появление в окне 
инспектора кнопки с многоточием 
    paMultiSelect	Означает, что редактор свойства может быть отображен, когда 
в форме выбрано несколько компонентов __ 
    paAutoUpdate	Означает, что нужно вызывать метод SetValue при каждом 
изменении в редакторе. При этом_не_тр_ебуется подтаерясдения изменений со 
стороны пользователя (путем нажатия клавиши <ТаЬ> длТпёрехода к другому 
свойству или закрытия диалога редактора свойства) 
    paSortList	Указывает инспектору объектов на сортировку элементов списка, 
возвращаемого методом 
    GetValues 
    paReadOnly	Означает, что свойство предназначено только для чтения 
    paRevertable   ____Означает, что свойство может вернуться к своему 
исходному значению____   ___ 

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

    I Совет 1 
    Флаг paValueList вызывает появление в окне инспектора объектов кнопки 
раскрывающегося списка, а флаг paDialog— кнопки с многоточием. Обе эти кнопки 
не могут быть отображены в окне инспектора объектов одновременно, поэтому, 
если вы установите флаги paValueList и paDialog в одном множестве атрибутов, 
DeTpfii использует флаг paValueList и проигнорирует флаг paDialog. 

    Метод GetEditLimit 

    Метод GetEditLimit возвращает максимальное количество символов, которые 
пользователь может ввести в инспектор объектов для данного свойства. По 
умолчанию метод GetEditLimit класса TPropertyEditor возвращает значение 255. 

    function  GetEditLimit:   Integer;   virtual; 


    Метод GetName 

    Метод GetName возвращает имя свойства, отображенного в окне инспектора 
объектов. По умолчанию это значение получается из RTTI для свойства. Кроме 
того, по умолчанию символы подчеркивания в имени свойства заменяются 
пробелами. Вряд ли вам часто придется переопределять этот метод. 

    function GetName:   string;   virtual; 

    Возможна ситуация, при которой переопределение этого метода необходимо. 
Речь идет об обработке множества вложенных свойств, когда было бы желательно, 
чтобы имя каждого вложенного свойства было именем элемента множества, а не 
именем свойства. Для примера рассмотрите метод GetName редактора свойств TSetElementProperty. 

    function  TSetElementProperty.GetName:   string; begin 
    Result   :=  GetEnumName(GetTypeData(GetPropType)Л.CompType",   FElement); end; 


    Метод GetProperties 

    Метод GetProperties вызывается, когда пользователь раскрывает в инспекторе 
объектов свойство, имеющее вложенные свойства. В этом случае метод 
GetAttributes должен содержать флаг paSubProperties. Метод GetProperties 
предоставляет список свойств и соответствующих редакторов свойств путем 
определения параметра Ргос для каждого вложенного свойства. 

    procedure GetProperties(Proc:   TGetPropEditProc);   virtual; 

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

    procedure  TClassProperty.GetProperties(Proc:   TGetPropEditProc); var 
    I:   Integer; 
    Components: TComponentList; begin 
    // Создание списка компонентов. Components := TComponentList.Create; try 
    for I := 0 to PropCount - 1 do 
    // Циклическое добавление всех свойств в список компонентов. Components.Add(TComponent(GetOrdValueAt(I))); 
    // Вызов метода GetComponentProperties со списком компонентов для // 
вывода типов свойств, имени разработчика и имени процедуры косвенного // 
вызова, которая была передана методу GetProperties. 
GetComponentProperties(Components, tkProperties, Designer, Proc); Finally 
    Components.Free; end; end; 


    Метод GetValue 

    Метод GetValue возвращает строковое значение редактируемого свойства. Он 
был переопределен во всех стандартных редакторах свойств для получения 
действительной строки, представляющей значение свойства. 

    function GetValue:   string;   virtual; 


    Глава 17. Сложные компоненты	395 

    Рассмотрим, например, как используется функция GetValue в редакторе 
свойств TStringProperty. По умолчанию метод GetValue возвращает " (unknown) ". 

    function TStringProperty.GetValue:   string; begin 
    Result   :=  GetStrValue; end; 


    Метод GetValues 

    Метод GetValues используется для создания списка значений, отображаемых в 
окне инспектора объектов для свойства, на кнопке комбинированного списка 
которого был выполнен щелчок. Этому методу передается процедура косвенного 
вызова, которая принимает один параметр типа string. Процедура косвенного 
вызова Ргос должна вызываться для каждого значения свойства. Метод GetValues 
вызывается только в том случае, если в методе GetAttributes установлен ([wiarpaValueList. 

    TGetStrProc = procedure(const S: string) of object; procedure 
GetValues(Proc: TGetStrProc); virtual; 
    Для примера рассмотрим метод GetValues редактора свойства TBoolProperty. 
    procedure TBoolProperty.GetValues(Proc: TGetStrProc); begin 
    // Вызов процедуры Proc для каждого значения свойства. 
    Proc('False'); 
    Proc('True'); end; 


    Метод Initialize 

    Метод Initialize вызывается после создания редактора свойств, но перед его 
реальным использованием. Он позволяет выполнить любую инициализацию, котогзую 
нельзя было сделать в конструкторе Create. В отличие от метода Activate, 
данный метод вызывается только один раз. Если какое-либо свойство используется 
впервые, метод Initialize вызывается до метода Activate. По умолчанию метод 
Initialize класса TPropertyEditor возвращает пустое значение. 

    procedure Initialize; virtual; 


    Метод SetValue 

    Метод SetValue используется для присвоения свойству значения. Этот метод 
принимает новое значение свойства как строку и должен преобразовать ее в тип 
данных, соответствующий данному свойству. Этот метод переопределяется водсех 
стандартных редакторах свойств, чтобы можно было обработать все используемые в 
них типы данных. 

    procedure  SetValue(const  Value:   string);   virtual; 

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

    procedure TBoolProperty.SetValue  SetValue(const Value:   string); var 
    I:   Integer/begin 
    if CompareText(Value,    'False')   =  0  then 
    I   :=  0 else  if  CompareText(Value,    'True')   =  0  then 
    I   :=  -1 else 
    I := StrToInt(Value) ; SetOrdValue (I); end; 


    Другие классы и методы класса TPropertyEditor 

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


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


    Свойство Designer 

    Свойство Designer содержит объект проектировщика форм, который 
используется для редактирования текущей формы. Это свойство передается как 
параметр методу GetComponentProperties. 

    Свойство PrivateDirectory 

    Свойство PrivateDirectory задает каталог, в который редактор свойств может 
записывать файлы. Оно досталось в наследство от Delphi 1.0, когда .было 
обычной практикой использовать INI-файлы для хранения скрытой информации, 
предназначенной для редактора свойств.'Сёичас же дляi этргр^ожна_испрльзрвать 
системный реестр.	"" * ' 

    Свойство PropCount 

    Свойство PropCount показывает, сколько компонентов было выбрано в 
инспекторе объектов. Это значение может превышать единицу, если в атрибутах 
данного свойства установлен флаг paMultiSelect. 

    Свойство Value 

    Свойство Value является стандартным свойством строкового типа, которое 
содержит значение редактируемого свойства. Его методами чтения и записи 
являются методы GetValue и SetValue соответственно. 

    Метод GetComponent 

    Метод GetComponent возвращает экземпляр компонента, заданный параметром 
Index, когда в окне инспектора объектов выбрано несколько компонентов. 
Редактор свойств может работать с несколькими компонентами только тогда, когда 
метод GetAttributes возвращает значение с установленным флагом paMultiSelect. 
Для определения количества выбранных компонентов используйте свойство PropCount. 

    function  GetComponent(Index:   Integer):   TPersistent; 


    Метод GetPropType 

    Метод GetPropType используется для получения информации RTTI для 
редактируемого в данный момент свойства. О том, как использовать эту 
информацию, вы узнаете, обратившись к главе 4, "Object Pascal: углубленное изложение"'. 

    function  GetPropType:   PTypelnfo; 


    Метод Modified 

    Метод Modified указывает на объект Designer (а следовательно, и на 
редактируемую в данный момент форму), свойство которого изменилось. Все методы 
SetXxxValue класса TPropertyEditor вызывают этот метод после того, как они 
изменят значение редактируемого свойства. Вам придется обращаться к этому 
методу только в случае, если вы измените значение свойства, не прибегая к 
вышеупомянутым методам SetXxxValue. 

    Методы Get и Set 

    Класс TPropertyEditor реализует методы для получения и установки значений 
данных, типы которых перечислены в табл. 17.7. Используйте эти методы при 
изменении значений свойства, поскольку после выполнения своих изменений они 
вызывают метод Modified. Если вы работаете с типом данных, для которого нет 
предопределенного метода Get или Set, не забудьте после выполнения изменений 
вызвать метод Modified. 

    Таблица 17.7. Методы Get и Set для класса TPropertyEditor 

    Тип данных_______________Метод Get_______________Метод Set_____________ 

    Float	GetFloatValue               SetFloatValue Method                     
GetMethodValue              SetMethodValue Ordinal                     
GetOrdValue                 SetOrdValue String                     
GetStrValue                 SetStrValue Variant                     
GetVarValue                 SetVarValue 


    Глава 17. Сложные компоненты	397 


    Регистрация редактора свойств 

    После создания редактора свойств вам останется лишь связать свойство 
компонента с вашим редактором. Регистрация редактора свойств выполняется с 
помощью IDE Delphi и процедуры RegisterPropertyEditor. Эта процедура принимает 
четыре параметра и определяется следующим образом. 

    procedure RegisterPropertyEditor(PropertyType: PTypelnfo; ComponentClass: 
TClass; const PropertyName: string; EditorClass: TPropertyEditorClass); 
    PropertyType 

    Это указатель на информацию RTTI для свойства, к которому применяется 
редактор свойств. Данный параметр получается путем выполнения операции 
приведения типа с помощью Type Info. Предположим, что у вас есть свойство типа 
ТМуРгорТуре, тогда вы должны использовать следующий синтаксис: 

    Typelnfo(ТМуРгорТуре) 


    ComponentClass 

    Это тип компонента, к которому применяется регистрируемый редактор 
свойств. Данный параметр может использоваться для ограничения редактора 
свойств заданным типом компонента. Если это значение равно njj., редактор 
свойств применяется ко всем свойствам первого параметра.

    PropertyName 

    Это имя свойства, к которому применяется данный редактор свойств. Параметр 
PropertyName может использоваться для ограничения редактора свойств 
определенным именем свойства. Если этот параметр равен пустой строке, редактор 
свойств применяется ко всем свойствам, указанным в первом параметре.	— 

    EditorClass 

    Это тип редактора заданного свойства. Delphi использует последний 
зарегистрированный редактор свойств, что значительно облегчает определение 
ваших собственных редакторов свойств для существующих свойств. 

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

    Редакторы компонентов во многом похожи на редакторов свойств, и это 
сходство состоит в том, что они предоставляют пользователю компонента способ 
изменения свойства своего компонента во время разработки. Но, в отличие от 
редакторов свойств, редактор.<«омпоне_нта позврдяет изменять более одного 
свойства сразу. На рис. 17.1 показан редактор для компбнен-"TifTDatabase. 
Обратите внимание на то, что большинство свойств, которые вы можете установить 
в окне инспектора объектов, отображены в этом редакторе компонента. Редактор 
компонента предоставляет простой интерфейс, который помогает пользователю 
увидеть компонент TDatabase "крупным планом" (вместо однотипных элементов 
инспектора объектов). 

    Рис. 17.1. Редактор компонента TDatabase 

    Редактор компонента может быть активизирован в редакторе форм с помощью 
двойного щелчка на компоненте или щелчка пра'вЖ"'кйоп"кои"мьТши на компоненте 
и выбора соответствующей команды из контекстного меню. Базовым классом для 
всех редакторов компонентов в Delphi является класс TComponentEditor. От него 
происходит класс TDe-faultEditor, который является стандартным редактором для 
всех компонентов. Из следующего раздела вы узнаете, как создать свой 
собственный редактор компонентов. 


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


    Класс TComponentEditor 

    Класс TComponentEditor является базовым классом для всех редакторов 
компонентов в Delphi. Он содержит четыре ключевых виртуальных метода, не 
считая конструктора Create (ладно, пусть будет пять). Этот базовый класс 
определен в файле DSGNINTF. PAS, и его объявление заслуживает вашего внимания. 

    TComponentEditor = class public 
    constructor Create(AComponent: TComponent; ADesigner: ^	TFormDesigner); virtual; 
    procedure Edit; virtual; 
    procedure ExecuteVerb(Index: integer); virtual; 
    function GetVerb(Index: integer): string; virtual; 
    function GetVerbCount: integer; virtual; 
    procedure Copy; virtual; 
    property Component: TComponent read FComponent; 
    property Designer: TFormDesigner read FDesigner; end; 

    Теперь рассмотрим каждый элемент в этом объявлении класса. 

    Конструктор Create 

    Редактор компонента создается, когда компонент выбран в проектировщике 
форм. Если вы выберете более одного компонента, Delphi создаст экземпляр 
редактора для каждого компонента. Метод Create принимает два параметра: первым 
является экземпляр компонента, выбранного для редактирования, а вторым — 
экземпляр объекта проектировщика. Если вы переопределите конструктор Create, 
обязательно вызовите наследованный метод Create. 

    Метод Edit 

    Метод Edit вызывается в случае, когда пользователь дважды щелкает на 
компоненте. При этом в классе TComponentEditor сравнивается значение функции 
GetVerbCount с нулем. Если оно больше нуля, вызывается процедура 
ExecuteVerb(0), тем самым активизируя первый определенный компонентом элемент 
контекстного меню. Класс TDef aultEditor переопределяет метод Edit, выполняет 
поиск среди публикуемых свойств компонентов события Оп-Create, OnChange или 
OnClick (повезет тому, кто будет найден первым) и вызывает метод редактора 
свойств для этого события. 

    Метод GetVerbCount 

    Метод GetVerbCount вызывается кaждJЬJЙJ^JiOJJЩJ[^pльзoвaтeль щелкает 
правой кнопкой мыши на компоненте. Метод возвращает элементы меню, которые 
нужно добавить к контекстному меню" редактора формы. Если вы хотите добавить 
элементы к контекстному меню, переопределите этот метод. По умолчанию это 
значение равно нулю. 

    Метод ExecuteVerb 

    Метод ExecuteVerb вызывается, когда пользователь выбирает из контекстного 
меню проектировщика форм определенный компонентом элемент. Емупередается 
параметр Index, минимальное значение которого равно нулю. Этот метод также 
назначен по умолчанию для метода Edit~KJiacca TComponentEditor. Если в 
контекстное меню проектировщика форм вы добавили новые элементы, то необходимо 
переопределить данный метод для обработки этих опций выбора. 

    Метод GetVerb 

    Метод GetVerb вызывается, когда пользователь щелкает правой кнопкой мыши 
на компоненте. Он возвращает строковое значение, которое необходимо добавить в 
контекстное меню в зависимости от параметра Index. Параметр Index предполагает 
массив с нулевым основанием, т.е. первый элемент определяется нулевым 
значением. Если в контекстное меню проектировщика форм вы хотите добавить 
новые элементы, переопределите этот метод. 

    Метод Сору 

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

    Глава 17. Сложные компоненты	399 


    Класс TDefaultEditor 

    Класс TDefaultEditor наследован от класса TComponentEditor. Он 
переопределяет метод Edit, а также вводит новый виртуальный метод 
EditProperty. Класс TDefaultEditor определяется следующим образом. 

    TDefaultEditor  =  class(TComponentEditor) protected 
    procedure EditProperty(PropertyEditor:   TPropertyEditor;   var Continue,  
 FreeEditor: Boolean);   virtual; public 
    procedure Edit;   override; end; 


    Метод Edit 

    Класс TDefaultEditor переопределяет метод Edit и выполняет цикл по всем 
публикуемым свойствам компонентов, вызывая метод EditProperty для каждого из 
них. Если находится искомое свойство, вызывается метод Edit. 

    Метод EditProperty 

    Метод EditProperty вызывается методом Edit для каждого публикуемого 
свойства компонента. По умолчанию метод EditProperty ищет событие OnCreate, 
OnChange или OnClick (по принципу первенства). Если обнаруживается совпадение, 
переменная экземпляра редактора свойства сохраняется в скрытой переменной, 
чтобы она могла быть вызвана методом Edit. 

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

    После создания редактора компонента необходимо связать компонент с 
редактором. Регистрация редактора компонентов выполняется с помощью IDE Delphi 
и процедуры RegisterComponentEditor. Эта процедура принимает два параметра и 
определяется следующим образом. 

    procedure  RegistuiComponentEditor(ComponentClass:   TComponentClass; 
ComponentEditor:   TComponentEditorClass); 

    Первым параметром является имя класса компонента, а вторым — имя класса 
вашего редактора компонента. 

    Пример редактора компонента 

    Чтобы увидеть редактор компонента в действии, рассмотрим компонент TEdit. 
Беглый просмотр исходного кода показывает, что для этого компонента нет 
зарегистрированного редактора компонента. Это значит, что при двойном щелчке 
на компоненте TEdit будет активизирован обработчик одного из событий 
(OnCreate, OnChange или OnClick), в зависимости от того, кто первым "попадется 
на глаза" обработчику Delphi. В данном случае Delphi находит событие OnChange. 
    Предположим, что вы разрабатываете приложение, которое использует много 
полей ввода, и, как следствие, вы часто выполняете проверку на возникновение 
события OnExit. Вам приходит в голову идея в духе традиций быстрой разработки 
приложений: было бы здорово, если бы двойной щелчок на компоненте TEdit 
создавал событие OnExit вместо события OnChange. Э то избавило бы вас от 
необходимости щелкать на компоненте TEdit в форме, на вкладке Events 
инспектора объектов и от двойного"щелчка на событии OnExit. В листинге 17.2 
показано, как построить редактор компонента, который создает событие OnExit 
после двойного щелчка и добавляет в контекстное меню для компонента TEdit 
(всплывающее после щелчка правой кнопкой мыши) события OnExit и OnChange. 

    { Листинг 17.2. \UDELPHI3\CHP17\TESTCD. PAS. Редактор компонента TEdit 

unit  TestCE;	" 
    interface 
    uses 
    Dsgnlntf, StdCtrls, SysUtils, Classes; 
    type 
    TMyEditEditor = class(TDefaultEditor) 


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


    private 
    FIndex : integer; public 
    constructor Create(AComponent: TComponent; 
    ADesigner: TFormDesigner); override; 
    function GetVerbCount: integer; override;      ^ 
    function GetVerb(Index: integer): string; override; 
    procedure ExecuteVerb(Index: integer); override; 
    procedure EditProperty(PropertyEditor: TPropertyEditor; var Continue, 
FreeEditor: Boolean); override; end; 
    procedure Register; implementation 
    constructor TMyEditEditor.Create(AComponent: TComponent; 
    ADesigner: TFormDesigner); begin 
    inherited Create(AComponent,ADesigner); 
    // Инициализация Findex. 
    FIndex := -1; end; 
    function TMyEditEditor.GetVerbCount: integer; begin 
    // Установка значения функции GetVerbCount равным 2 для двух элементов меню, 
    // которые мы будем добавлять. 
    result := 2; end; 
    function TMyEditEditor.GetVerb(Index: integer): string; begin 
    // Возвращение строки элемента меню, case Index of 
    0: result := 'O&nChange'; I: result := 'OnE&xit'; end; end; 
    procedure TMyEditEditor.ExecuteVerb(Index: integer); begin 
    // Заметьте, что двойной щелчок на элементе управления Edit 
    //не приведет в данную точку программы. 
    // ?оль,ко щелчок правой кнопкой вызове-Т. этот метод. 
    // Установите FIndex'равным iri'dex, а затем вызовите метод Edit. 
    FIndex := Index; 
    Edit; 
    // Снова установите переменную FIndex равной -1. 
    FIndex := -1; 
    // Вызов__метода Mp_dified объекта Designer для индикации того, 
    // что форма изменилась. 
    if Designer <> nil then 
    Designer.Modified; end; 
    procedure TMyEditEditor.EditProperty(PropertyEditor: TPropertyEditor; 
    var Continue, FreeEditor: Boolean); begin 
    case FIndex of 
    // Проверка значения FIndex на равенство опции меню, // всплывающего после 
щелчка правой кнопкой мыши. О : begin 
    if CompareText(PropertyEditor.GetName, 'OnChange') = 0 then begin 


    Глава 17. Сложные компоненты	401 

    PropertyEditor.Edit; Continue := false; end; end; 1 : begin 
    if CompareText(PropertyEditor.GetName, 'OnExit') = 0 then begin 
    PropertyEditor.Edit; Continue := false/end; end; else 
    // Обработка нормальной ситуации. 
    inherited EditProperty{PropertyEditor,Continue,FreeEditor); end; end; 
    procedure Register; begin 
    // Регистрация редактора TMyEditEditor для компонента Tedit. 
    RegisterComponentEditor(TEdit, TMyEditEditor); end; 
    end. 


    Резюме 

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

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

