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

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

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

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

1 Если вам на глаза попадется английский оригинал этой книги, будьте осторожны в ней почему-то вместо свойства Active упоминается несуществующее у TTable свойство Enable. —

Примеч. перев.

Замечание

Для всех полей, кроме строковых (или массивов символов), свойство Size будет равно 0. Все остальные поля имеют неявный размер, основывающийся на их реальном воплощении. Например, 16-битное числовое поле будет представлено 2-байтным целым, а 32-битное числовое поле будет представлено 4-байтным целым (или long). dBase уже знает обо всех этих полях, так что только символьные строковые поля должны быть определены при помощи свойства Size.

Последняя вещь в нашем коде, на которую стоит обратить внимание, — это преобразование номера поля в строку при помощи класса AnsiString. Хотя мы и рассмотрели класс string в STL, мы так толком и не поговорили об AnsiString. Этот класс поддерживает большинство методов класса string и используется в ситуации, когда требуется строка типа принятой в языке Pascal. Все методы VCL, работающие со строками, допускают AnsiString в качестве типа аргумента.

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

void __fastcall TForm1::Exit1Click(TObject *Sender)

{

Application->Terminate();

}

На этом программа просмотра баз данных успешно завершена. Скомпилируй те и запустите программу и выберите базу данных для просмотра. Хорошие примеры можно найти в подкаталоге CBuilder\Examples\Data основного каталога CBuilder. На рис. 7.2 показано окно программы просмотра с отображенным в нем файлом Clients.dbf.

Рис. 7.2. Выбранная пользователем база данных в окне программы просмотра

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

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

возможности получить напрямую ту информацию, которую вы без труда почерпнули в объекте

TTable.

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

Создание базы данных на пустом месте

После того как вы разобрались, как связаны поля в базе данных, логично было бы научиться объединять поля в базу данных, не так ли? Конечно, именно этого вы и добиваетесь. В конце концов, все, что вам надо сделать, — это выбрать каталог базы данных, установить корректное имя, добавить несколько полей и установить свойство Active в true, правда? Действительно, вы не так далеки от правды. Наверняка случится ситуация, когда вам понадобится создавать базу данных в соответствии с требованиями пользователя, которые нельзя узнать заранее. Отлов ошибок,

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

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

На рис. 7.3 показана форма, которую мы будем использовать для создания баз данных. Обратите внимание на несколько важных моментов. Во-первых, на форме нет компонентов, воспринимающих данные (если вам так уж интересно, отмечу, что сетка, лежащая на форме, относится к TStringGrid). Нет их потому, что мы не собираемся использовать основополагающие управляющие элементы для баз данных CBuilder в интерфейсе этой формы. Все, что мы будем делать, будет делаться на низком уровне доступа к базам данных. Кроме того, как можно видеть, на форме нет объекта TTable. Мы будем использовать TTable для создания базы данных, но в этом примере мы его создадим динамически, чтобы вы поняли, как это делается.

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

поставить ему в соответствие тип действительного (физического) поля для непосредственного описания поля в таблице. Итак, добавьте следующий код в начало исходного файла Unit1.cpp.

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

Рис. 7.3. Форма приложения создания баз данных

typedef struct

{

Db::TFieldType nCode; char *strDesc;

} DbFieldType; DbFieldType sFieldTypes[] =

{

{ftUnknown, "Неизвестно или не определено"}, {ftString, "Символьное или строковое поле"}, {ftSmallint, "16-битное целое поле"}, {ftInteger, "32-битное целое поле"},

{ftWord, "16-битное беззнаковое целое поле"}, {ftBoolean, "Логическое поле"},

{ftFloat, "Поле чисел с плавающей точкой"}, {ftCurrency, "Денежное поле"},

{ftBCD, "Двоично-кодированное десятичное поле"}, {ftDate, "Поле даты"},

{ftTime, "Поле времени"},

Глава 7•Работа с базами данных

{ftDateTime, "Поле даты и времени"},

{ftBytes, "Фиксированное количество байт (двоичное представление)"},

{ftVarBytes, "Переменное количество байт (двоичное представление"},

{ftAutoInc, "Автоматически увеличивающееся 32-битное целое поле счетчика"},

{ftBlob, "Поле Binary Large Object (большой двоичный объект)"},

{ftMemo, "Поле memo (строка неограниченной длины)"}, {ftGraphic, "Поле растрового рисунка"},

{ftFmtMemo, "Поле форматированного memo"}, {ftParadoxOle, "Поле Paradox OLE"}, {ftDBaseOle, "Поле dBase OLE"},

{ftTypedBinary, "Типизированное двоичное поле"}, };

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

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

#define NumberOfFieldTypes (sizeof(sFieldTypes)/sizeof\

(sFieldTypes[0]))

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

В случае массива элементов структуры каждый элемент имеет свой специфичес кий размер (обычно 8 байт, но это зависит от компилятора). Размер специфичес кого элемента тем не менее, если поделить на него произведение размеров, позволит получить общее количество элементов. Формула будет выглядеть примерно так:

int nTotalEntries = (nNumberOfEntries * nSizeOfOneEntry) / nSizeOfOneEntry;

Этот код, как могут засвидетельствовать те из вас, кто силен в математике, сводится к тождеству nTotalEntries = nTotalEntries. Это все, что мы используем в блоке #define, описанном выше, не заботясь о том, чему равны nNumberOfEntries и nSizeOfOneEntry. Чудесная штука, этот оператор sizeof.

Следующее, что нам надо сделать, — это навести порядок в системе. Нам надо изменить конструктор формы, вставив туда объект TTable, который мы будем использовать, а также кое-что убрать из этого объекта, чтобы его действительно можно было использовать для создания таблицы. Добавьте следующий код в конструктор объекта Form1.

Разбираемся во внутреннем устройстве базданных

__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)

{

Table = new Table(this);

Table->Active = false;

Table->FieldDefs->Clear();

Table->IndexDefs->Clear();

}

Как видно из приведенного выше кода, мы создаем объект TTable в конструкто ре формы. Тогда это позволит нам позже использовать его напрямую. Строго говоря, вызовы Clear для массивов описаний полей и индексов не являются здесь необходимыми, поскольку эти объекты при создании по умолчанию являются пустыми. Однако хорошей практикой будет не полагаться ни на что при работе с объектами. Точно так же объект TTable создается со свойством Active, установлен ным в false. Все же, чтобы подстраховаться и не зависеть от предпосылок о верности инициируемых по умолчанию значений, мы сами устанавливаем все, что нужно в конструкторе.

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

инициализируйте все сами.

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

новый обработчик для события формы OnCreate и в соответствующий метод FormCreate добавьте следующий код:

void __fastcall TForm1::FormCreate(TObject *Sender)

{

StringGrid->ColCount = 5; StringGrid->Cells[0][0] = "Поле"; StringGrid->Cells[1][0] = "Имя"; StringGrid->Cells[2][0] = "Тип"; StringGrid->Cells[3][0] = "Длина"; StringGrid->Cells[4][0] = "Обязательное"; StringGrid->RowCount = 1;

// Загружаем комбинированный список for (int i=0; i<NumberOfFieldTypes; ++i)

ComboBox->Items->Add(sFieldTypes[i].strDesc);

}

}

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

Итак, мы можем видеть перед собой форму, содержащую пустую сетку и комбинированный список описаний типов полей, поддерживаемых системой. Следу

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

Добавляем поля в таблицу

Процесс добавления нового поля в таблицу выглядит примерно следующим образом. Сначала

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

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

//———————————————————————————— void __fastcall TForm1::Button2Click(TObject *Sender)

{

char szBuffer[ 80 ];

strcpy (szBuffer, Edit2->Text.c_str());

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

unsigned short nSize = (unsigned short)atoi(szBuffer); // Проверяем, нужно ли задавать размер

if (sFieldTypes[ComboBox1->ItemIndex].nCode != ftString) nSize = 0;

// Устанавливаем описания полей

Table1->FieldDefs->Add(Edit1->Text, sFieldTypes[ComboBox1->ItemIndex], nCode, nSize, CheckBox1->Checked);

//Переходим к следующей строке

StringGrid1->RowCount++;

//Теперь заполняем сетку данными int nRow = StringGrid1->RowCount-1;

StringGrid1->Cells[0][nRow] = AnsiString(nRow); StringGrid1->Cells[1][nRow] = Edit1->Text; StringGrid1->Cells[2][nRow] = sFieldTypes[ComboBox1->ItemIndex].strDesc; StringGrid1->Cells[3][nRow] = Edit2->Text;

if (CheckBox1->Checked) StringGrid1->Cells[4][nRow] = "Yes"; else

StringGrid1->Cells[4][nRow] = "No";

}

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

Есть две важные вещи, которые надо отметить при работе с полем размера. Во-первых, никогда не передавайте текст для поля редактирования прямо в функцию, даже используя метод c_str() объекта TEdit. При этом небезопасно изменять строку прямо в поле объекта TEdit. По этой

причине мы копируем строку в обычный символьный буфер и используем его для получения размера поля.

Вторым важным аспектом, касающимся добавления размера полю, является дилемма разрешен размер или нет. Для всех полей, кроме полей строкового (символьного) типа, размер не только не используется, но и не разрешается для использования. Если вы позволите пользователю ввести размер для не строкового поля и передадите его в метод Add объекта field definitions, то получите ошибку, сгенерированную объектом, и метод не будет выполнен. По этой причине мы проверяем тип поля перед тем, как добавить размер, и для не строковых типов устанавливаем размер

(который, кстати, типа unsigned short, а не int) в 0.

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

Защищаем пользователя

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

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

Добавьте в форму новый обработчик для события OnChange (при изменении) комбинированного списка. Следующий код добавьте в метод ComboBox1Change формы:

void __fastcall TForm1::ComboBox1Change(TObject *Sender)

{

if (sFieldTypes[ComboBox1->ItemIndex].nCode != ftString)

{

Edit2->Enabled = false;

}

else

Edit2->Enabled = true;

}

Создаем базу данных

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

Добавьте следующий код в метод Button1Click (Button1 — кнопка Создать, служит для создания базы данных):

void __fastcall TForm1::Button1Click(TObject *Sender)

{

//Устанавливаем название базы данных и имя таблицы

Table1->DatabaseName = Edit3->Text; Table1->TableName = Edit4->Text;

//Устанавливаем тип таблицы в dBase

Table1->TableZType = ttDBase;

//Создаем таблицу

Table1->CreateTable();

//Удаляем объект delete Table1;

//Уведомляем пользователя

MessageBox1(NULL, "База данных создана!",

"Подтверждение", MB_OK); // Закрываем приложение

Application->Terminate();

}

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

Первый шаг в процессе создания базы данных это присвоение имени базы данных и таблицы. Это определяет, где же на самом деле на диске будет создана база. Вы можете ввести любое допустимое название каталога и файла, в том числе и сетевой каталог. После присвоения имен нам надо установить тип создаваемой базы данных. Система CBuilder по умолчанию умеет создавать таблицы dBase и Paradox. С соответствующим ODBC-драйвером вы можете создавать также

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

другие типы таблиц.

После того как база данных была успешно создана (обратите внимание, что в коде примера нет проверки возможных ошибок, которую вам обязательно надо будет осуществлять в ваших приложениях), объект TTable удаляется, чтобы избежать потери памяти в системе, а пользователь извещается о создании базы. После этого приложение закрывается. Теоретически, вам не обязательно прекращать работу приложения в этот момент. Вы могли бы создать и

инициализировать новый объект TTable и позволить пользователю создавать много баз данных за один запуск программы. Однако для первого примера мы применяем простейший метод выхода.

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

Обработка событий базы данных

В комплекте CBuilder поставляется замечательный инструмент, называемый Мастером форм баз данных (Database Form Wizard). Вы можете вызвать его, выбрав пункт меню Database, а в нем команду FormWizard. Вы увидите Мастера форм баз данных, который позволит вам выбрать существующий файл базы данных, выбрать из базы поля, с которыми хотите работать, и определить тип формы просмотра баз данных горизонтальный, вертикальный или сеточный. После этого Мастер прекращает работу, создав законченную новую форму с файлом формы (DFM), исходным файлом (CPP) и заголовочным файлом (HPP). Все эти файлы добавляются прямо в ваш проект.

Конечно же, CBuilder не первая система, поддерживающая подобное автоматическое создание форм, но это один из немногих пакетов быстрой разработки приложений (RAD), который способен создать законченную систему, не требующую дополнительного кодирования для добавления данных. Что более важно, CBuilder посредством программирования событий и перехватов сообщений (hooks) предоставляет в распоряжение программисту богатые возможности по проверке,манипулированию и даже изменению данных до или после их ввода в базу данных.

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

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

Создайте новую базу данных (в каком душе угодно каталоге) стиля адресной книги. Для этого создайте в ней поля и типы в соответствии с табл. 7.1.

Таблица 7.1. Формат полей базы данных адресов

Last Name

Символьное поле в 40 символов

First Name

Символьное поле в 40 символов

Address Line 1

Символьное поле в 40 символов

Address Line 2

Символьное поле в 40 символов

City

Символьное поле в 60 символов

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

State

Символьное поле в 10 символов

ZIP Code

Символьное поле в 12 символов

Phone Number Символьное поле в 14 символов

После того как при помощи программы создания баз данных создана новая таблица,

воспользуйтесь мастером форм баз данных для создания новой формы для нашего нового приложения. Лично я выбрал все поля и вертикальное расположение в качестве параметров формы. Но о вкусах не спорят вы, возможно, захотите сделать как-то по-другому.

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

Рис. 7.4. Форма контроля базы данных с панелью состояния

Отслеживаем изменения

Теперь, когда панель состояния находится на форме, надо отследить индивидуальные изменения, сделанные пользователем в записях базы данных. Для того чтобы что-то отследить, нам надо кое- что узнать о том, когда и какие события происходят в «жизни» записи.

Когда пользователь нажимает кнопку + объекта TDBNavigator, расположенно го на форме, новая запись добавляется в базу данных. Изменение существующей записи происходит, когда пользователь переходит к какой-либо записи, используя одну из кнопок перемещения объекта TDBNavigator, и изменяет ее перед тем, как перезаписать в базу. В обоих случаях событие, которое возникает в базе данных, называется Post (прописывание). Событие Post возникает всякий раз, когда запись пишется в базу данных, вне зависимости от того, в первый раз или после изменения.

Если посмотреть на список событий, возможных для объекта-таблицы, ассоциированного с формой, то в нем не найдется различных событий для обновления и добавления записи. Вместо этого есть события AfterInsert (после добавления) и AfterPost (после записи). Событие AfterInsert возникает после того, как по нажатию пользователем кнопки Insert (+) новая запись добавляется в базу. Мы не можем определить, вызывается ли Post в результате добавления новой

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

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

Измените заголовочный файл формы, добавив в описание формы новые переменные, как показано ниже. Заодно обратите внимание на переменные, уже добавленные туда мастером форм. Немало работы он за вас проделал, не правда ли?

private; // private declarations int FnNumAdds;

int FnNumUpdates; int FnNumDeletes; bool FbUpdateMode;

void UpdateStatusBar(void);

Не удивляйтесь, что здесь же находится функция UpdateStatusBar (обновить панель состояния),

мы собираемся использовать ее для переноса новых данных в блоки панели состояния формы.

Следующим шагом будет инициализация всех переменных в конструкторе формы. Все счетчики, естественно, устанавливаются в 0, но флаг режима (FbUpdateMode) установлен в значение false. Почему? Когда вы изначально откроете форму, CBuilder добавит новую запись для введения данных. Если пользователь переместится к какой-нибудь другой записи и изменит ее, мы это все равно отследим. Вот полный код для конструктора:

__fastcall TForm2::TForm2(TComponent *Owner) : TForm(Owner)

{

FnNumAdds = 0; FnNumUpdates = 0; FnNumDeletes = 0; FbUpdateMode = false;

}

После того как все значения инициализированы, мы добавим алгоритм для логической функции обновления панели состояния. Вот код, в котором мы просто присваиваем каждой секции (panel) панели состояния новые данные по отображаемой ей информации:

void TForm2::UpdateStatusBar(void)

{

StatusBar1->Panels->Items[0]->Text = "Добавлено записей: " + AnsiString(FnNumAdds); StatusBar1->Panels->Items[1]->Text =

"Удалено записей: "+ AnsiString(FnNumDeletes); StatusBar1->Panels->Items[2]->Text =

"Изменено записей: " + AnsiString(FnNumUpdates);

}

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

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