Bad_typeid
Если typeid не может определить тип объекта, выбрасывается исключение bad_typeid. Это происходит, например, при попытке определить тип, на который ссылается нулевой указатель:
//////////////////////////////////////////////
// BadType.cpp: Исключение bad_typeid.
//
#include <iostream.h> #include <typeinfo.h>
#pragma hdrstop
#include <condefs.h>
class Base { public:
virtual ~Base() {} };
class Derived: public Base {};
int main() {
try {
Base *bp = NULL;
cout<< "Typeid of bp: " << typeid(*bp).name() << endl;
} catch(bad_typeid) {
cout << "Bad typeid caught!"<< endl;
} return 0;
}
Const_cast
Операция cons't_cast имеет ту же форму, что и предыдущая:
соnst_сonst<целевой_тип>(аргумент)
Целевой тип, возвращаемый такой операцией, может быть любым и должен отличаться от типа аргумента только модификаторами const и volatile.
Вот пример инициализации динамической константной строки:
/////////////////////////////////////////////
// ConstCast.срр: Подавление модификатора const.
//
#include <string.h>
#include <iostream.h>
#pragma hdrstop
#include <condefs.h>
int main ()
cons с char *ip;
ip = new char[20];
strcpy(const_cast<char*>(ip), "New const string,");
cout << ip << end1; delete [] ip;
return 0;
Dynamic_cast
Операция динамического приведения типа
dynamic сast<целевой_тип>(аргумент)
не имеет аналогов среди операций, выполняемых .с применением “классической” нотации приведения. Операция и проверка ее корректности при известных условиях происходит во время выполнения программы.
Динамическое приведение типа опирается на механизм RTTI, поэтому необходимо установить флажок Enable RTTI в диалоге Project Options (страница C++). Если этот флажок сброшен, программа компилироваться не будет.
Целевой тип операции должен быть типом указателя, ссылки или void*. Если целевой тип — тип указателя, то аргументом должен быть указатель на объект класса; если целевой тип — ссылка, то аргумент должен также быть соответствующей ссылкой. Если целевым типом является void*, то аргумент также должен быть указателем, а результатом операции будет указатель, с помощью которого можно обратиться к любому элементу “самого производного” класса иерархии, который сам не может быть базовым ни для какого другого класса.
Приведение от производного класса к базовому разрешается на этапе компиляции. Преобразования от базового класса к производному, либо перекрестные преобразования на некоторой иерархии, происходят во время выполнения программы. Операция нисходящего приведения типа допустима только в случае, если базовый класс (класс аргумента) является полиморфным.
При попытке произвести некорректное преобразование операция возвращает нуль, если целевой_тип — указатель. Если ссылка, операция выбрасывает исключение типа bad_cast.
С помощью операции dynamic_cast можно выполнять нисходящее приведение виртуального базового класса, что невозможно сделать с применением обычной нотации приведений, при условии, что базовый класс является полиморфным и преобразование разрешается однозначно.
Ниже показаны две программы, демонстрирующие динамическое приведение типа. В первой из них для контроля успешности преобразований используются исключения, во второй — проверка на равенство результата нулю.
Листинг 13.3. Нисходящее и перекрестное приведение типа
//////////////////////////////////////////////////
// Dynamic.срр: Динамическое приведение типа.
//
#include <iostream.h>
#include <typeinfo.h>
#pragma hdrstop
#include <condefs.h>
class Bl { // Полиморфный базовый класс.
public:
virtual ~B1() {} } ;
class B2 {}; class D:
public Bl,
public B2 {}; // Производный класс.
int main () {
D d;
Bl bl;
Bl &rbl = d;
try { //
// Нисходящее приведение. //
cout <<" Downcasting from Bl; object ID: "
<< typeid(rbl).name() << endl;
D &rd = dynamic_cast<DS>(rbl);
cout << "OK..."<< endl;
//
// Перекрестное приведение.
//
cout << "Cross-castind from Bl to B2; object ID: "
<< typeid(rbl).name() << endl;
B2 &rb2 = dynamic_cast<B2&> (rbl);
cout << "OK..." << endl;
//
// Попытка недопустимого приведения.
//
Bl &rrbl = bl;
cout << "Try invalid cross-casting; object ID:"
<< typeid(rrbl).name() << endl;
B2 &rrb2 = dynamic_ca3t<B2&>(rrbl);
cout << "OK..." << endl;
} catch(bad_cast) {
cout << "Cast failed." << endl;
} catch(bad_typeid) {
cout << "Typeid failed." << end1;
}
return 0;
}
Вывод программы:
Downcasbing from Bl; object. ID: D
OK. . .
Cross-castind from Bl to B2; object ID: D
OK. . .
Try invalid cross-casting; object ID: Bl
Cast failed.
Перекрестное приведение типа, т. е. такое, при котором классы хотя и относятся к одной иерархии, но находятся на разных ее “ветвях”, допускается и для обычных приведений, но в этом случае вряд ли вы получите сколько-нибудь осмысленный результат.
Действие операции dynamic_cast при перекрестном приведении типов можно представить следующим образом. Сначала ищется наивысший класс иерархии, являющий производным сразу от обоих классов, участвующих в преобразовании. Указатель приводится к этому классу (нисходящее преобразование). После этого он возводится до класса результата (преобразование от производного класса к базовому, что можно сделать всегда).
Листинг 13.4. Приведение от виртуального базового класса
/////////////////////////////////////////////////
// VBaseCast.срр: Приведение виртуального базового класса.
//
#include <iostream.h>
#include <typeinfo.h>
#pragma hdrstop
#include <condefs.h>
class VBase { // Виртуальный базовый класс. public:
virtual ~VBase() {} 1;
class Bl: public virtual VBase {};
class B2: public virtual VBase {};
class D: public Bl, public B2 {}; // Производный класс.
//
// Вспомогательная функция, аргументом которой
// может' быть любой класс данной иерархии.
//
void Report(VBase *pvb)
{
try {
cout << " ... Object ID: "
<< typeid (*pvb).name() << endl;
}
catch(bad_typeid) {
cout << " ...'Bad typeid in Report()."<< endl;
}
xin-. ilia in ( ) {
D d;
Bl bl;
{
Base *pvb = &d;
cout << "Original class: " << typeid(*pvb).name();
//не корректное приведение - pvb ссылается на объект //Производного класса.
//
Report (dynamic_cast<D*> (pvb) ) ;
pvb = o.al;
cout<< "Original class: " << typeid(*pvb).name();
//
// Следующее приведение не удается, поскольку объект,
// на который ссылается pvb, не является D. В Report()
// выбрасывается.bad_typeid, т.к. аргумент нулевой.
//
Report(dynamic cast<D*>(pvb));
} catch(bad__typeid) {
cout << " ... Bad typeid in main()." << end1;
}
return 0;
}
Программа выводит:
Original class: D ... Object ID: D
Original class: B1 ... Bad typeid in Report ().
При преобразованиях типа на некоторой иерархии, особенно нисходящих и перекрестных, применяйте операцию dynamic_cast, а не статическое приведение. Динамическое приведение типа обладает неоспоримыми преимуществами и к тому же не вносит в код никаких дополнительных издержек, если приведение можно осуществить на этапе компиляции. Другими словами, в таких случаях, если это достаточно безопасно, dynamic_cast генерирует точно такой же код, что и static_cast.
Заключение
Управление исключениями, идентификация типа времени выполнения и специальные операции приведения составляют в своем роде единство, которое, при грамотном к нему подходе, позволяет значительно повысить надежность и устойчивость создаваемого программного обеспечения. Конечно, ко всему этому придется довольно долго привыкать, если раньше вы программировали, скажем, только на традиционном С.
Этой главой мы заканчиваем описание стандартного языка C++. Следующая часть книги посвящена визуальным средствам C++Builder и связанными с ними особенностям языка этой системы.
Операция typeid
Для получения информации о типе во время выполнения программы применяется операция typeid:
typeid(имя_ типа) typeid(выражение)
Ее операндом является либо имя типа, либо выражение, оцениваемое как некоторый тип. Операция возвращает константную ссылку на объект класса type_info, объявленный в заголовке typeinfo.h.
Если операция не может определить тип своего операнда, она выбрасывает исключение типа bad_typeid.
Следует помнить, что RTTI в собственном смысле, как динамическое распознавание типа, работает только с полиморфными типами, т. е. классами, имеющими хотя бы одну виртуальную функцию. Если применить операцию typeid к обычному типу, идентификация типа будет произведена статически, т. е. при компиляции.
Reinterpret_cast
Синтаксис данной формы операции приведения таков:
reinterpret_cast<Целевой_тиn> (аргумент)
Такую операцию можно применить для того, чтобы изменить интерпретацию объекта без действительного преобразования данных.
Целевой_тип может быть типом ссылки, указателя, целым, перечислимым или вещественным типом.
Если целевой_тип — тип указателя или ссылки, то аргумент может быть указателем или ссылкой, а также числовой (вещественной, целой, перечислимой) переменной; когда целевым типом является числовой тип, то операнд может быть указателем или ссылкой.
Операция возвращает значение целевого типа.
Возможно, например, явное преобразование указателя в целый тип, равно как и обратная операция. Можно приводить указатель на функцию одного •типа к указателю на функцию другого типа или на некоторый объект, при условии, что он (указатель на объект) имеет достаточную разрядность.
Вот пример преобразования указателя в целое и наоборот:
////////////////////////////////////////////////
// Reinterpret.срр: Демонстрация reinterpret_cast
//
#include <iostream.h>
#pragma hdrstop
#include <condefs.h>
int main () {
int i = 7;
int *ip = Si;
int temp = reinterpret cast<int>(ip);
cout.setf(ios::showbase) ;
cout << "Pointer value is"<< ip << end1;
cout << "Representation of a pointer as int is " << hex << temp << endl;
cout << "Convert it back and dereference:"<<*reinterpret_cast<int*>(temp) << endl;
return 0;
}
Эта программа выводит:
Pointer value is 0065FEOO
Representation of a pointer as int is Ox65fe00
Convert it back and dereference: 0х7
Все это можно проделать, разумеется, и с помощью обычных операций приведения, однако последние мало надежны. Тут при опечатках могут происходить совершенно дикие преобразования, и компилятор не выдает даже предупреждающих сообщений. Специальные же операции имеют более корректный вид и явно показывают, что вы делаете.
RTTI
В этом разделе описан синтаксис и даются примеры использования RTTI.
RTTI и приведение типов
Аббревиатура RTTI означает RunTime Type Identification, т. е. “Идентификация типа времени выполнения”. Это механизм, позволяющий определить тип объекта во время выполнения программы, что очень полезно в иерархии типов, где указатель или ссылка базового класса может ссылаться на представитель любого производного класса. Полиморфные механизмы, конечно, хороши, но выглядят снаружи подобно “черному ящику”. Вы вызываете виртуальные методы, но не знаете, к чему, собственно, они применяются. Иногда требуется точно знать тип объекта. Если же можно с уверенностью идентифицировать типы, открывается возможность безопасного их приведения.
В этой главе рассматриваются RTTI и усовершенствованные операции приведения типа C++.
Специальные операции приведения типа
Стандарт ANSI определяет специальный синтаксис операций приведения типа, позволяющий программисту воспользоваться преимуществами RTTI и, кроме того, указать точно, что он хочет получить в результате таких операций. Новых операций приведения четыре: dynamic_cast, static cast, reinterpret cast и const_cast.
Здесь нужно вспомнить, для чего вообще может служить приведение типа. Можно назвать следующие случаи:
Чтобы изменить действительное представление данных либо поведение объекта, на который ссылается некоторый указатель. Простейшее приведение такого рода — преобразование целого типа в вещественный.
Чтобы изменить лишь интерпретацию компилятором некоторых данных, не меняя их действительного (физического) представления. Таково, например, приведение типа int к типу unsigned и наоборот.
Чтобы снять ограничения на возможные манипуляции с объектом, накладываемые модификатором const.
Эти три случая соответствуют, говоря, может быть, несколько упрощенно, трем последним из перечисленных в начале раздела операций. Операция же dynamic cast позволяет безопасно приводить типы в различных полиморфных иерархиях классов, в том числе с виртуальными базовыми классами.
Мы начнем с более простых и традиционных приведений.
Static_cast
Операция статического приведения типа
static саst<целевой тип> (аргумент)
может выполнять преобразования между числовыми типами, а также между указателями либо ссылками на объекты классов, находящихся в иерархическом отношении (если оно однозначно и базовый класс — не виртуальный). Операция реализуется во время компиляции.
Преобразования числовых типов происходят точно так же, как в случае обычной нотации приведений. Приведение указателей и ссылок возможно как от производного класса к базовому (тут все достаточно просто), так и от базового к производному (нисходящее приведение типа). Конечно, следует помнить, что во многих случаях нисходящее приведение указателя базового типа не будет безопасным, если только он не ссылается в действительности на представитель производного класса.
Если некоторый указатель может быть приведен к типу Т*, то объект этого типа может быть приведен к типу Т&.
Объект или значение могут быть приведены к объекту некоторого класса, если в данном классе объявлен соответствующий конструктор или имеется подходящая операция преобразования. Этот момент продемонстрирован в приведенной ниже программе.
Листинг 13.2. Нисходящее приведение указателей и ссылок
//////////////////////////////////////////////////////
// StatCast.срр: Статическое нисходящее приведение типа.
//
#include <iostream.h>
#pragma hdrstop
#include <condefs.h>
class A {} ;
class B: public A { public:
int i;
B(int ii): i(ii) {}
B(A&): i(11) {
cout << "Derived conversion constructor... ";
} };
int main() {
В b(22), *pb = &b;
A Sra = static cast<A&>(b); // Ссылка на b как
// базовый объект.
А *ра = static_cast<A*>(pb); // Указатель на b как
// базовый объект.
cout << "Derived object: " << b.i << endl;
cout << "Downcasting pointer to pointer: "
<< static_cast<B*>(pa)->i << endl;
// Приведение
// указателей.
cout <<"Downcasting referense to referense: "
<< static cast<B&>(га).i<< endl;
// Приведение
// к ссылке.
cout << "Downcasting reference to object: ";
cout << static cast<B>(ra).i<< endl;
// Приведение
// к объекту.
return 0;
}
Вот что выводит этот код:
Derived object: 22
Downcasting pointer to pointer: 22
Downcasting referense to referense: 22
Downcasting reference to object: Derived conversion
constructor... 11
Как видите, приведение ссылки базового класса к ссылке производного дает ссылку на первоначальный объект производного класса (b), в то время как преобразование той же ссылки в представитель производного класса конструирует новый (временный) объект.
Type info
Класс type_info объявлен следующим образом:
class _TIDIST _rtti type_info {
public:
tpid * tpp;
private:
cdecl type_info(const type info FAR &);
type info & cdecl operator=(const type_info _FAR &);
public:
virtual _cdecl ~type_info() ;
bool cdecl operator==(const type info FAR &) const;
bool cdecl operator!=(const type info FAR &) const;
bool _cdecl before(const type_info _FAR &) const;
const char _FAR *_cdecl name() const;
protected:
cdecl type_info(tpid * tpp) { tpp = tpp; } };
Ключевое слово _rtti перед именем класса гарантирует, что информация о типе для него будет генерироваться вне зависимости от состояния флажка Enable RTTI на странице C++ диалога Project Options (ему соответствует ключ компилятора -rt).
Открытые элементы класса представлены операциями сравнения на равенство и неравенство, а также функциями name () и before (). Первая возвращает указатель на символьную строку с именем типа. Вторая возвращает true, если класс ее объекта является базовым по отношению к классу аргумента.
Вот пример с использованием операции typeid и класса type_info:
Листинг 13.1. Операция typeid
//////////////////////////////////////////////////////
// Typeinfo.срр: Операция typeid.
//
#include <typeinfo.h>
#include <iostream.h>
#include <string.h>
#pragma hdrstop
#include <condefs.h>
class Base { // Базовый класс.
public:
virtual ~Base (){} };
class Derived: public Base { // Производный класс.
char *str;
public:
Derived(const char *s) {
str = new char[strien(s)+1];
strcpy(str, s);
}
~Derived() { delete [] str;}
const char *Get() ( return str;}};
int main() {
Derived d("Derived class' string.");
Base &bRef = d; // Базовая ссылка на производный объект.
cout << "Typeinfo of bRef: " << typeid(bRef).name() << end1;
if (typeid(bRef) == typeid(Derived))
cout << "Contents of bRef: "<< ((Derived 6)bRef).Get() << endl;
else
cout << "Cannot cast safely bRef to Derived." << endl;
return 0;
Здесь демонстрируется операция typeid, сравнение типов и функция name () класса type_inf о. Программа выводит:
Typeinfo of bRef: Derived
Contents of bRef: Derived class' string.
Сравнение типов объекта bRef и Derived показывает, что они совпадают, и программа приводит ссылку к производному типу. Если закомментировать виртуальный деструктор класса Base, он станет неполиморфным, и typeid уже не сможет определить тип объекта, на который в действительности ссылается bRef:
Typeinfo of bRef: Base
Cannot cast safely bRef to Derived.
Эта программа является примером того, как не следует поступать. Идентифицировать класс, затем привести ссылку к нужному типу — это попытка поставить RTTI на место виртуального механизма. Гораздо проще сделать функцию Get() виртуальной:
class Base { public:
virtual ~Base () { }
virtual const char *Get() { throw MyExcpt; } };
try {
cout << "Contents of bRef: " << bRef.Get() << endl;
} catch(MyExcept) {
}
При недопустимом типе объекта выбрасывается исключение (возможны и другие решения).
RTTI следует применять только в тех случаях, когда тип объекта не известен во время компиляции и нецелесообразно применение других средств C++ вроде позднего связывания.
Часть III. Визуальное программирование в C++Builder
Введение в визуальное программирование
Создание визуальных компонентов
В этой заключительной, очень небольшой части книги мы обсудим некоторые аспекты “визуального программирования”, как обычно называют этот род деятельности. Другое название — Rapid Application Development (RAD), “ускоренная разработка приложений”. В отличие от описаний стандартного языка C/C++ в первых двух частях книги изложение будет носить совершенно неформальный характер. Мы будем ориентироваться в основном на разбор конкретных примеров. Я настоятельно рекомендую читателю побольше экспериментировать, изучать материалы оперативного справочника (кстати, сейчас существует, кажется. Help для C++Builder'a на русском языке) и вообще, так сказать, творчески подходить к делу. Визуальное программирование, при всей его кажущейся легкости, для рядового программиста оказывается своего рода искусством с неизбежным атрибутом в виде метода проб и ошибок, поскольку C++Builder и его библиотеки визуальных компонентов представляют собой очень большую и сложную систему.
Еще один пример
В заключение главы мы покажем еще один пример, который не продемонстрирует ничего особенно нового, но послужит основой для разработки нашего собственного компонента в следующей главе. Пример выводит на форму “бегущую строку”.
Форма
Проектирование формы сводится к установке подходящего (небольшого) ее размера и размещению всего трех командных кнопок с надписями “Старт”, “Стоп” и “Выход”. Вы можете руководствоваться рис. 14.5, на котором показана запущенная программа.
Введение в визуальное программирование
Вы, конечно, уже знакомы с визуальным программированием в С+ + Builder, когда на экране перед вами находится фирма, на нее накладываются различные компоненты, реализующие элементы управления графического интерфейса, а затем пишется код для событий этих компонентов, использующий свойства последних. Возможно, вы работали раньше с Visual Basic или даже с Delphi. Все эти системы внешне очень похожи друг на друга, a Delphi — вообще двойник C++Builder'a и опирается на ту же самую библиотеку компонентов.
Класс формы
Определение класса формы создается в h-файле с тем же именем, что и имя модуля. Открыть в редакторе этот файл можно, выбрав в контекстном меню пункт Open Source/Header File. Как видите, класс содержит указатели на объекты размещенных на форме компонентов, а также объявления обработчиков событий. Все элементы класса формы (кроме открытого конструктора) объявлены в разделе с меткой _published (опубликованные). Объявление элемента класса в этом разделе эквивалентно объявлению в разделе public за исключением того, что опубликованные свойства и методы доступны в режиме проектирования через инспектор объектов. Например, поле выбранного события в правой колонке инспектора представляет собой комбинированный выпадающий список. При нажатии на стрелку отображаются доступные обработчики события с подходящим набором параметров. Если переместить, например, обработчик ButtonlClick из раздела _published в раздел public, в этих списках вы его больше не увидите, однако вы можете ввести его имя вручную или, скажем, установить обработчик программно.
Как вы, без сомнения, также заметили, все функции-элементы класса формы объявлены со спецификацией _fastcall. Этот спецификатор протокола вызова описывался в 4-й главе. Он означает, что аргументы при вызове такой функции должны по возможности передаваться в регистрах процессора.
В библиотеке VCL применяется исключительно соглашение о вызове _fastcall. Функции, написанные вами, не обязаны иметь эту спецификацию, однако если функция является методом формы или компонента, разумным будет объявить ее именно так. Во-первых, этот вызов действительно быстрее вызова С или stdcall, а во-вторых, просто ради единообразия.
Код
После этого нужно ввести код программы. Помимо кода обработчиков OnClick для кнопок и OnPaint для формы, вам потребуется написать:
код тела конструктора формы;
деструктор формы;
объявления полей bm, started, position и -interval класса формы;
методы Setup () и Loop ().
Код программы показывает следующий листинг.
Листинг 14.2. Файлы RunningU.h и RunningU.cpp
//---------------------------------------
// RunningU.h
//---------------------------------------
#ifndefRunningUII
#define RunninqUH
//---------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <:Forms.hpp>
#include <ExtCtrls.hpp>
//---------------------------------------
class TFormI : public TForm {
_published: // IDE-managed Components
TButton *Buttonl;
TButton *Button2;
TButton *Button3;
void _fastcall Button3Click(TObject *Sender);
void _fastcail Button1Click(TObject *Sender);
void _fastcall Button2Click(TObject *Sender);
void _fastcall FormPaint(TObject *Senders-private:
// User declarations
Graphics::TBitmap *bm;
inL position;
bool started;
void fastcall Setup();
void _fastcall Loop();
public: // User declarations
int interval;
_fastcall TFormI(TComponent* Owner);
_fastcail ~TForml<);
};
//---------------------------------------
extern PACKAGE TFormI *Forml;
//---------------------------------------
#endif
//---------------------------------------
// RunningU.cpp: Исходный модуль программы
//с "бегущей строкой".
//---------------------------------------
#include <vcl.h>
#pragma hdrstop
#include <sys\timeb-:h>
#include "RunningU.h"
//---------------------------------------
#pragma package (smart_init)
#pragma resource "*.dfm"
TFormI *Forml;
char tent[80] = "Тестирование довольно длинной бегущей
строки...";
//---------------------------------------
// Конструктор формы - выделяет и инициализирует
// битовую матрицу.
//
_fast-call TFormI: :TForml (TComponent* Owner)
: TForm(Owner) {
position = Width;
interval = 10;
started = false;
bm = new Graphics::TBitmap;
Setup 0;
}
//---------------------------------------
// Деструктор формы - удаляет битовую матрицу.
//
_fastcall TFormI::--TFormI() (
delete bm;
}
//---------------------------------------
// Инициализирует битовую матрицу образом текстовой строки.
void _fastcall TFormI:: Setup ()
bm->Canvas->Font->Name = "Comic.Sans MS";
bm->Canvas->Font->Size = 16;
bm->Height = bm->Canvas->TextHeight(text);
bm->Width = bm->Canvas->TextWidth(text) + 1;
bm->Canvas->Brush->Color = clBtnFace;
bm->Canvas->FillRect(Rect(0, 0, bm->Width, bm->Height)) ;
bm->Canvas->TextOut (0, 0, text) ;
}
//---------------------------------------
// цикл бегущей строки. Организует таймер с помощью
//Функциии API GetTickCount () .
//
void_fastcall TFormI::Loop() {
unsigned long nextTick = GetTickCount();
while (started) {
Application->ProcessMes sages ();
if (!started) return; if (GetTickCount () > nextTick)
{
//
// Копировать битовую матрицу с текстом
// на канву формы.
//
Canvas->Draw(--position, 12, bm);
if (position < -bm->Width) position = Width;
nextTick += interval;
} }
//---------------------------------------
// Кнопка "Выход".
//
void fastcall TFornil::Button3Click(TObject *Sender)
{ —
started = false;
Close () ;
}
//---------------------------------------
// Кнопка "Старт".
//
void_fastcall TFormI::ButtonlClick(TObject *Sender)
{
if (started) return;
started = true;
Loop() ;
} //-------------------------------------
// Кнопка "Стоп".
//
void _fastcall TFormI::Button2Click(TObject *Sender)
{
started = false;
}
//---------------------------------------
// Событие OnPaint.
// Обеспечивает обновление изображения
// при остановленной строке.
//
void _fastcall TFormI::FormPaint(TObject *Sender)
{
if (!started)
Canvas->Draw(position, 12, bm) ;
}
Для определения элементов любого класса можно воспользоваться услугами ClassExplorer. Щелкните правой кнопкой на узле нужного класса. Контекстное меню имеет пункты New Field..., New Property... и New Method... Эти пункты меню вызывают соответствующие диалоги, автоматически генерирующие необходимый код, по крайней мере его основу. Например, New Method вводит в класс объявление метода и создает в исходном модуле его оболочку.
Обратите внимание на методы Setup () и Loop (). В первом из них используется свойство Canvas битовой матрицы, созданной в конструкторе формы, во втором — свойство Canvas формы. Это свойство имеется у очень многих компонентов и инкапсулирует контекст графического устройства
Windows (DC). Подсвойства Canvas (такие, как Font, Brush и т. гг.) представляют различные графические объекты. Методы класса TCanvas позволяют рисовать основные графические формы, выводить текст, копировать изображения с одной канвы на другую и выполнять другие операции, связан Метод Loop (), вызываемый при нажатии кнопки “Старт”, содержит цикл ожидания, из которого программа не выходит до тех пор, пока не будет нажата кнопка “Стоп”. На каждом проходе цикла вызывается функция ProcessMessages () объекта Application. Эта функция передает управление системе Windows, чтобы последняя могла обработать находящиеся в очереди сообщения.
Метод использует также функцию API GetTickCount (), которая возвращает число миллисекунд, прошедшее с момента запуска системы. Цикл ожидания проверяет, достигло ли это число маркера времени, хранящегося в переменной nextTick, и если достигло, выводит текст на экран в следующей позиции, сдвигая при этом маркер времени дальше на заданный интервал.
На рисунке показана работающая программа.
Рис. 14.3 Программа бегущей строки
В библиотеке VCL имеется таймер (класс TTimer), который может периодически вырабатывать событие OnTimer. Применение в этой программе таймера позволило бы не прибегать к методике такого, не очень красивого, цикла ожидания. Беда в том, что компонент таймера не может обеспечить достаточно короткий интервал генерирования события. Хотя интервал таймера задается в единицах миллисекунд, на самом деле он оказывается кратным 55 ms, т. е. частота срабатывания таймера не может быть выше 18 герц. (По крайней мере, так обстоит дело в Windows 98, с которой я работаю.) Частота это определяется аппаратным таймером системы. Нам же нужна частота порядка сотни герц, чтобы получилась приемлемая скорость сдвига строки.
Заключение
В этой главе мы представили довольно элементарный материал по основам визуального программирования в C++Builder. Мы немного рассказали о концепции свойств, являющейся, по сути, центральным моментом всего визуального программирования. Далее мы подробнее исследуем устройство визуальных компонентов на примере собственноручно написанного специального компонента.
Компоненты
Под компонентами в C++Builder понимаются объекты или классы объектов, являющиеся, в некотором смысле, объектами “реального мира”. Вы непосредственно видите их на экране, их можно передвигать мышью, они реагируют на нажатие ее кнопок и т. д. Компоненты VCL инкапсулированы в классах языка Object Pascal, однако вполне возможно и написание компонентов на расширенном варианте C++, реализуемом в C++Builder.
Компоненты календаря и кнопки прокрутки, которые мы взяли для вышеприведенного примера, включены в палитру компонентов именно в качестве образцов такого рода. На самом деле стандартный календарь из VCL со страницы Win32 уже имеет все то, что мы реализовали в своем примере.
Чтобы можно было подключать библиотеку VCL к программам на C++, она сопровождается заголовочными файлами C++, моделирующими разделы интерфейса модулей языка Pascal. Эти заголовки содержат “параллельные” определения классов VCL. Заголовки VCL имеют по большей части расширение .hpp.
VCL расшифровывается как “библиотека визуальных компонентов”. Однако среди ее компонентов попадаются и не визуальные. Не визуальным компонентом является, например, таймер (класс TTimer). Компоненты стандартных диалогов тоже, как это ни странно, не визуальны.
Компоненты, свойства и события
Основным строительным элементом визуального программирования является компонент. В свою очередь, для компонентов, в отличие от обыкновенных объектов C++, характерно наличие свойств и событий.
Поля и методы
Поле — это просто другое название для элемента данных класса. Соответственно метод — синоним для функции-элемента класса. Для классов компонентов в C++Builder применяются именно эти термины.
Простой пример
Начнем с очень простого примера. Создайте новое приложение (File New Application в главном меню или значок Application в диалоге New Items). На экране появится пустая форма. Уменьшите ее размер и, руководствуясь рис. 14.1, разместите на ней следующие компоненты: календарь (CCalendar) и кнопку прокрутки (CSpinButton) со страницы Samples палитры компонентов, а также командную кнопку и две метки со страницы Standard. (Я не буду приводить детальных пошаговых инструкций, поскольку вы и так наверняка умеете все это делать, а если не умеете, то полезно будет разобраться самому, да это и не сложно.)
Рис. 14.1 Форма программы в режиме проектирования
С точки зрения определения класса
С точки зрения определения класса компонента события являются просто свойствами определенного типа. В том же классе календаря:
private:
TNotifyEvent FOnChange;
published:
property TNotifyEvent OnChange = {read=FOnChange,
write=FOnChange};
Тип свойства-события должен быть так называемым замыканием (closure), о котором мы подробнее расскажем в следующей главе. Пока имейте в виду, что это специального вида указатель на функцию-элемент.
TNotifyEvent — простейший тип события, не имеющий дополнительных параметров кроме указателя па пославший событие объект:
typedef void _fastcall
(closure *TNotifyEvent)(System::TObject* Sender);
События, как и Другие свойства, можно читать и записывать. Инспектор объектов, например, позволяет присноить событию компонента указатель на требуемую процедуру обслуживания.
Особенность событий состоит в том, что они связывают компонент с внешним миром, позволяя компоненту играть активную роль в общении с ним.
При всяком изменении состояния календарь вызывает свой метод Change ():
void _fastcall TCCalendar::Change() {
if(FOnChange)
FOnChange(this);
}
Метод, в свою очередь, вызывает процедуру, указатель на которую записан в поле свойства OnChange.
На первый взгляд свойство компонента
На первый взгляд свойство компонента не отличается от обычного элемента данных класса (поля). Действительно, в приведенном выше листинге мы видим выражения вроде
Cal->Month =12;
Cal->Year++;
Свойству можно присваивать значение, извлекать из него значение и вообще вроде бы делать с ним все то, что делают с простым полем. Действительно, свойство, как правило, имеет ассоциированное с ним поле, и иногда операции над свойством не означают ничего, кроме соответствующих операций над его полем. Но в примере из предыдущего раздела вы могли видеть, как простое присваивание значения, например, свойству Month календаря сразу меняет все его содержимое. Другими словами, свойство может иметь непосредственный коррелят в “реальном мире” компьютерного экрана. Механизм свойств обеспечивает компонентам ту их реальность, о которой мы говорили чуть выше.
Все это означает, что изменение значения свойства должно сопровождаться некоторыми побочными действиями, и потому присваивание значения свойству реализуется посредством вызова некоторой функции. Такие функции называют обычно set-функциями, а функции для чтения свойств — get-функциями. И те и другие называются функциями доступа.
Наш пример с календарем позволит нам исследовать некоторые аспекты свойств. Откройте окно обозревателя классов, если оно еще не открыто (View Explorer в контекстном меню редактора). Найдите в нем узел TCCalendar и дважды щелкните на нем кнопкой мыши. В редакторе откроется файл ccalendr.h, и курсор будет установлен на начало определения класса календаря. В разделе published вы можете видеть несколько типичных объявлений свойств, например:
_property TDayOfWeek StartOfWeek =(read=FStartOfHeek, write=SetStartOfWeek, defauit=l);
В разделе private объявлено ассоциированное поле:
TDayOfWeek FStartOfWeek;
TDayOfWeek — это просто short. В фигурных скобках записан список атрибутов свойства, который означает, что:
чтение свойства осуществляется посредством прямого доступа,
для установки значения свойства вызывается функция SetStartOfWeek();
свойство имеет значение по умолчанию, равное 1.
Set-функция, как и поле свойства, объявлена в разделе private:
void _fastcall SetStartOfWeek(TDayOfWeek Value);
Объявление свойства Month несколько сложнее:
property Integer Month = {read=GetDateElement,
write=SetDateElement,
stored=false,
index=2,
nodefault};
Атрибуты stored и nodefault относятся к так называемым спецификаторам хранения. Атрибут index показывает, что функции доступа должны вызываться с дополнительным (первым) аргументом, равным 2. Вот объявление set-функции:
void _fastcall SetDateElement(int Index, int Value);
На самом деле календарь сохраняет дату в единственном поле FDate типа TDateTime. Свойства Year, Month, Day не имеют собственных полей, а их функции доступа (они одни и те же, только с разными индексами) оперируют полем FDate.
Исходный модуль с кодом календаря ccalendr.cpp вы можете найти в папке ...\CBuilder5\Examples\Controls\Source.
Установка свойств компонентов
Для дальнейшей работы вам потребуется инспектор объектов (если его нет на экране, откройте его через меню View). Чтобы проще было писать
код, назовите для краткости календарь Са1, а кнопку прокрутки — Spin (т. е. измените в инспекторе свойство Name этих компонентов).
Введите надписи (свойство Caption) для командной кнопки и второй метки (той. что справа, у меня она называлась Label2) — соответственно “Выход” и “Месяц”. Для первой метки (внизу) установите желаемый размер и, если хотите, гарнитуру шрифта, чтобы текст легко читался и выглядел прилично.
На этом этап визуального проектирования нашего приложения закончен. (Сейчас самое время сохранить проект, присвоив имена модулю формы, который по умолчанию называется Unitl.cpp, и главному модулю Ргоjectl.cpp, по имени которого будет назван весь проект и конечный исполняемый файл.) Теперь нужно написать код, который придаст существованию расположенных на форме компонентов какую-то осмысленность.
Этот простой проект содержит следующие основные файлы:
Ргор.Ьрг — файл проекта;
Ргор.срр — главный исходный модуль, с ним редко приходится иметь дело;
PropU.cpp — модуль исходного кода, связанного с формой;
PropU.dfm — файл визуальных ресурсов формы.
PropU.h — заголовочный файл с определением класса формы.
В проекте может быть несколько форм, и каждой из них будет соответствовать свой исходный модуль. Для каждой формы генерируется также заголовочный файл с расширением .h. Проект может включать в себя и модули исходного кода, не связанные непосредственно с какими-либо формами. Если вы создаете именно модуль (значок Unit в диалоге New Items), а не просто срр-файл, то C++Builder автоматически создаст и h-файл с тем же именем.
Ввод кода событий
Откройте страницу Events инспектора объектов. Общая методика написания кода такова: вы выбираете компонент (на форме или из выпадающего списка инспектора объектов) и дважды щелкаете кнопкой мыши на нужном событии в правой колонке страницы событий инспектора. C++Builder автоматически генерирует оболочку обработчика события и переключает фокус на окно редактора кода. Текстовый курсор стоит прямо там, куда вы должны ввести свой код.
Начните с кнопки “Выход”, которая должна закрывать форму, завершая тем самым приложение. Обработчик должен вызывать метод формы Close ().
Листинг 14.1. Файлы программы Prop — PropU.h и PropU.cpp
//---------------------------------------
// PropU.h: Заголовок для PropU.срр.
//---------------------------------------
#ifndef PropUH #define PropUH
//---------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include "CCALENDR.h"
#include <Grids.hpp>
#include "CSPIN.h"
//---------------------------------------
class TFormI : public TForm
{
_published: // IDE-managed Components
TButton *Buttonl;
TCCalendar *Cal;
TLabel *Labell;
TCSpinButton *Spin;
TLabel *Label2;
void _fastcall ButtonlClick(TObject *Sender);
void_fastcall CalChange(TObject *Sender);
void _fastcall SpinDownClick(TObject *Sender);
void _fastcall SpinUpClick(TObject *Sender) ;
void _fastcall FormCreate(TObject *Sender) ;
private: // User declarations
public: // User declarations
_fastcall TFormI(TComponent* Owner) ;
};
//---------------------------------------
extern PACKAGE TFormI *Forml;
//---------------------------------------
#endif
//---------------------------------------
// PropU.cpp: Исходный модуль примера с календарем.
//---------------------------------------
#include <vcl.h>
#pragma hdrstop #include <stdio.h>
#include "PropU.h"
//---------------------------------------
#pragma package(smart_init)
#pragma link "CCALENDR"
#pragma link "CSPIN"
#pragma resource "*.dfm"
TFormI *Forml;
//---------------------------------------
fastcall TFormI::TFormI(TComponent* Owner)
: TForm(Owner) {
}
//---------------------------------------
void_fastcall TFormI::ButtonlClick(TObject *Sender) {
Close () ;
}
//---------------------------------------
void_fastcall TFormI::CalChange(TObject *Sender)
{
char s[40] ;
sprintf(s, "Новая дата %d/%d/%d",
Cal->Day, Cal->Month, Cal->Year);
Labell->Caption = s;
}
//---------------------------------------
void_fastcall TFormI::SpinDownClick(TObject *Sender)
{
Cai->Day = 1;
if (Cal->Month != 1)
Cal->Month-;
else {
Cal->Month = 12;
Cal->Year-;
}
}
//---------------------------------------
void _fastcall TFormI::SpinUpClick(TObject *Sender) (
Cal->Day = 1;
if (Cal->Month != 12) Cal->Month++;
else {
Cal->Month = 1;
Cal->Year++;
}
//---------------------------------------
void _fastcall TFormI::FormCreate(TObject *Sender) {
char s[40] ;
sprintf(s, "Текущая дата %d/%d/%d",
Cal->Day, Cal->Month, Cal->Year);
Labell->Caption = s;
}
//---------------------------------------
В соответствии с листингом файла PropU. cpp введите код для тела обработчиков следующих событий:
OnCreate формы;
OnClick командной кнопки;
OnUpClick и OnDownClick кнопки прокрутки;
OnChange календаря.
Если вы случайно, щелкнув лишний раз мышью, создали ненужный обработчик какого-нибудь события, лучше не удалять его вручную (не забывайте также, что одновременно с оболочкой обработчика создается его объявление в классе формы в h-файле). При компиляции программы он будет удален автоматически; C++Builder сам удаляет все пустые обработчики событий.
На этом этап написания кода закончен. Все остальное, что вы видите в листинге, генерирует C++Builder, в том числе определение класса формы в файле PropU.h. Для компиляции и запуска приложения нажмите кнопку Run. Кнопка прокрутки позволяет менять текущий месяц (и год), который отображается календарем. В календаре можно выбрать мышью текущее число. Получившаяся дата отображается меткой Labell.
На рис. 14. 2 показано окно программы сразу после запуска (вверху) и после некоторых манипуляций ее органами управления.
Рис.14.2 Запущенная программа
Как вы понимаете, ничего полезного эта программа не делает; она просто отображает (по событию OnChange календаря) выбранную пользователем дату. В настоящем приложении событие OnChange и извлеченная из компонента календаря дата могли бы, например, управлять чем-нибудь вроде ежедневника. Вся действительная работа с данными происходила бы “внутри” обработчика этого события в том смысле, что возврат из него означал бы переход программы в состояние ожидания выбора новой даты. Возможны, правда, и другие варианты организации потока управления, однако все равно — в приложении C++Builder любой программный код должен прямо или косвенно вызываться из некоторого обработчика события.
_Classid
Это ключевое слово обозначает операцию, выполняемую во время компиляции. Синтаксис:
_classid(имя_класса)
Стандартный C++, в отличие от Object Pascal, не может оперировать классами как таковыми. Он оперирует представителями классов, конструированными объектами. Операция _classid позволяет получить, как говорят, указатель на метакласс для специфицированного класса (класс TMetaClass).
Указатель на класс как таковой, безотносительно к его представителям, необходим, например, при регистрации класса компонента. При создании нового компонента C++Builder автоматически генерирует в его модуле такой код:
namespace Cticktape Х
void __fastcail PACKAGE ReaisterO
{
TComponentClass classes[1]=
{_classid(CTickTape)};
RegisterComponents("Samples", classes, 0);
}
}
Операция _classid редко используется непосредственно. C++Builder генерирует ее автоматически, как, например, в приведенном фрагменте кода.
_Closure
Ключевое слово _closure позволяет объявить специальный указатель (замыкание) на функцию-элемент. В отличие от стандартного указателя на функцию-элемент, объявленного, например, как
void (AClass::*funcPtr)(int);
и привязанного к конкретному классу, в объявлении замыкания класс не указывается и, таким образом, оно может ссылаться на любую функцию-элемент любого класса, имеющую соответствующий список параметр ров. Объявление замыкания отличается от указателя на функцию наличием ключевого слова closure перед именем переменной:
void (_closure *aClosure) (int) ;
SomeClass *some0bj = new SomeClass;
aClosure = someObj->Func;
aClosure (1);
Замыкание комбинирует указатель на функцию-элемент с указателем на конкретный объект, и последний передается в качестве this при вызове функции через замыкание.
Типы событий в классах компонентов являются замыканиями:
typedef void fastcall ( closure *TPassCompleteEvent)
(System::TObject *Sender, bool& stop);
published:
_property TPassCompleteEvent OnPassComplete =
{ read=FOnPassComplete, write=FOnPassComplete };
_Declspec
Применение этого ключевого слова не ограничено визуальным программированием, однако также участвует в поддержке кода VCL.
Общее применение _declspec
Выражение вида _declspec (аргумент) является модификатором, который может применяться к функциям или переменным. Аргументы dllexport, dilimport и thread формируют модификаторы, соответствующие обычным export, _import и _thread. Разница между ними заключается в том, что обычные модификаторы должны всегда непосредственно предшествовать имени объявляемой переменной или функции, в то время как модификаторы _declspec можно помещать в любом месте объявления:
void _declspec(dllexport) f(void);// Верно.
_declspec(dllexport) void f(void);// Верно.
_export void f(void) // Ошибка: должно быть
// void export.
Другими аргументами _declspec общего применения являются:
naked: Применяется к определению функции. Подавляет генерацию кода пролога/эпилога вызова, позволяя программисту написать свой собственный код, используя встроенный ассемблер.
noreturn: Сообщает компилятору, что функция не возвращает управления вызывающей программе. Обычно, когда компилятор обнаруживает, что при некоторых условиях функция, объявленная с типом возвращаемого значения, ничего не возвращает, он выдает предупреждение. Однако если эта ситуация связана с вызовом другой функции, не возвращающей управления, то, объявив последнюю с модификатором _declspec (noreturn), можно подавить такое предупреждение. Вот пример:
_declspec(noreturn) void finish(){
...
throw "No return";
}
int RetInt(){
if(....)
return 1;
else
finish(); // Без noreturn генерировалось бы
// предупреждение.
nothrew: Соответствует спецификации исключения без аргументов. Следующие объявления эквивалентны:
__declspec(nothrow) void f();
void f() throw();
novtable: Применяется к классам, для которых никогда не будет создаваться представителей. В большинстве случаев этот модификатор подавляет генерирование vtaDie и, соответственно, кода всех функции,на которые она ссылается.
property: Позволяет реализовать в классе не статические “виртуальные элементы данных”, нечто вроде свойств. Синтаксис:
_declspec(property(get = get-функция,
put = put-функция)) объявление_ элемента;
Одна из спецификаций функций доступа может быть опущена. Компилятор транслирует обращения к “элементу данных” в вызовы функций доступа. Возможно также объявление “массива” с одним или несколькими “индексами”:
declspec(property(get=GetVal, put=PutVal)) int vArr[];
Число индексов в обращении к виртуальному массиву должно соответствовать числу параметров у функций доступа. (У простой виртуальной переменной get-функция не имеет ни одного, а put-функция имеет один параметр — присваиваемое значение.) Индексы не обязательно должны быть целыми, они могут быть, например, строками.
selectany: Применяется к глобальным переменным. В ANSI C/C++ принята концепция пробного определения (tentative definition). Если встречается объявление глобальной переменной без модификаторов класса хранения и без инициализации, оно рассматривается как пробное определение. Если тот же идентификатор появляется позднее в другом файле, предыдущее пробное определение считается объявлением external.
Однако инициализация глобальной переменной должна производиться только в одном месте. Если, например, глобальная переменная объявляется и инициализируется в заголовочном файле, подключаемом несколькими файлами проекта, при компоновке возникнет ошибка. Спецификация seiectany решает эту проблему:
_declspec(selectany) int gint = 10;
// Может появляться в
// нескольких файлах проекта.
selectany не может применяться к неинициализируемым переменным и переменным, недоступным за пределами текущего файла (т. е. глобальным статическим, например, глобальным константам C++ без спецификатора external).
uuid: Присоединяет GUID к объявляемому классу. Применяется только к СОМ-классам. Синтаксис:
_Declspec (uuid("GUID_COM-объекта") )
объявление/определение класса
Применение _declspec с VCL
Перечисленные ниже аргументы _declspec, служащие для поддержки VCL, редко применяются непосредственно. Они используются в макросах, определяемых файлом vcl\sysmac.h.
delphiclass: Может применяться для объявления классов, производных от TObject. Для классов, объявленных с этим аргументом _declspec, обеспечивается совместимость с VCL no RTTI, поведению конструкторов/деструкторов и обработке исключений. Накладываются также некоторые ограничения: класс не может быть сложным производным, его представители могут создаваться только динамически, он обязан иметь деструктор и для него не генерируются автоматически конструктор копии и операция присваивания по умолчанию.
delphireturn: Только для внутреннего использования VCL в C++Builder. Служит для поддержки встроенных типов ObjectPascal, для которых не существует эквивалентов в C++. Применяется в реализациях классов C++Builder Currency, AnsiString, Variant, TDateTime и Set.
dynamic: Служит для объявления динамических функций. По своему поведению они не отличаются от виртуальных, но реализуются по-другому. Они включаются в виртуальную таблицу только того класса, который их определяет. Тем самым экономится память, но падает эффективность, так как иногда приходится производить поиск в виртуальных таблицах, возможно, нескольких базовых классов. Аргумент может применяться только к функциям классов, производных от TObject.
hidesbase: В языке Object Pascal виртуальные функции базового класса могут появляться в производном как функции, не имеющие никакого отношения к одноименным функциям базового класса (это имеет место, если у производной функции не указан спецификатор override). Применение hidesbase к объявлению функции производного класса моделирует эту семантику Object Pascal.
package: Показывает, что код класса может компилироваться в пакете. При создании пакетов спецификатор генерируется компилятором автоматически.
pascalimplementation: Показывает, что код класса реализован на Object Pascal. Применяется в заголовочных файлах .hpp, моделирующих интерфейс Pascal-классов VCL.
Создание визуальных компонентов
Одним из показателей квалификации программиста является то, может ли он разрабатывать свои собственные компоненты для визуальной среды программирования вроде C++Builder. Пользовательский компонент должен ничем не отличаться от “настоящего” в том смысле, что его можно будет найти в палитре компонентов, можно будет устанавливать его свойства и события в инспекторе объектов и т. п. Эта глава посвящена написанию пользовательских визуальных компонентов и некоторым сопутствующим вопросам.
Код компонента
Создание визуального компонента — процесс сам по себе не визуальный. Все сводится к написанию кода для свойств и методов класса компонента. Здесь вам может очень помочь ClassExplorer, автоматически или, скорее, полуавтоматически генерирующий базовый код. С двумя его диалогами — для полей и методов — вы уже познакомились в предыдущей главе. Правда, чтобы можно было вводить в класс компонента поля, методы и свойства средствами обозревателя классов, нужно сначала создать проект, который будет этот компонент использовать. Создайте новое приложение и присоедините к его проекту модуль CTickTape.cpp (это делается либо в контекстном меню менеджера проектов, либо выбором Project | Add to Project... в главном меню). Созданное приложение станет потом тестовой программой для проверки работоспособности нашего компонента. Пока же с ним ничего делать не нужно.
Registei-() и ValidCtrCheck()
C++Builder уже создал заготовку компонента — файл CTickTape.cpp. Он содержит на данный момент пустой конструктор и две функции — Registerf) и ValidCtrCheck():
static inline void ValidCtrCheck(CTickTape *)
{
new cricKTapenull);
}
//-----------------------
_fastcall CTickTape::CTick(TComponent*Ownet)
: TGraphicControl(Owner) { }
//----------------------------------------
namespace Cticktape {
void _fastcall PACKAGE Register()
(
TComponentClass classes[1] =
{_classid(CTickTape)};
RegisterComponents("Samples", classes, 0);
}
}
Процедура Register () вызывается при регистрации компонента в палитре C++Builder. Первый ее оператор создает массив компонентов (метаклассов), состоящий в нашем случае из единственного элемента.
С помощью ValidCtrCheck () C++Builder проверяет, можно ли создать представитель класса компонента, т. е. не является ли класс абстрактным. Если класс компонента содержит не определенные чистые виртуальные функции, компонент зарегистрирован не будет.
Ввод пользовательского кода
Завершенный код компонента показан в нижеприведенных листингах 15.1 и 15.2.
Листинг 15.1. Класс компонента телетайпной ленты — CTickTape.h
//------------------------------
#ifndef CTickTapeH
#define CTickTapeH
//-------------------------------
#include <SysUtils.hpp>
#include <Controls.hpp>
#include <Classes.hpp>
#include <Forms.hpp>
//-------------------------
typedef void _fastcall (_closure *TPassCoitipleteEvent)
(System::TObject *Sender, boolS stop);
class PACKAGE CTickTape : public TGraphicControl { private:
int FTickRate;
unsigned long FNextTick;
bool FStarted;
bool needsFill;
int storeWidth;
int position;
Graphics::TBitmap* bm;
TPassCompleteEvent POnPassComplete;
void _fastcall SetStartcd(bool value);
void fastcall SetTickRate(int value);
void _fastcall CMChanged(Messages::TmeasageS message) ;
protected:
virtual void _fastcall Setup ();
virtual void_fastcall DoDrawText(TRect rect, long flags);
virtual void fastcall Paint();
virtual void fastcall PassComplete();
public:
_fastcall CTickTape(TComponent* Ownersvirtual void fastcall Tick();
_fastcall -CTickTape();
_property unsigned long NextTick = {
read=FNextTick, write=FNextTick };
_property bool Started =
{ read=FStarted, write=SetStarted };
_published:
property Caption;
property Font;
_property ParentFont;
_property int TickRate = { read=FTickRate,
write-SetTickRate, default=10};
_property TPassCompleteEvent OnPassComplete = { read=FOnPassComplete, write^FOnPassComplete };
BEGIN_MESSAGE_MAP
VCL_MESSAGE_HANDLER(CM_FONTCHANGED,
TMessage, CMChanged) ;
VCL_MESSAGE_HANDLER(CM_TEXTCHANGED,
TMessage, CMChanged) ;
END_MESSAGE_MAP(TGraphicControl) ;
};
//---------------------------------
#endif
Листинг 15.2. Модуль компонента — CTickTape.cpp
//-----------------------------------
// CtickTape.срр: Модуль компонента "Бегущая строка".
//
#include <vcl.h>
#pragma hdrstop
#include "CTickTape.h"
#pragma Package (smart_init)
//-----------
// VolidCtrCheck is used to assure that the components
//createddonot have any pure virtual functions.
static in void ValidCtrCheck (CTickTape *)
{
new CTickTape (NULL);
}
//------------------
_fastcall CTickTape: :CTickTape (TComponent* Owner)
:TGraphicControl (Owner)
{
Width = 100;
FTickRate = 10;
ControlStyle << csOpaque;
bm = new Graphics::TBitmap ;
Setup ();
}
_fastcall CTickTape::~CTickTape () {
delete bm;
}
//----------------------------
namespace Cticktape {
void _fastcall PACKAGE Register() {
TComponenfcClass classes[1] =
{_classid(CTickTape)};
RegisterComponents("Samples", classes, 0) ;
} I
//------------------------------------
// Установка параметров битовой матрицы.
//
void _fastcall CTickTape::Setup() {
AnsiString text = Caption;
bm->Canvas->Font = Font;
Height = bm->Canvas->TextHeight(text);
storeWidth = Width;
bm->Width = bm->Canvas->TextWidth(text) + 1;
bm->Height = Height;
bm->Canvas->Brush->Color = Color;
bm->Canvas->TextOut(0, 0, text + " ");
if (ComponentState.Contains(csDesigning))
position = 0;
else
position =г Width;
needsFill = true;
}
void_fastcall CTickTape::DoDrawText(TRect rect, long flags) {
if (Width != storeWidth) Setup() ;
if (needsFill) {
Canvas->Brush->Color = Color;
Canvas->FillRect(rect);
needsFill = false; .
} Canvas->Draw(position, 0, bm) ;
}
void _fastcall CTickTape::Paint() {
TRect rect - ClientRect;
DoDrawText(rect, 0);
}
//---------------------------------
// Сдвиг строки на пиксел, если истек
// очередной интервал ожидания.
//
void _fastcall CTickTape::Tick()
{
if ( ! FStarted || (GetTickCount () < FNextTicK) ) return;
FNextTick += FTickRate;
Repaint () ;
if (~position < - bm->Width) {
position = Width;
PassComplete ();
}
}
//-------------------------------------------
// Set-функции.
//
void _fastcall CTickTape::SetStarted(bool value)
{
if (value == FStarted) return;
FStarted = value;
if (value)
FNextTick = GetTickCount ();
}
void _fastcall CTickTape::SetTickRate (int value)
{
if (value > 0) FTickRate = value;
}
//--------------------------------------------------------
// Отработка сообщений FontCbanged и TextChanged.
//
void _fastcall CTickTape::CMChanged(Messages::
TMessage& message)
{
Setup () ;
Repaint () ;
}
//----------------------------------
// Событие OnPassComplete.
//
void_fastcall CTickTape:: PaseComplite()
{
if (OnPassComplete) { bool stop = false;
OnPassComplete (this", stop);
Started = [stop];
} }
Если вы хотите собственноручно повторить все этапы создания этого компонента, то, воспользовавшись услугами обозревателя классов и руководствуясь листингами, введите в класс следующие элементы:
поля needsFill, storeWidth, position и bm;
методы Setup(),DoDrawText(),Paint(),PassComplete(),CMChanged (), Tick () и деструктор;
свойства NextTick, Started, TickRate и OnPassComplete (последнее будет событием компонента).
Не забудьте также ввести код тела конструктора.
В файл CTickTape.h введите typedef замыкания TPassComplete-Event и четыре строки с макросами, которые вы видите в самом конце определения класса. Переобъявите в разделе _published свойства Caption, Font и ParentFont. Это защищенные свойства класса TControl. Если подходить к делу написания компонента совсем серьезно, можно и должно было бы переобъявить и другие унаследованные свойства и, возможно, ввести еще какие-то другие, но для демонстрации этих трех вполне достаточно. Даже ParentFont, пожалуй, лишнее.
Вот, кажется, и все. Сохраните компонент (оба файла). Теперь мы рассмотрим, как он работает.
Начало разработки
Разработка специального компонента начинается с создания файлов исходного модуля и заголовка. Это можно сделать, выбрав либо значок Component в диалоге New Items, либо пункт Component | New Component... в меню. Появится диалог, показанный на рис. 15.1.
Рис. 15.1 Диалог New Component
В этом диалоге нужно выбрать из выпадающего списка Ancestor class базовый класс компонента. Мы возьмем в качестве базового TGraphicControl. Далее нужно указать имя нового класса. (Обычно имена классов компонентов начинаются с Т, но мы назвали наш компонент CTickTape.) После этого осталось указать местоположение и имя модуля в поле Unit file name. Нажмите кнопку с многоточием, и появится стандартный диалог Save.
Все поля заполнены (убедитесь, что в поле Palette page указано Samples), и можно нажимать кнопку ОК. В редакторе кода откроется файл CTickTape. срр. Сохраните компонент (кнопкой SaveAll).
Кстати, визуальные компоненты бывают оконные (базовый класс TWinControl) и графические (базовый класс TGraphicControl). Последние, к которым относится и наш компонент, не имеют собственного окна Windows. Они располагаются в пространстве окна своего родительского объекта. Компоненты оконные, как, например, командная кнопка, имеют свое собственное окно.
Окончательное тестирование
Теперь, когда компонент находится в палитре, можно модифицировать программу-тестер (закомментировать код конструктора и деструктор) и разместить компонент на форме визуальным образом, а также установить нужные его свойства в инспекторе объектов. На рис. 15.5 показан размещенный на форме компонент и инспектор объектов, показывающий имеющиеся опубликованные свойства.
Пример применения компонента
Мы хотим здесь продемонстрировать, что наш компонент очень прост в использовании. Форма имеет три кнопки — для запуска, остановки и выхода из программы. Также на ней размещаются три компонента TTickТаре. Для каждого из них можно задать свой размер и шрифт. Свойства TickRate были установлены в инспекторе объектов равными 10, 7 и 15. Код модуля приведен в листинге 15.4.
Рис. 15.4 Компонент конструкторе форм
Листинг 15.4. Файл ThreeU.cpp.
//-------------------------
#include<vcl.h>
#pragma hdstop
#include<ThreeU.h"
//-------------------------
#pragma packege(smart_init)
#pragma link "CTickTape"
#pragma resourse "*.dfm"
TForm1 *Form1;
//---------------------------
_fastcall TFormI: :TForml (.TComponent* Owner) :Form(Owner)
{
}
//---------------------
void _fastcalll TFormI: :Button2Click(TObject *Sender)
{
if(run)return;
CTickTapel->Started = true
CTickTape2->Started = true CTickTape3->Started = true
run = true;
while (run) (
Application->ProcessMessages() ;
if (!run) break;
CTickTapel->Tick()
CTickTape2->Tick()
CTickTape3->Tick() }
} //-----------------------------
void_fastcall TFormI::Button3Click(TObject *Sender) {
if (run) { run = false;
CTickTapel->Started = false;
CTickTape2->Started = false;
CTickTape3->Started = false;
}
}
//----------------------------------
void_fastcall TFo'rmI: :ButtonlClick (TObject *Sender) {
Button3Click(this) ;
Close () ;
} //------------------------------------
В обработчике кнопки “Старт” находится цикл ожидания:
run = true;
while (run) {
Application->ProcessMessages() ;
if (!run) break;
CTickTapel->Tick() ;
CTickTape2->Tick() ;
CTickTape3->Tick() ;
}
При нажатии этой кнопки три строки начинают бежать по экрану, каждая со своей скоростью. Как видите, все, что требуется от программиста — это достаточно часто вызывать метод Tick() строки. Время компонент будет отмерять сам. На рис. 15.5 показано запущенное приложение.
Рис. 15.5 Программа с тремя бегущими строками
Заключение
На этом наше знакомство с C++Builder заканчивается. В этой главе мы рассмотрели один из довольно сложных аспектов визуального программирования — создание визуальных компонентов. Созданный нами компонентов конечно, нельзя считать завершенным. Его нужно было бы дополнить свойствами класса TComponent, такими, как Visible, Enabled и т. д., может быть, придумать и реализовать какие-то специфические свойства и события. Скажем, можно было бы определить событие для двойного щелчка, который бы останавливал и снова запускал строку. Но все это мы оставляем на усмотрение читателя.
Пример создания компонента
В этом разделе мы продемонстрируем, как в C++Builder создается визуальный компонент. Это будет компонент телетайпной ленты, отображающий “бегущую строку”. Нечто подобное вы видели в предыдущей главе.
_Property
Ключевое слово _property служит для объявления свойств и спецификации их атрибутов. Синтаксис:
property тип свойства имя = { список атрибутов свойства };
Спецификации атрибутов имеют вид атрибут [= значение] и отделяются друг от друга запятыми. Возможны следующие атрибуты:
read = поле \ get-функция
write = поле \ set-функция
stored = true | false | поле get-функция
index = целая константа
default = значение
nodefault
Для атрибутов read/write может указываться либо ассоциированное со свойством поле (тогда говорят о прямом доступе к свойству), либо имя метода доступа. Если спецификатор write опущен, то свойство можно только читать. Можно, конечно, объявить и свойство только для записи, но трудно придумать случай, когда это имело бы смысл.
Обычным является объявление прямого доступа для чтения и set-функции — для записи:
_property bool Started = {read=FStarted, write=SetStarted};
Атрибут index специфицирует целое число, которое должно передаваться в качестве единственного аргумента get-функции (обычно последняя параметров не имеет) и первого аргумента set-функции (которая обычно имеет один параметр — значение для свойства):
private:
int FCoords [4];
int _fastcall GetCoords(int Index) {
return FCoords[Index];
void _fastcall SetCoords (int Index, int value) FCoords[Index] = value;
}
public:
_property int Left = {read=GetCoords,
write=SetCoords, index=0};
_property int Top = (read=GetCoords,
write=SetCoords, index=l);
_property int Right = (read=GetCoords,
write=SetCoords, index=2} ;
_property int Bottom = {read=GetCoords,
write=SetCoords, index=3};
Атрибуты stored, default и nodefault yназываются спецификаторами хранения
Они не влияют на поведение программы и специфицируются обычно только для опубликованных свойств. В частности, они определяют. будет ли значение свойства сохраняться в файле формы. Если атрибут stored равен true и значение свойства отлично от указанного в атрибуте default, значение свойства сохраняется, в противном случае — нет. Если спецификатор stored опущен, для него принимается значение true.
Атрибут default позволяет указать для свойства значение по умолчанию
_property int TickRate = { read=FTickRate,
write=SetTlcKRate, default=10 );
Значение по умолчанию, заданное атрибутом default, относится только к значению свойства, отображаемому инспектором объектов и не присваивается свойству автоматически при запуске программы. Это значение нужно явно присвоить в конструкторе компонента.
Атрибут nodefault отменяет унаследованное значение свойства по умолчанию.
Атрибуты default и nodefault поддерживаются только для целых типов, перечислений и множеств (класс Set библиотеки VCL).
В C++Builder приняты определенные соглашения об именах свойств, их полей и методов доступа. Этим соглашениям, в частности, следует ClassExplorer при автоматическом генерировании кода для свойств. Если имя свойства, скажем, PropName, то именем поля будет FPropName, именем get-функции GetPropName и именем set-функции — SetPropName.
Опубликование унаследованных свойств
Базовый класс создаваемого компонента может объявлять некоторое свойство как открытое или защищенное. В производном классе компонента можно переобъявить такое свойство:
Если свойство базового класса — защищенное, в производном классе его можно сделать открытым или опубликованным.
Если базовое свойство открытое, его можно сделать опубликованным.
Кроме того, при переобъявлении свойства в производном классе можно указать новое значение по умолчанию или, специфицировав nodefault, отменить унаследованное значение, не задавая нового.
При переобъявлении свойства указывается только его имя и, возможно, спецификаторы хранения, например:
_published:
_property OldProp = {nodefault};
_Published
В разделе класса _published объявляются опубликованные свойства. Этот раздел могут иметь только классы, производные от TObject.
В качестве спецификатора доступа _published эквивалентно public. Разница между ними в том, что опубликованные свойства доступны и в режиме проектирования через инспектор объектов, в то время как к открытым свойствам можно обращаться только программно во время выполнения.
В разделе _published нельзя объявлять конструкторы и деструкторы, а также свойства-массивы. Объявляемые в нем поля должны принадлежать к классовому типу.
Расширение набора ключевых слов
Реализация визуальной среды программирования требует расширения языковых средств, поскольку невозможно на стандартном C++ осуществить, например, механизм свойств. Кроме того, разработчики C++Builder ориентировались на уже существующую библиотеку VCL, разработанную для Delphi и написанную на языке Object Pascal, и потому необходимо было ввести в язык дополнения, необходимые для моделирования чуждых C++ языковых конструкций. В этом разделе мы вкратце опишем ключевые слова C++Builder, так или иначе связанные с визуальным программированием.
Разбор кода
Идея работы компонента проста и уже знакома вам по последнему примеру предыдущей главы. Имеется битовая матрица (TBitmap), которая служит для буферизации вывода на экран строки текста. При перерисовывании ее на канву компонента выходящие за его пределы части битовой матрицы автоматически отсекаются. При каждой перерисовке позиция битовой матрицы сдвигается влево на пиксел. Когда строка совсем уйдет с экрана, восстанавливается ее исходная позиция — за правым краем компонента.
Конструктор и деструктор
Конструктор создает операцией new битовую матрицу и устанавливает значения некоторых полей и свойств (Width — унаследованное и уже опубликованное свойство). После этого он вызывает функцию Setup ().
Обратите внимание на строку со свойством ControlStyie. Оно относится к классу Set, который моделирует встроенный тип множеств языка Pascal. Перегруженная операция “ вводит элемент в множество. Стиль компонента определяет, в частности, как будет выполняться его перерисовка. Если не установить csOpaque, весь компонент перед перерисовкой, т. е. вызовом функции Paint (), о которой речь пойдет ниже, будет очищаться и текст строки будет заметно мерцать.
Деструктор удаляет выделенную в конструкторе битовую матрицу.
Метод Setup()
Этот метод производит инициализацию битовой матрицы и вызывается при первоначальном конструировании и изменении свойств компонента — шрифта, ширины или текста.
Метод копирует свойство компонента Font в соответствующее свойство канвы битовой матрицы, определяет ширину и высоту образа текстовой строки и устанавливает по ним размеры матрицы. Устанавливается также цвет фона, и строка выводится на битовую матрицу методом ее канвы TextOut () . Образ строки готов. Справа от текста в битовой матрице имеется по крайней мере одна колонка пикселов, закрашенных фоновым цветом. Благодаря этому обеспечивается очистка пространства компонента, “появляющегося” из-под конца строки при движении ее влево.
Наконец, устанавливается начальная позиция вывода битовой матрицы в графическое пространство компонента. Проверяется свойство Component-State (это множество). Оно индицирует различные аспекты статуса компонента, в частности, участвует ли компонент в визуальном проектировании приложения или он функционирует в работающей программе. Если компонент находится в режиме проектирования, начальная позиция образа строки устанавливается равной 0, т. е. строка будет выведена начиная от его левого края и в конструкторе формы будет виден установленный в инспекторе объектов текст. В противном случае позиции присваивается значение, равное ширине компонента, что помещает строку за его правым краем.
Методы DoDrawTextQ и PaintQ
DoDrawText () производит вывод битовой матрицы на канву компонента, вызывая ее метод Draw () . Попутно он проверяет, не была ли измена ширина компонента и, если это так, вызывает Setup () и очищает компонент, вызывая FillRect (). Методу передается прямоугольник, задающих координаты экранной области компонента (области клиента). Setup(), конечно, производит в данном случае массу лишних операций, но ради простоты логики можно поступиться его изяществом и эффективностью, которая в данном случае не важна. Изменение ширины — единичное редкое действие.
Кстати об эффективности. Вывод образа текста на экран с буферизацией в битовой матрице был выбран потому, что копирование матрицы должно происходить очень быстро, по идее, быстрее, чем непосредственный вывод текста методом Text0ut(). Однако я этого не проверял. Если читатель захочет поэкспериментировать, он может сравнить эффективность методов Draw (), TextOut () и Text?ect (). Это полезное упражнение.
DoDrawText () вызывается из метода Paint (). Виртуальный метод Paint () вызывается, в свою очередь, по сообщениям Windows WM PAINT, a также в результате вызова Refresh () , Repaint (), Update () или Invalidate (). Наш компонент никогда не перерисовывает себя, непосредственно вызывая Paint () . Перед вызовом Paint () производится инициализация канвы компонента и ей выделяется контекст устройства Windows. Без этого наша функция DoDrawText () работать не будет.
Set-функции
Функции SetStartedf) и SetTickRate () устанавливают значения соответствующих свойств. Свойство Started управляет статусом строки — бежит она или стоит на месте. Если его значение меняется с false на true, инициализируется поле маркера времени, свойства NextTick.
Свойство TickRate задает интервал срабатывания “таймера” строки. Set-функция проверяет, не присваивается ли свойству отрицательное число или ноль. Проверка допустимости присваиваемых данных является еще одним важным аспектом механизма свойств.
Метод Tick()
Это единственный, не считая конструктора и деструктора, открытый метод компонента. Именно он обеспечивает “бег” строки по экрану. Если строка остановлена или время нарисовать ее в следующей позиции еще не подошло, метод немедленно возвращает управление. Если же строка запущена и текущий счетчик миллисекунд сравнялся или перешел за маркер времени NextTick, вызывается Repaint () , маркер времени сдвигается на интервал TickRate и определяется следующая позиция строки.
Если строка вышла за пределы компонента, восстанавливается ее начальная позиция и вызывается функция PassComplete () .
Функция PassCompleteQ
Наш компонент может генерировать событие OnPassComplete. Инициирует событие функция PassComplete (). При вызове этой функции из метода Tick() она проверяет, присвоено ли значение (указатель-замыкание) свойству OnPassComplete. Если присвоено, то через свойство вызывается установленная пользователем процедура обработки события со вторым аргументом — переменной stop, в которой пользовательский обработчик может передать true, чтобы остановить строку. Другими словами, если строка ушла с экрана, обработчик события может немедленно запретить возобновление цикла, модифицировав свой параметр-ссылку.
Обработка сообщений
Наш компонент может обрабатывать сообщения cm_fontchanged и CM_TEXTCHANGED, передаваемых ему при изменении соответственно свойств Font и Caption. Это внутренние сообщения приложения С++Вuider, не связанные с сообщениями Windows.
Для обработки обоих этих сообщений мы предусмотрели единственную функцию CMChange() Она вызывает Setup () для обработки изменившихся свойств и затем Repaint () , немедленно перерисовывающую компонент.
Чтобы привязать данные конкретные сообщения к данной процедуре обработки, в классе должен быть реализован виртуальный метод Dis?ат:сЬ (), распределяющий сообщения по назначенным для них обработчикам. В конце определения класса в файле CTickTape.h вы видите четыре макроса “таблицы сообщений”. Они генерируют метод Dispatch () примерно такого вида:
void _fastcali CTickTape::Dispatch(void* Message)
{
switch (((TMessage*)Message)->Msg) { case CM_TEXTCHANGED:
case CM_FOMTCHANGED:
CMChanged(*(TMessage*)Message) ;
breaks; default :
TGraphicControl::Dispatch(Message) ;
} }
Я его несколько “оптимизировал”, поскольку для обоих событий вызывается, одна и та же функция и нет смысла дублировать код для двух меток case. Оператор switch сравнивает идентификатор сообщения Msg с пунктами “таблицы” и вызывает в случае совпадения CMChanged. Если сообщение не опознано, вгдзывается Dispatch () базового класса.
Тестирование компонента
Теперь можно тестировать и отлаживать компонент. Тестировать его как полноценный компонент, установленный в палитре, еще рано. Компоненты отлаживаются сначала в виде обычных модулей, скомпонованных с тестовым приложением. Компонент не размещается на форме с помощью конструктора форм, а создается динамически. Все его начальные свойства также устанавливаются программно, а не инспектором объектов.
Заготовка тестового приложения у вас уже есть. Нужно спроектировать его форму и написать код.
Форма тестера показана на рис. 15.2. Она содержит несколько кнопок и поле редактирования (компонент TEdit). Метка “Текст” слева от поля редактора, собственно, не нужна.
Рис. 15.2 Форма текстового приложения
Что должно тестироваться? В первую очередь, конечно, что строка движется и может быть остановлена и вновь пущена установкой значения свойства Started. Затем нужно проверить, как компонент реагирует на изменения свойств и на сообщение wm_paint, которое посылается приложению, например, при открытии его окна, ранее заслоняемого окном другой программы. Наконец, нужно проверить, генерирует ли компонент предусмотренные события.
Листинг 15.3 показывает код файлов TapeU.h и TapeU.cpp (проект тестера мы назвали Таре).
Листинг 15.3. Файлы программы-тестера для CTickTape
//-------------------------------
// TapeU.h
//
#fndef TapeUH
#define TapeUH
//--------------------------------------------
#include <Classes .hpp>
#include <Controis .hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <Dialogs.hpp>
#include "CTickTape.h"
//----------------------------------
class TFormI : public TForm
{
_published:
// IDE-managed Components
TButton *Buttonl;
TLabel *Labell;
TEdit *Editl;
TButton *Buttun2;
TButton *Button3;
TButton *Button4;
TButton *Button5;
TFontDialog *FontDialogl;
TButton *Button6;
CTickTape *Tape;
void _fastcall ButtonlClick(TObject *Sender);
void _fastcall Button2Click(TObject *Sender);
void _fastcall Button3Click (TObject *Sender);
void _fastcall Button4Click(TObject *Sender);
void _fastcall Button5Click(TObject *Sender);
void _fastcall Button6Click(TObject *Sender);
void _fastcall TapePassComplete(TObject *Sender,buul Sstop),
private: // User declarations
public: // User declarations
_fastcall TFormI(TComponent* Owner) ;
// _fastcall -TFormlO;
};
//-------------------------------------
extern PACKAGE TFormI *Forml;
//-------------------------------------
#endif
//-------------------------------------
// TapeU.cpp: Модуль тестера бегущей строки.
//
#include <vcl.h>
#pragma hdrstop
#include "TapeU.h"
//--------------------------------------
#pragma package(smart_init)
#pragma link "CTickTape"
#pragma resource "*.dfm"
TFormI *Forml;
char *strings[3] = ("Первая строка...", "Вторая строка...", "Третья строка...");
//---------------------------------------
_fastcall TFormI::TForm1(TComponent* Owner) : TForm (Ow.ner)
{
/*
Tape = new CTickTape(this);
Tape->Parent = this;
Tape->Left = 20;
Tape->Top = 10;
Tape->OnPassComplete = TapePassComplete;
*/
}
/*
_fastcall TFormI::-TFormI()
{
delete Tape;
}
*/
//-----------------------
//кнопка "Выход".
//
_fastcall TFormI::ButtonlClick(TObject *Sender) Tape->Started = false;
Close();
}//------------------
//Кнопка"Ввести".
//
void _fastcall TFormI::Button2Click(TObject *Sender)
{
Tape->Caption = Editl->Text;
}
//--------------------------------
//
void_fastcall TFormI : : Button3Click (TObject *Sender)
{
Tape->Started - true;
while (Tape->Started) {
Application->ProcessMessages() ;
Tape->Tick() ;
}
//-----------------------------------------------
// Кнопка "Стоп".
//
void _fastcall TFormI::Button4Click(TObject *Sender)
{
Tape->Started = false;
} //----------------------
// Кнопка "Ширина".
//
void_fastcall TFormI::Button6Click(TObject *Sender)
{
Tape->Width = Tape->Width % 300 + 50;
}
//----------------------------------------
//----------------------------------------
// Выбор шрифта.
//
void_fastcall TFormI::ButtonSClick(TObject *Sender)
{
FontDialogl->Font = Tape->Font;
if (FontDialogl->Execute ())
Tape->Font = FontDialogl->Font;
//------------------------------------------
// Обработчик события OnPassComplete.
//
void _fastcall TFormI::TapePasaComplete(TObject* Sender,
bool& stop)
static int i = 0;
if (i > 2) {
i = 0;
stop = true;
} Tape->Caption =strings[i++] ;
} //----------------------------------------
В модуле TapeU. cpp вы видите закомментированный код (и одну строку в TapeU.h), выделенный жирным шрифтом, который нужно в данный момент раскомментировать. Показанный в листинге вариант программы (с исключенным деструктором формы и кодом тела конструктора) соответствует окончательному тестированию компонента, установленного в палитру к размещенному на форме тестера во время ее проектирования.
Код программы-тестера
Конструктор формы динамически создает компонент CTickTape и задает начальные значения некоторых свойств, в том числе указатель на процедуру обработки события OnPassComplete. Указатель на компонент, объявленный в классе формы, назван Таре.
Чтобы запустить бегущую строку, нужно нажать кнопку “Старт”. Обравитчик события кнопки устанавливает свойство компонента Started и входит в цикл, который будет прерван только при нажатии кнопки “Стоп”; в общем случае тогда, когда свойство Started окажется равным false.
Код кнопки “Старт” исполняет неопределенный цикл, вызывающий функцию приложения ProcessMessaghes () и метод компонента Tick () . Метод Tick () сам знает, когда нужно будет перерисовать компонент.
Остальная часть программы довольно очевидна. Для управлением шрифтом мы разместили на форме компонент TFontDialog. Компонент этот невизуальный, на форме отображается только его значок.(на рис. 15.3 его можно видеть в левом нижнем углу). Настоящий диалог выбора шрифта заводится на экран, когда вызывается его метод Execute () . Это делает функция Button5Click () (кнопка “Шрифт”).
Тестируется также способность компонента реагировать на ширину своего поля. Размер компонента можно менять, устанавливая свойство Width. Такое действие не генерирует никакого сообщения, однако вызывает функцию Paint ().
Окно редактора и кнопка “Ввести” позволяют присвоить любой текст свойству компонента Label.
Кнопка “Ширина” устанавливает горизонтальный размер компонента.
Пример тестирует также событие компонента OnPassComplete. Как только текущая строка ушла с экрана, по этому событию свойству Caption аписваивается друругая строка. Когда все наличные строки исчерпаны, обработчик события останавливает строку.
Работающий компонент показан на рис. 15.3.
Рис. 15.3 Программа-тестер,
работающая с компонентом CtickTape
_Thread
Это ключевое слово имеет отношение к VCL лишь постольку, поскольку последняя включает в себя средства для работы с нитями (threads) кода В программе может инициироваться несколько потоков управления исполняющих параллельно. Эти потоки и называются нитями.
Обычно все параллельные нити используют одни и те же глобальные
переменные. Это кстати, позволяет эффективно реализовать взаимодействие между нитями. Объявление глобальной переменной с модификатором _thread приводит к тому, что для каждой нити будет создана своя копия этой переменной.
Модификатор не может применяться к объектам, требующим инициализации во время выполнения (например, переменной — объекту класса с определенным пользователем конструктором).
Установка компонента
Чтобы установить компонент в палитру компонентов, выберите в меню Component | Install Component... В появившемся диалоге вам нужно будет задать только имя модуля компонента. По умолчанию ваш компонент будет введен в пакет dclusr50.b.pk. Компонент окажется на странице палитры Samples. Если вы не предусматриваете для него специального значка, он будет представлен значком по умолчанию.
Значок компонента
Если вы хотите, чтобы ваш компонент, как и “настоящие”, был представлен в палитре компонентов индивидуальным значком, вы, во-первых, должны предоставить для регистрации сам значок, а также сообщить C+4-Builder сведения о его местоположении. Значок (пиктограмма) должен иметь размер 24Х24 пиксела. “Прозрачным” цветом в C++Builder считается оливковый.
Значок должен находиться в компилированном файле ресурсов с расширением .res или .dcr и иметь идентификатор ресурса, соответствующий имени класса компонента, в нашем случае CTICKTAPE.
Файл ресурсов должен быть присоединен к проекту пакета, в котором будет размещаться компонент. Если ваш компонент имеет стандартное имя, начинающееся с Т, то он может быть загружен автоматически, если файл ресурсов имеет расширение .dcr, находится в том же каталоге, что и модуль компонента и имеет то же имя, что и класс, но без Т. В нашем случае имя класса нестандартное, так что придется либо присоединить значок к проекту пакета с помощью менеджера проекта, либо вручную открыть файл CBuilder5\Lib\dclusr50.bpk и ввести в него примерно такую строку:
USERES("C:\Ppojects\Chl5\TickTape\CTickTape.dcr") ;
Как вы понимаете, все это нужно сделать до того, как вы станете устанавливать компонент.
Класс AnsiString
Класс AnsiString определяется в заголовке dstring.h. Конструктор этого класса перегружен, так что строки можно инициализировать различными способами — другой строкой, строкой С (указателем на char), а также целыми типами. В последнем случае строка будет содержать текстовое представление числа.
Класс Set
Set — это шаблон, определенный в заголовке sysset.h:
tempiate<class T, unsigned char minEl, unsigned char maxEl>
class _declspec(delphireturn) Set;
При объявлении конкретного класса нужно задать следующие параметры: тип элементов (обычто целый, символьный или перечисление), минимальное значение, которое может содержать множество (должно быть не меньше 0), и максимальное значение, которое может входить в множество (должно быть не больше 255). Вот примеры:
Set<int, 0, 31> a5et0f32;
typedef Set<char, 'A', 'Z'> SetOfUppercase;
Перегруженный конструктор создает либо пустое множество, либо копию существующего множества того же типа.
Множества можно складывать, умножать (объединение и пересечение) и вычитать (пересечение c дополнением). Операции “ и ” соответственно вводят или удаляют элемент из множества. Эти же операции могут использоваться для извлечения и передачи множеств в поток (в виде последовательностей нулей и единиц), если перед sysset.h включается заголовок iostream или директива
#define VCL_IOSTREAM
Множества Set имеют два метода:
Sets _fastcall Clear (); Удаляет из множества все элементы.
boo1 _fastcall Contains(const T el) const; Возвращает true, если множество содержит указанный элемент.
Методы
Мы не будем приводить здесь полное описание всех методов AnsiString, а расскажем только о важнейших.
int _fastcall AnsiCompare(const AnsiStringfi rhs) const.; Этот метод производит сравнение строки с указанной строкой. Сравнение производится в соответствии с текущим локалом Windows, т. е., например, в русифицированной системе сравнение строк с русским текстом должно давать правильный результат. (Честно говоря, я этого не проверял.) Соответственно результат сравнения может отличаться от результата операций сравнения, которые производятся исходя из ASCII-значений символов.
int _fastcall AnsiPos(const AnsiStringS subStr) const; Метод возвращает позицию указанной подстроки. Первому символу строки соответствует 1. Нулевой результат означает, что подстрока не найдена.
char* _fastcall с str() const; Возвращает указатель на ограниченную нулем строку С (символьный массив). Если строке ничего не присвоено, возвращается указатель на пустую строку.
const void* fastcall data() const { return Data; } Аналогичен предыдущему, но возвращает нулевой указатель в случае неприсвоенной строки.
AnsiString& _fastcall Delete(int index, int count); Удаляет заданное число символов начиная с указанной позиции. Позиция 1 соответствует первому символу строки.
enum TStringFloatFormat
(sffGeneral, sffExponent, sffFixed,
sffMumber, sffCurrency };
static AnsiString _fastcall FloatToStrF(long double value, TStringFloatFormat format, int precision, int digits) ;
Производит преобразование числа с плавающей точкой в текстовую форму в соответствии с указанным форматом.
AnsiString& _fastcall Insert(const AnsiString& sir, int index) ; Вставляет строку в указанной позиции (1 — перед первым символом). Если индекс меньше 1, он считается единицей.
static AnsiString fastcall IntToHex(int value,int digits) ; Преобразует число в строку с представлением его в виде щестнадцате-ричных цифр. Второй параметр указывает минимальное число цифр. Аналогичен Pos, но допускает многобайтовые символы.
bool _fastcall IsDelimiter (const AnsiString& delimiters, int index) const; Проверяет, входит ли символ в позиции index в указанную строку delimiters.
bool _fastcall IsEmptyO const; Возвращает true, если строка пустая.
int _fastcall LastDelimiter (const AnsiString& delimiters) const; Возвращает позицию последнего из символов строки, входящих в указанную строку ограничителей.
int _fastcall Length() const; Возвращает число байтов в строке.
static AnsiString fastcall LoadStr(int ident) ; Загружает строку строковым ресурсом исполняемого файла. Если ресурса с указанным идентификатором не существует, возвращается пустая строка.
AnsiString _fastcall Lowercase() const; Возвращает строку, преобразованную в нижний регистр в соответствии С Текущим ЛОКалом Windowa. He хюмвняет исходную строку.
int _fastcall Pos(const AnsiString& subStr) const; Возвращает позицию указанной подстроки.
int _cdecl printf(const char* format, ...); Формирует строку в соответствии со стандартным форматом С. Возвращает длину строки-результата.
AnsiString& _fastcall SetLength(int newLength) ; Устанавливает новую длину строки, перераспределяя память под ее символьный массив. В новый массив копируется исходное содержимое строки. Если новая длина меньше исходной, строка усекается.
AnsiString& cdecl sprintf(const char* format, ...); To же, что и printf (), но возвращается ссылка на модифицированную строку (*this).
StringOfChar
AnsiString fastcall Substring(int index, int count) const; Возвращает подстроку с указанной позицией и длиной.
AnsiString _fastcall Substring(int index, int count) const; Преобразует строку в число двойной точности. Если строка не содержит корректного представления числа с плавающей точкой, выбрасывается исключение EConvertError.
int _fastcall Tolnt() const; Преобразует строку в целое. Если строка не содержит корректного представления числа, выбрасывается исключение EConvertError.
int _fastcall ToIntDef(int defaultValue) const; Преобразует строку в целое. Если строка не содержит корректного представления числа, возвращается указанное значение по умолчанию.
AnsiString fastcall Trim() const; Возвращает строку с удаленными начальными и конечными пробелами и управляющими символами.
AnsiString fastcall TrimLeftO const; Возвращает строку с удаленными начальными пробелами и управляющими символами.
AnsiString _fastcall TrimRight() const; Возвращает строку с удаленными конечными пробелами и управляющими символами.
Unique
AnsiString _fastcall Uppercase() const;
Возвращает строку, преобразованную в верхний регистр в соответствии с текущим локалом Windows. He изменяет исходную строку. В классе AnsiString реализованы операции присваивания, сложения (возвращает конкатенацию строк), индексации (возвращает символ с указанным индексом) и отношений (сравниваются ASCII-значения входящих В строку символов).
Если перед dstring.h включается заголовок iuatrcam кла директива
#define vcl_iostream
то строки можно использовать в операторах ввода/вывода потоков C++ с операциями “ и ”.
Классы AnsiString и Set
В VCL для представления текстовых строк используются не символьные массивы и не шаблон стандартной библиотеки, а строки Object Pascal. Кроме того, многие свойства компонентов представлены множествами этого языка. Для моделирования таких объектов C++Builder реализует классы AnsiString и Set. В этом приложении мы даем краткое описание этих классов.