Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Теллес М. - Borland C++ Builder. Библиотека программиста - 1998

.pdf
Скачиваний:
799
Добавлен:
13.08.2013
Размер:
4.35 Mб
Скачать

Borland C++ Builder (+CD). Библиотека программиста 351

if ( pos || !isspace(c) ) szBuffer[pos++] = c;

if ( !strcmp(szBuffer, "public:") || !strcmp(szBuffer, "private:") || !strcmp(szBuffer, "protected:") )

{

pos = 0; szBuffer[pos] = 0;

}

last_c = c;

} while ( !IsEndOfLine(c) );

if ( (c == '#' && pos == 0) || (c == '/' && last_c == '/') )

{

while ( c != '\n' && !feof(fp) ) c = fgetc(fp);

}

else

bFoundIt = ProcessLine( bFoundIt, szBuffer, className, pPropList, pMethodList );

}

fclose(fp);

}

Последняя в нашем коде функция GetMethodsAndProperties используется для заполнения второго (свойств) и третьего (методов) списка в нашем приложении.

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

Теперь, когда мы разобрались со вспомогательными функциями, пришло время написать код для самой формы, используя эти замечательные функции. Первое, что надо сделать, — это добавить обработчик для команды меню Файл д Открыть. Итак, добавьте обработчик для этого пункта меню, а в него впишите следующие строки:

void __fastcall TForm1::Open1Click(TObject *Sender)

{

ListBox1->Clear();

if ( OpenDialog1->Execute() )

{

FstrFileName = OpenDialog1->FileName; PreScanClasses(FstrFileName.c_str(), ListBox1);

}

}

Этот метод открывает диалог открытия файла, для того чтобы пользователь мог выбрать заголовочный файл для рассмотрения содержимого. После того как файл выбран, загружается первый список (с именами классов) посредством вызова функции PreScanClasses, которую мы воплотили во вспомогательном модуле нашего проекта.

Выбор следующего пункта меню Файл д Выход прекращает работу нашего приложения. Добавьте обработчик для пункта меню Файл д Выход и внесите в него следующие строки:

void __fastcall TForm1::Exit1Click(TObject *Sender)

{

Borland C++ Builder (+CD). Библиотека программиста 352

Application->Terminate();

}

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

void __fastcall TForm1::ListBox1Click(TObject *Sender)

{

AnsiString s;

// Получаем выбранный элемент

for ( int i=0; i<ListBox1->Items->Count; ++i ) if ( ListBox1->Selected[i] )

{

s = ListBox1->Items->Strings[i];

}

// Загружаем остальные списки

ListBox2->Clear();

ListBox3->Clear(); GetMethodsAndProperties(FstrFileName.c_str(), s.c_str(), ListBox3, ListBox2 );

}

Как можно видеть из приведенного кода, большую часть работы выполняют наши вспомогательные функции, которые могут произвести синтаксический разбор заголовочного файла, загрузить списки и проверить корректность отображения информации для пользователя. Итак, теперь у вас есть полностью завершенное приложение просмотра классов, которое может использоваться для отображения всех классов, а также их свойств и методов, содержащихся в выбранном заголовочном файле. На рис. 17.2 показано окно работающего приложения, в котором отображены классы, найденные в директории, в которой находится VCL системы.

Вторая стадия: добавление в базу данных

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

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

Borland C++ Builder (+CD). Библиотека программиста 353

Рис. 17.2. Приложение просмотра файлов в действии

Рис. 17.3. Обновленная форма приложения просмотра классов

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

Для их создания мы воспользуемся программой создания баз данных, освоенной нами в одной из предыдущих глав (в конце концов, не в этом ли основная прелесть многократного использования кода?).

В этом приложении нам потребуются три таблицы баз данных имен классов (ClassNames), методов (Methods) и свойств (Properties). Очевидно, что таблица ClassNames, содержащая имена классов, загруженные из заголовочного файла, должна содержать поля для имени класса и уникального идентификатора класса. Поскольку имена классов уникальны только в пределах одной именованной области видимости, для создания связей между таблицами нам и потребуется уникальный идентификатор класса. В табл. 17.1 показаны поля, которые мы будем использовать в таблице имен классов.

 

 

Borland C++ Builder (+CD). Библиотека программиста 354

 

 

Таблица 17.1. Таблица ClassNames

Поле

Тип

Длина

ClassId

Character String

10

ClassName Character String

80

Определив поля, можно воспользоваться программой создания баз данных для добавления этих полей в таблицу. Итак, создайте таблицу с именем Names.DBF — файл dBase, который мы будем использовать далее в нашем приложении.

Таблица методов

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

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

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

 

 

Таблица 17.2. Таблица Methods

Поле

Тип

Длина

ClassId

Character String

10

Method

Character String

255

Используя программу создания баз данных, создайте новую таблицу с именем Methods.DBF. Это файл dBase, который мы будем использовать для хранения информаци и о методах, полученной нами при анализе заголовочного файла. Каждый раз, когда в таблицу классов будет заноситься очередное название, мы будем присваивать ему идентификатор. Тот же самый идентификатор будет записываться и при добавлении в таблицу методов, что позволит нам без труда организовать ссылки между таблицами и увязать необходимую нам информацию.

Таблица свойств

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

Borland C++ Builder (+CD). Библиотека программиста 355

В табл. 17.3 показаны все поля, которые нам понадобятся в таблице свойств (Properties). Вы

удивлены?

 

 

 

 

Таблица 17.3. Таблица Properties

Поле

Тип

Длина

ClassId

Character String

10

PropertyName

Character String

80

Как можно уложить описание целого свойства в одно-единственное поле базы данных? Где же типы? Где же функции чтения и записи? Как это все будет работать при таком описании? Расслабьтесь, все это очень просто.

Как вы помните, при определении свойства для компонента надо определить имя свойства, его тип, а также способ для пользователя получать и передавать информацию из этого свойства. Типичное описание свойства в вашем коде выглядит примерно следующим образом:

__property int AProperty = {read = FAProperty, write = SetAProperty, default = 32};

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

Однако для свойств, которые просто представлены из базового класса компонента, вовсе не обязательно заново определять все, что было только что перечислено. Для так называемых «подсаженных» (hoisted) свойств достаточно, чтобы вы указали название свойства, которое вы представляете из базового класса в секции __published. Предположим, что у нас есть следующее описание свойства в базовом классе:

protected:

__property System::AnsiString Text ={read = Text;

write = SetText};

Если вы захотите использовать свойство Text в классе компонента, наследующем от этого базового класса, вам не потребуется вся информация, хранящаяся в этом свойстве. Вам потребуется лишь «подсадить» это свойство в наследующий класс компонента. Это делается написанием одной строки вроде следующей:

__published:

__property Text;

Помните, что компилятор уже знает тип свойства из базового класса. Вам нельзя менять тип свойства, поскольку это совершенно собьет пользователя с толку у вас в Object Inspector появятся два различных свойства с различными типами. Которое из них какого типа? Зависят ли они друг от друга? И если можно использовать оба, то как их различить в коде?

Borland C++ Builder (+CD). Библиотека программиста 356

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

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

проще просто использовать объект TTable для записи нужных нам данных из списков в базу данных. Давайте этим и займемся.

Сохранение данных

Изменения, которые нам надо внести в форму нашего приложения просмотра классов для обеспечения постоянного хранения данных, достаточно прямолинейны. В правый нижний угол формы мы добавим кнопку с заголовком Импорт. Эта кнопка будет использоваться пользователем для импорта текущего выбранного класса в базу данных. Кроме кнопки, мы добавим на форму три объекта TTable, которые будут представлять три определенные нами только что таблицы классов, методов и свойств.

Установите название базы данных (Database name) для таблицы классов в «.\», что даст знать CBuilder о том, что таблица должна находиться в той же директории, что и само приложение. Поскольку это относительный путь, программа будет работать корректно вне зависимости от того, в какую директорию мы ее сынсталлируем. Если мы зададим абсолютный путь (типа «C:\Program Files\Class View\»), нам придется инсталлировать нашу систему в эту директорию для кор

ректной работы. Мы, конечно, можем задавать путь при запуске приложения, но примененный нами подход является самым простым и действенным. Мы таким образом удостоверяемся, что все части нашего компонента были сынсталлирова ны в одно и то же место, тем самым избавляя приложение от необходимости дополнительных проверок. Вы можете спросить, почему было не присвоить приложению псевдоним (alias) BDE и не использовать его? Это более трудоемкий процесс и, следовательно, содержащий больше ошибок. Вам надо заводить псевдоним на машине конечного пользователя, проверять, правильно ли все установле но, и т. д. Псевдонимы отлично работают в программах, работающих с сетевыми или удаленными базами данных, но чересчур перегружают небольшие приложения типа нашего.

После установления названия базы данных перейдем к названию таблицы (Table name) — установим его в Names.DBF. Это та самая таблица, которую мы чуть ранее создали. Сам компонент-таблицу назовем NameTable (то есть изменим значение ее свойства Name). Я вовсе не всегда устанавливаю персональные названия для компонентов, используемых в приложении, — только когда существует реальная возможность перепутать их друг с другом. В данном случае у нас на форме есть три объекта TTable, которые отличаются друг от друга только номерами, так что легко запутаться. Именно поэтому мы и назовем их осмысленно так, чтобы можно было сразу понять, к которой из используемых нами таблиц относится объект.

Определив полностью таблицу Names, проделайте все то же самое для таблиц Methods и Properties. Обе таблицы будут иметь тот же самый путь к базе данных (.\); название ассоциируемой с Methods таблицы будет Methods.DBF, а с Properties — Props.DBF. Для всех объектов-таблиц установите флаг Active в true, чтобы можно было напрямую использовать их в

Borland C++ Builder (+CD). Библиотека программиста 357

нашем коде.

Добавление данных в таблицы

Теперь, когда все таблицы на форме полностью определены, пришло время написать код для занесения данных в таблицы. Для этого нам надо добавить обработчик для события OnClick кнопки Импорт. Но обрабатывать это событие мы будем только в том случае, если пользователь выбрал какой-нибудь класс, так что сначала нам надо изменить обработчик выбора элемента в списке классов. Измените его следующим образом:

void __fastcall TForm1::ListBox1Click(TObject *Sender)

{

AnsiString s;

// Получаем выбранный элемент

for ( int i=0; i<ListBox1->Items->Count; ++i ) if ( ListBox1->Selected[i] )

{

s = ListBox1->Items->Strings[i];

}

//Загружаем остальные списки

ListBox2->Clear(); ListBox3->Clear();

GetMethodsAndProperties(FstrFileName.c_str(), s.c_str(), ListBox3, ListBox2 );

//Если какой-нибудь элемент был выбран,

//идем дальше импортируем данные

//в базу данных

if ( ListBox1->ItemIndex > -1 )

{

Button1->Enabled = true;

}

}

Теперь с этим кодом кнопка Импорт будет доступна только в том случае, если выбрано какое- нибудь значение в списке. Для большей уверенности изначально установите свойство Enabled кнопки Импорт в false, а то она будет доступна сразу после запуска приложения.

Когда пользователь нажимает кнопку Импорт, нам надо добавлять данные в базу. Для этого добавьте обработчик для события OnClick кнопки Импорт со следующими строками кода:

void __fastcall TForm1::Button1Click(TObject *Sender)

{

//Сначала добавляем выбранный элемент

//в список

if ( ListBox1->ItemIndex != -1 )

{

AnsiString strClass = ListBox1->Items-> Strings[ ListBox1->ItemIndex ];

// Добавляем его в базу данных

int nRecordNo = NameTable->RecordCount; NameTable->Append(); NameTable->FieldValues["ClassName"] = strClass.c_str(); NameTable->FieldValues["ClassID"] =

Borland C++ Builder (+CD). Библиотека программиста 358

AnsiString(nRecordNo+1); try

{

NameTable->Post();

}

catch ( Exception& te )

{

MessageBox(NULL, te.Message.c_str(), "Error", MB_OK);

}

// Теперь обрабатываем свойства

for ( int nProp=0; nProp<ListBox3->Items->Count; ++nProp )

{

PropertyTable->Append(); PropertyTable->FieldValues["ClassID"] = AnsiString(nRecordNo+1); PropertyTable->FieldValues["PropertyNa"] = ListBox3->Items->Strings[nProp]; PropertyTable->Post();

}

// И наконец, обрабатываем методы

for (int nMethod=0; nMethod<ListBox2->Items->Count; ++nMethod )

{

MethodTable->Append(); MethodTable->FieldValues["ClassID"] = AnsiString(nRecordNo+1); MethodTable->FieldValues["Method"] = ListBox2->Items->Strings[nMethod]; MethodTable->Post();

}

}

Этот код выполняет несколько задач. Во-первых, имя выбранного класса добавляется в таблицу Names. Уникальный идентификатор будет установлен равным номеру этой записи в таблице, так что нам не придется генерировать его при помощи какого-нибудь хитрого алгоритма или волноваться о том, что у двух записей будут совпадающие номера. Мы предоставляем базе данных позаботиться об уникальности идентификатора, сделав его чем-то вроде «поля автоматического приращения». Итак, идентификаторы класса нумеруются от 0 до количества записей в базе1.

После того как главная запись добавлена в таблицу Names, мы повторяем процесс для двух оставшихся списков и таблиц. Нет нужды искать выбранные значения в этих списках, поскольку все элементы в них представляют свойства (или методы), принадлежащие выбранному классу. Обратите внимание на свойство FieldValues объектов-таблиц, которое мы используем для присвоения значения полю в базе. Это свойство имеет тип Variant. Переменные типа Variant могут содержать любое количество полей любых типов. В данном случае Variant может содержать

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

Borland C++ Builder (+CD). Библиотека программиста 359

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

Обратите внимание на имена полей в таблицах. Несмотря на то что поле имен свойств в таблице Props.DBF называется у нас PropertyName, dBase поддерживает только 10 символов в названии поля, так что нам придется сокращать его название до PropertryNa в нашем коде. Если мы попробуем использовать полное название, BDE не сможет его переварить и решит, что поле не найдено. Так что надо об этом помнить. Используйте разработанное нами ранее приложение просмотра полей баз данных для выяснения необходимых имен полей (конечно, можно использовать и Database Desktop — утилиту для работы с базами данных, поставляемую с

CBuilder).

Первая проба

После того как приложение скомпилировано, собрано и запущено, его надо протестировать. В некоторых случаях при попытке запустить приложение непосредственно из среды CBuilder вы будете получать странное сообщение об ошибке (Database Structure Corrupted — нарушена структура базы данных). Если это происходит, просто закройте CBuilder и запустите ваше приложение из Windows Explorer или из командной строки. Я, честно говоря, так и не понял, почему возникает эта ошибка, но возникает она только в IDE CBuilder.

Рис. 17.4. Приложение просмотра и импорта классов в действии

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

Мы готовы создать Мастера

Ну что ж, теперь мы готовы к созданию нового, улучшенного Мастера компонентов. Идея его создания нравится мне по двум причинам. Во-первых, его описанием я смогу довести данную главу до объема, который удовлетворит моего редактора. Во-вторых, это станет вашей стартовой площадкой в написании Мастеров, в частности полноразмерного Мастера компонентов++. Наш Мастер компонентов, в отличие от поставляемого с CBuilder, позволит вам не только выбрать

Borland C++ Builder (+CD). Библиотека программиста 360

базовый класс, но и добавить в компонент еще много полезной информации он позволит нам «подсаживать», говоря в терминах CBuilder, свойства из базового класса. Он также позволит нам замещать методы в базовом классе. Кроме того, он позволит нам определять новые свойства и методы и добавлять их в класс компонента. Короче говоря, это новая версия Мастера компонентов

Мастер компонентов++.

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

Для того чтобы создать Мастера, надо сначала создать тот код, что делает его работу. Лучший способ сделать это создать код для него сначала в виде приложения, а потом этот код перенести на каркас Мастера (с чем я познакомлю вас несколько позже), создав приложение-Мастер. Последним шагом будет инсталляция Мастера мы и ее рассмотрим, закончив с кодом Мастера.

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

Наш Мастер будет реализован в виде страничного диалога, как это показано на рис. 17.5 — 17.7. Три странички, показанные на рисунках, собственно и образуют в совокупности страничный диалог нашего Мастера; на них представлено все, что нам надо знать о форме для нашего приложения.

Для создания формы воспользуйтесь пунктом меню File д New, в котором в разделе форм выберите форму страничного диалога. В этот диалог добавьте три странички, соответствующие показанным на рис. 17.5 — 17.7. Как только вы с этим закончите, можно будет перейти к написанию кода.

Рис. 17.5. Страница 1 формы Мастера компонентов

Соседние файлы в предмете Программирование на C++