1. Лекция №13-3 для дисциплин: «Прикладное программирование» и «Языки
программирования»
1
Лекция 13-3
Разработка компонент в C++Builder
Как уже было отмечено, среда С++Builder содержит большое количество
стандартных компонент, позволяющих упростить разработку приложений.
Программисты могут расширять функциональность системы благодаря
возможности создания собственных пользовательских компонент. Созданные
компоненты могут быть помещены в палитру компонент, для них могут быть
созданы редактор компонента, редакторы свойств, а также файл помощи. Таким
образом, дальнейшая работа с созданным компонентом ничем не отличается от
работы со стандартными компонентами. Созданные компоненты могут быть
объединены в пакеты для упрощения их установки. Существует большое
количество коммерческих и свободно распространяемых дополнительных пакетов
компонент. Кроме того, программисты могут сами создавать компоненты для
упрощения дальнейшей разработки приложений. Рассмотрим процесс создания
компонент более подробно.
1. Процедура разработки компонента C++Builder
При разработке нового компонента необходимо наследовать его от какого-
либо стандартного класса в соответствии с принципами наследования ООП. Как
уже было отмечено, все компоненты должны быть наследованы как минимум от
класса TComponent. Процедура создания и установки компонентов в среде
С++Builder автоматизирована. Для автоматической генерации заготовки нового
компонента можно выбрать пункт главного меню Component | New Component.
Далее в появившемся окне список выбора “Ancestor Type” позволяет выбрать имя
класса, от которого будет наследован компонент. В поле “Class Name” указывается
2. Лекция №13-3 для дисциплин: «Прикладное программирование» и «Языки
программирования»
2
имя будущего класса. В соответствии с нотацией, принятой в среде С++Builder,
имена классов должны начинаться с заглавной буквы “T”. В поле “Palette Page”
указывается закладка, на которой будет располагаться компонент. В поле “Unit file
name” указывается полный путь и имя файла, который будет содержать новый
класс. После нажатия на кнопку “ОК” по указанному пути будет сгенерировано два
файла c расширениями *.cpp и *.h и именами, совпадающими с именем класса без
начальной буквы “T”. Кроме того, будет отображено окно редактирования этих
файлов. В самих файлах уже будет содержаться код для определения класса и
инсталляции компонента. Программист должен добавить в этот код все
необходимые функциональные возможности нового компонента. Допустим, мы
выбрали в поле “Ancestor Type” класс TComponent, в поле “Class Name” –
TOurComponent, в поле “Palette Page” – OurSample, тогда будет сгенерирован файл
OurComponent.h
#ifndef OurComponentH
#define OurComponentH
#include <SysUtils.hpp>
#include <Classes.hpp>
class PACKAGE TOurComponent : public TComponent
{private:
protected:
public:
__fastcall TOurComponent(TComponent* Owner);
__published:};
#endif
Вместе с заголовочным файлом будет создан файл OurComponent.cpp
#include <basepch.h>
#pragma hdrstop
#include "OurComponent.h"
#pragma package(smart_init)
static inline void ValidCtrCheck(TOurComponent *)
3. Лекция №13-3 для дисциплин: «Прикладное программирование» и «Языки
программирования»
3
{ new TOurComponent(NULL); }
__fastcall TOurComponent::TOurComponent(TComponent* Owner)
: TComponent(Owner){ }
namespace Ourcomponent
{ void __fastcall PACKAGE Register()
{TComponentClass classes[1] = {__classid(TOurComponent)};
RegisterComponents("OurSamples", classes, 0);}}
Как видно из приведенного примера создается заготовка для конструктора, а
также определяются две функции ValidCtrCheck и Register. Функция ValidCtrCheck
необходима для проверки наличия в создаваемом компоненте чистых виртуальных
функций. Данная функция будет вызвана при установке компонента. В ней просто
производится попытка создания экземпляра компонента, что в случае наличия
чистой виртуальной функции приведет к генерации ошибки и отмене инсталляции.
Функция Register регистрирует компонент в палитре компонентов. В первом
параметре указывается имя закладки в палитре компонент, второй - параметр-
массив указателей на метаклассы регистрируемых компонент, третий содержит
количество регистрируемых компонентов минус 1.
Для компонента может быть создана пиктограмма, которая будет
отображаться в палитре компонентов. Для этого необходимо использовать Image
Editor из пункта главного меню Tools. В этом редакторе необходимо выбрать пункт
File | New | Component Resource File (.dcr), а затем пункт Resource | New | Bitmap и
задать размер 24х24 пиксела. Необходимо переименовать созданный Bitmap,
назвав его так же, как и будущий класс компонента, но заглавными буквами. Для
нашего случая это будет имя TOURCOMPONENT. Далее можно нарисовать
необходимый рисунок, учитывая, что цвет левого нижнего квадрата будет
считаться прозрачным. Сам файл ресурса необходимо сохранить в том же каталоге,
что и файлы .cpp и .h, с тем же именем, но с расширением .dcr. Для нашего случая
это будет имя ourcomponent.dcr. После внесения изменений в файлы .cpp и .h
можно приступать к установке нового компонента. Для этого необходимо выбрать
4. Лекция №13-3 для дисциплин: «Прикладное программирование» и «Языки
программирования»
4
пункт главного меню Component | Install Component и в появившемся окне в поле
“Unit file name” указать имя .cpp файла, содержащего исходный код компонента. В
поле “Packege File Name” выбрать пакет, в который будет помещен компонент. По
умолчанию пользовательские компоненты помещаются в пакет “Borland User
Components”. После нажатия кнопки “Ok” необходимо согласиться с
перекомпиляцией пакета и сохранить изменения в пакете. После этого новый
компонент появится в палитре компонентов той закладки, которая была указана
при регистрации компонента. Для удаления/добавления или перекомпиляции
модулей в пакетах можно также использовать пункт Component | Install Package. В
появившемся окне необходимо выбрать нужный пакет и нажать кнопку «Edit».
2. Определение свойств и событий компонента
В рассмотренном выше примере была создана всего лишь заготовка
компонента. В реальной ситуации программист должен реализовать некоторый
код, обеспечивающий функциональность нового компонента. Принцип работы с
новым компонентом ничем не отличается от обычной процедуры наследования
ООП. Таким образом, программист может определять новые компонентные
данные или функции, а также использовать наследуемые. Новым в среде
С++Builder является возможность использования свойств и событий. Как уже
отмечалось, свойства предоставляют дополнительный интерфейс для доступа к
защищенным данным класса. События позволяют определить реакцию на
изменение состояния компонента. Свойства и события могут быть определены в
одной из стандартных областей видимости класса (private, protected, public), в этом
случае их доступность определяется также как и для данных класса. Однако в
среде С++Builder введена еще одна директива для области видимости __published.
Если свойство или событие будет размещено в этой секции, то оно будет доступно
в инспекторе объектов на этапе проектирования. Для описания свойств и событий
5. Лекция №13-3 для дисциплин: «Прикладное программирование» и «Языки
программирования»
5
используется ключевое слово __property. Описание свойств и событий в общем
случае выглядит следующим образом
__property <type> <id> { read = <data/function id>, write = <data/function id>}
Кроме атрибутов read и write для свойств можно устанавливать
дополнительные атрибуты, которые также называются спецификаторами свойств,
однако их рассмотрение выходит за рамки данного учебного пособия. <id>
определяет имя свойства и задается по стандартным правилам для
идентификаторов С++. Согласно нотации среды С++Builder имена событий
начинаются с букв “On”. Описание свойств и событий отличаются только типом
<type>. События должны иметь специальный тип «событие», указатель на
функцию, описанный с ключевым словом __closure. Видя такой тип, инспектор
объектов размещает события на закладке Events. Свойства могут иметь любой
стандартный или производный тип, доступный для переменных. При
использовании классов VCL необходимо использовать тип указатель на класс,
поскольку статические экземпляры классов VCL не допустимы. Кроме того, в этом
случае необходимо создать экземпляр класса в конструкторе и уничтожить его в
деструкторе. Присваивание переменных, содержащих указатели на класс,
необходимо осуществлять с помощью метода Assign. Свойства на самом деле не
хранят никаких данных и являются как бы интерфейсом для доступа к внутренним
данным компонента. Где на самом деле будут храниться данные, и как будет
происходить доступ к ним, определяется с помощью спецификаторов read и write.
И read, и write могут указывать и на метод, и на переменную (обычно объявленную
в private секции класса). Чаще всего write указывает на функцию, а read просто на
переменную. В самой функции также происходит запись значения в переменную,
однако использование функции позволяет реализовать дополнительные
возможности, например, проверку допустимых значений при записи в свойство
или изменение значения других свойств и переменных в зависимости от данного.
6. Лекция №13-3 для дисциплин: «Прикладное программирование» и «Языки
программирования»
6
Имя свойства может не иметь ничего общего с именем переменной, которую оно
модифицирует. Однако согласно нотации среды С++Builder имя связанной со
свойством должно быть таким же, как и имя свойства, но с префиксом F. Если write
отсутствует, то свойство будет только для чтения (но значение переменной, на
которую оно указывает, может быть изменено какими-либо другими способами,
например из других методов класса). Как уже отмечалось, события позволяют
определить пользователю компонента собственный обработчик событий.
Обработчик события представляет собой обычную функцию класса (обычно
принадлежащую классу формы), в которую передается указатель на объект,
который сгенерировал события и некоторые дополнительные данные,
специфичные для каждого типа события. Если произошло какое-либо событие, то
компонент просто вызывает назначенный этому событию обработчик, таким
образом позволяя пользователю компонента выполнить необходимые действия при
возникновении этого события. Рассмотрим пример определения свойств и
событий.
Заголовочный .h файл
class PACKAGE TOurComponent : public TComponent
{private:
int FMyInteger; char FMyChar;
Graphics::TFont *FMyFont;
TNotifyEvent FOnMyError;
void __fastcall SetMyInteger(const int Value);
__published:
__property int MyInteger = {read=FMyInteger, write=SetMyInteger};
__property char MyChar = {read=FMyChar, write=FMyChar};
7. Лекция №13-3 для дисциплин: «Прикладное программирование» и «Языки
программирования»
7
__property Graphics::TFont* MyFont = {read=FMyFont, write=SetMyFont};
__property TNotifyEvent OnMyError = {read=FOnMyError, write=FOnMyError};}
Исходный .cpp файл
__fastcall TOurComponent::TOurComponent(TComponent* Owner)
: TComponent(Owner)
{FMyInteger=10;FMyChar='s';
FMyFont= new Graphics::TFont();}
__fastcall TOurComponent::~TOurComponent()
{delete FMyFont;}
void __fastcall TOurComponent::SetMyFont(const Graphics::TFont *Value)
{ FMyFont->Assign((TPersistent*)Value); }
void __fastcall TOurComponent::SetMyInteger(const int Value)
{if (Value%2) FMyInteger = Value;
else if (!ComponentState.Contains(csDesigning))
if (FOnMyError) FOnMyError (this);}
В данном примере определяется три свойства: MyChar, MyFont и MyInteger.
Свойство MyChar обеспечивает доступ к защищенной переменной FMyChar без
каких-либо дополнительных возможностей. Свойство MyFont обеспечивает доступ
к защищенной переменной FMyFont, являющейся указателем на класс типа TFont.
Еще раз отметим, что в этом случае объект должен быть создан в конструкторе и
уничтожен в деструкторе. Кроме того, как видно из функции SetMyFont, значение
переменной FMyFont присваивается с помощью метода Assign. Данный метод
обеспечит корректное копирование всех внутренних данных и свойств класса
TFont. При выполнении же присваивания вида FMyFont=Value произойдет
8. Лекция №13-3 для дисциплин: «Прикладное программирование» и «Языки
программирования»
8
присваивание указателей. В результате мы потерям доступ к блоку памяти,
выделенному ранее под FMyFont, и при удалении объекта Value, указатель
FMyFont станет некорректным. Свойство MyInteger обеспечивает доступ к
защищенной переменной FMyInteger. При чтении значения свойства происходит
просто чтение этой переменной, при записи же происходит проверка: для записи в
переменную доступны лишь четные значения. При попытке же записи нечетного
значения будет сгенерировано событие FOnMyError, причем генерация события
происходит, только если компонент находится на этапе выполнения, а не на этапе
проектирования. Для проверки на каком этапе находится компонент, можно
использовать свойство ComponentState: если это свойство содержит флаг
csDesigning, то компонент находится на этапе проектирования. Далее происходит
проверка, назначил ли программист, использующий наш компонент, обработчик
события OnMyError. Поскольку переменная FOnMyError содержит адрес функции
обработчика, то, если программист не назначил обработчик, в ней будет
содержаться значение NULL. В противном случае происходит вызов назначенной
функции обработчика, при этом в качестве параметра передается указатель this, т.е.
указатель на текущий экземпляр класса, для которого была вызвана функция
SetMyInteger. В обработчике этот параметр будет доступен как указатель Sender,
что позволит программисту определить в обработчике, какой именно экземпляр
класса вызвал данное событие.
Вывод
Среда С++Builder предоставляет удобные средства для создания
пользовательских компонент. Работа с созданными компонентами ничем не
отличается от работы со стандартными, и они позволяют существенно расширить
функциональность среды С++Builder.