Главная » C++, C++ Builder » Сохранение данных – приложение просмотра классов в CBuilder

0

Изменения, которые нам надо внести в форму нашего приложения просмотра классов для обеспечения          постоянного          хранения           данных,           достаточно           прямолинейны. В правый нижний угол формы мы добавим кнопку с заголовком Импорт. Эта кнопка будет использоваться пользователем для импорта текущего выбранного класса в базу данных. Кроме кнопки, мы добавим на форму три объекта 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, чтобы можно было напрямую использовать их в нашем коде.

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

Теперь, когда все таблицы на форме полностью определены, пришло время написать код для занесения данных в таблицы. Для этого нам надо добавить обработчик  для  события  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"]  =

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

строки, числа или логические значения. Внутри базы данных тип поля определяет, может ли заданный тип данных быть преобразован в тип, хранимый в базе данных. Для того чтобы было проще работать с данными символьных строковых типов, можно использовать тип 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,  позволит  вам  не  только  выбрать

базовый класс, но и добавить в компонент еще много полезной информации  — он позволит нам

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

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

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

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

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

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

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

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

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

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

Начнем мы, естественно, с первой странички. Нам надо загрузить в находящийся на ней комбинированный список классов классы, находящиеся в созданной нами ранее базе данных. Это стоит сделать  в самом начале — при создании формы, то есть в обработчике события Create формы. Итак, добавьте обработчик для этого события, назвав его FormCreate, а в него добавьте следующий код:

void __fastcall TPagesDlg::FormCreate(TObject *Sender)

{

// Загружаем комбинированный список

Table1->First();

while ( !Table1->Eof )

{

ComboBox1->Items->Add(Table1->FieldValues["CLASSNAME"]); Table1->Next();

}

}

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

void __fastcall TPagesDlg::ComboBox1Change(TObject *Sender)

{

// Получаем имя выбранного класса

Table1->Filter = "CLASSNAME = ‘" + ComboBox1->Text + "’"; Table1->Filtered = true;

// Переходим на эту запись

Table1->Last();

// Получаем идентификатор класса

AnsiString strClassId = Table1->FieldValues["ClassID"]; Table1->Filtered = false;

// Загружаем свойства

Table2->Filter = "CLASSID = ‘" + strClassId + "’"; Table2->Filtered = true;

Table2->First(); ListBox1->Clear(); while ( !Table2->Eof )

{

ListBox1->Items->Add( Table2->FieldValues["Method"] ); Table2->Next();

}

// Загружаем методы

Table3->Filter = "CLASSID = ‘" + strClassId + "’"; Table3->Filtered = true;

Table3->First(); ListBox3->Clear(); while ( !Table3->Eof )

{

ListBox3->Items->Add( Table3->FieldValues["PropertyNa"]  ); Table3->Next();

}

}

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

Следующая группа управляющих элементов, которую нам надо обработать, — это кнопки перемещения элементов между списками выбранных и существующих свойств (методов). Эти кнопки будут использоваться для внесения свойств и замещения методов базового класса в наш класс. Во вторую страничку нашего диалога добавьте два обработчика для кнопок > и < (кнопки 3 и 4 формы):

void __fastcall TPagesDlg::Button3Click(TObject *Sender)

{

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

if ( ListBox1->ItemIndex != -1 )

{

ListBox2->Items->Add(  ListBox1-> Items->Strings[ListBox1->ItemIndex]  );

ListBox1->Items->Delete(  ListBox1->ItemIndex );

}

}

//——————————————————–

void __fastcall TPagesDlg::Button4Click(TObject *Sender)

{

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

if ( ListBox2->ItemIndex != -1 )

{

ListBox1->Items->Add(  ListBox2-> Items->Strings[ListBox2->ItemIndex]  );

ListBox2->Items->Delete(  ListBox2->ItemIndex );

}

}

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

void __fastcall TPagesDlg::Button6Click(TObject *Sender)

{

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

if ( ListBox3->ItemIndex != -1 )

{

ListBox4->Items->Add(  ListBox3-> Items->Strings[ListBox3->ItemIndex]  );

ListBox3->Items->Delete(  ListBox3->ItemIndex );

}

}

//——————————————————

void __fastcall TPagesDlg::Button7Click(TObject *Sender)

{

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

if ( ListBox4->ItemIndex != -1 )

{

ListBox3->Items->Add(  ListBox4-> Items->Strings[ListBox4->ItemIndex]  );

ListBox4->Items->Delete(  ListBox4->ItemIndex );

}

}

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

Источник: Теллес М. – Borland C++ Builder. Библиотека программиста – 1998

По теме:

  • Комментарии