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

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

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

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

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

Чтобы обработать факт того, что произошло событие Post (а это все, что нам надо сделать), добавьте обработчик события AfterPost (по факту записи) объекта TTable. В этот обработчик добавьте следующий код:

void __fastcall TForm2::Table1AfterPost(TDataSet *DataSet)

{

if (FbUpdateMode) FnNumUpdates++; else FnNumAdds++; UpdateStatusBar();

}

Если пользователь добавляет новую запись в базу данных, объектом TTable генерируется событие AfterInsert (по факту вставки). Между прочим, для каждого события After (по факту, после) есть соответствующее событие Before (перед, до). Таким образом, если бы вы захотели обработать события BeforeInsert или BeforePost (к которому мы вскоре обратимся), вы могли бы добавить обработчики и для них.

Добавьте следующий код в обработчик события AfterInsert, чтобы отследить факт добавления новой записи в базу:

void __fastcall TForm2::Table1AfterInsert(TDataSet *DataSet)

{

FbUpdateMode = false;

}

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

void __fastcall TForm2::Table1AfterEdit(TDataSet *DataSet)

{

FbUpdateMode = true;

}

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

void __fastcall TForm2::Table1AfterDelete(TDataSet *DataSet)

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

{

FnNumDeletes ++; UpdateStatusBar();

}

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

void __fastcall TForm2::Table1AfterPost(TDataSet *DataSet)

{

if (FbUpdateMode) FnNumUpdates++; else FnNumAdds++; UpdateStatusBar;

}

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

Проверка данных

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

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

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

Событие Post возникает, когда происходит изменение записи базы данных. Подсистема работы с базами данных CBuilder сгенерирует событие BeforePost до того, как запись будет внесена в базу данных. Здесь вам и предоставляется шанс проверить введенные данные перед тем, как разрешить дальнейшее прохождение процесса. Если проблем не возникло, вы просто разрешаете продолжение процесса с его обработкой по умолчанию (которая состоит в физическом занесении записи обратно в файл базы данных). С другой стороны, если проблемы с обработкой данных в

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

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

void __fastcall TForm2::Table1BeforePost(TDataSet *DataSet)

{

// Сначала проверим, введено ли значение ZIP-кода if (Table1ZIPCODE->Text.IsEmpty())

{

MessageBox(NULL,"Необходимо ввести ZIP-код!", "Ошибка",MB_OK);

Abort();

return;

}

//Удостоверяемся, что для ZIP-кода введено

//числовое значение

std::string s = Table1ZIPCODE->Text.c_str(); for (int i=0; i<s.size(); ++i)

{

if (!isdigit(s[i]))

{

MessageBox(NULL,

"ZIP-код должен иметь числовое значение!", "Ошибка",MB_OK);

Abort();

return;

}

}

//Все в порядке, разрешаем дальнейшее прохождение

//процесса занесения записи в базу данных

}

В качестве примечания скажу, что для того, чтобы скомпилировать приведенный выше код, вам надо включить <string.h> в начало вашего исходного файла, чтобы получить определение класса string STL.

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

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

Ответ состоит в том, что CBuilder автоматически генерирует исключительную ситуацию (exception), когда вы вызываете метод Abort. Это не значит, что исключительная ситуация не обрабатывается, в чем вы можете убедиться, сообщив, что желаете продолжить работу программы. Все дело в том, что вы велели программе останавливаться в случае возникновения исключительных ситуаций. Вы не помните, чтобы делали такое? Ну, по правде говоря, вы этого и не делали. CBuilder

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

устанавливает этот флаг по умолчанию. Если вас это не устраивает, как не устраивает большинство программистов, вы можете его изменить. Выберите команду меню Options|Environment и взгляните на область Debugging на первой странице свойств. Снимите флажок Break on exception (останов при исключительной ситуации) и снова запустите программу. Она больше не будет выдавать пугающих сообщений об исключительных ситуациях, а будет работать так, как вы от нее ожидали.

Проверка любых других полей на соответствие данных может быть осуществлена тем же путем. Проверка при удалении (проверка соответствующих записей и тому подобное) может производиться в совершенно аналогичном обработчике события BeforeDelete.

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

CBuilder.

Я хочу сделать это сам!

У меня две маленькие дочки, и они часто заявляют, что хотят сделать что-нибудь сами, несмотря на то, что мне кажется, что у них это не получится. И что же я постоянно не угадываю, и они справляются лучше, чем справился бы я сам. Мне кажется, таков уж удел родителей. Фирма Borland столкнулась с такой же проблемой, когда ее программисты стали писать объекты VCL для работы с базами данных. Многие программисты напоминают моих дочерей в своем нежелании допустить, чтобы VCL делала всю работу по изменению, записи и удалению в базах данных посредством объекта DBNavigator. Они предпочитают сами проделать всю работу по перезаписи базы данных при добавлении, изменении или удалении. CBuilder позволяет вам делать собственноручно все, что вы сами пожелаете.

На рис. 7.5 показана форма, которую мы будем использовать в нашем следующем примере. Она сделана по образу и подобию формы, которую сгенерировал Мастер форм баз данных CBuilder.

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

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

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

void __fastcall TForm1::FormCreate(TObject *Sender)

{

// Добавляем новую запись в базу

Table1->Append();

}

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

Рис. 7.5. Форма редактора баз данных

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

void __fastcall TForm1::Button1Click(TObject *Sender)

{

if (Table1->State == dsInsert || Table1->State == dsEdit) Table1->Post(); Table1->Append();

}

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

отличие от некоторых других компонентных систем, не доверяет макросу ASSERT и не останавливает программу), но и не будет вести себя так, как вы от нее ожидаете. Поэтому мы проверяем режим, в котором находится база данных, исследуя, соответственно, свойство State (состояние, здесь режим). Если текущим является режим редактирования или добавления, мы разрешаем выполнение Post, в противном случае мы игнорируем текущие изменения в записи.

Кнопка Удаление действует абсолютно прямолинейно. Вот код, обрабатываю щий нажатие кнопки Удаление:

void __fastcall TForm1::Button2Click(TObject *Sender)

{

Table1->Delete;

}

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

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

Кнопки перемещения по базе вперед (>) и назад (<) также не сложны. Они лишь проверяют, не находимся ли мы в конце или начале базы данных соответственно. Если не верно ни то ни другое, мы перемещаемся в указанном направлении к следующей записи. Вот обработчики для кнопок:

void __fastcall TForm1::Button3Click(TObject *Sender)

{

if (!Table->Bof) Table1->Prior();

}

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

{

if (!Table->Eof) Table1->Next;

}

Обратите внимание на использование свойств базы данных Bof (Beginning of File, начало файла) и Eof (End of File, конец файла) для проверки, сможем ли мы выполнить просьбу пользователя.

На этом написание примера закончено. Я думаю, вы убедились, насколько просто можно работать с базами данных на том уровне доступа, на котором вы хотите. Вы даже можете убрать поля DBEdit и заменить их обычными полями редактиро вания (Edit). Если вы решите так сделать, вам надо будет присваивать значения полям с помощью свойств FieldValues базы данных, например:

Table1->Fields["LASTNAME"] = "Иванов";

Эта строка установит значения поля с фамилиями (LASTNAME) в Иванов (не так уж редко встречающееся значение). Это все, что делает компонент DBEdit. Аналогично, чтобы установить поле редактирования равным значению поля базы данных, вам надо написать:

Edit1->Text = Table1->Fields["LASTNAME"];

Вот, собственно, и все с редактированием баз данных. Теперь давайте обратимся к проблеме просмотра данных в полном объеме.

Обобщенная программа просмотра баз данных

Одной из самых замечательных особенностей системы поддержки баз данных в CBuilder является возможность использовать один и тот же код для доступа к различным наборам данных. Чувствительные к данным компоненты (data-aware controls), поставляемые с CBuilder, выполняют впечатляющую работу по обработке, отображению и обработке изменений большого спектра данных, хранящихся в любых типах, поддерживаемых CBuilder базах данных (а поддерживается большинство из существующих типов).

В этом примере мы создадим универсальную программу просмотра баз данных. Эта программа

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

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

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

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

VCL.

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

void __fastcall TForm1::OpenClick(TObject *Sender)

{

if (OpenDialog->Execute())

{

if (Table->Active == true) Table->Active = false; Table->DatabaseName = ExtractFilePath(OpenDialog->FileName); Table->TableName =

ExtractFileName(OpenDialog->FileName); Table->Active = true; RecordCount->Caption = AnsiString((int)Table1->RecordCount);

}

}

Рис. 7.6. Форма просмотра баз данных

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

В данном коде мы сначала проверяем, не открыта ли уже база данных, поскольку пользователь может сначала открыть одну базу, а потом решить открыть еще одну. Чтобы не раздражать CBuilder попытками изменить имя базы данных и имя таблицы у уже открытой базы, мы закрываем ее, устанавливая свойство Active в false. Код для определения имени базы данных и имени таблицы совершенно схожий с тем, что мы использовали ранее. Мы устанавливаем свойство Active в true для открытия выбранной базы данных и затем отображаем количество записей в ней, получая значение свойства RecordCount таблицы.

Замечание

Не все базы данных поддерживают свойство RecordCount. В некоторых случаях вам надо сначала пройтись по всем записям, чтобы свойство RecordCount приняло корректное значение. И все же большинство баз данных для IBM PC вернут вам правильное значение в данном случае.

Приведенный выше код не делает практически ничего, кроме как открытия базы данных. Почему же, запустив эту же программу с прилагаемого компакт-диска, вы увидите, что сетка данных, расположенная на форме, автоматически загрузит в себя и отобразит все записи из выбранной базы? Дело в том, что вы упустили один шаг (точнее, я не сказал вам о нем, что одно и то же). Расположен ный на форме объект DBGrid имеет свойство DataSource, которое представляет объект-источник данных, находящийся на форме, из которого DBGrid и получит данные. Точно так же у объекта DataSource есть свойство DataSet, представляющее таблицу базы данных (или запрос), из которой он получит данные. Установите свойство DataSet вашего объекта DataSource в Table1. Установите его же свойство Enabled в true, чтобы позволить ему загружать данные. Затем установите свойство DataSource объекта DBGrid в DataSource1, что является именем объекта DataSource, расположенного на форме. Этим вы завершите цепочку между базой данных и сеткой данных.

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

Для того чтобы отфильтровать данные, пользователь должен ввести выражение в поле редактирования, которое представляет критерий фильтрования. Критерий для фильтра имеет вид «поле выражение значение». Поле это имя одного из полей, отображенных в сетке данных. Выражение это оператор сравнения, такой как =, <> (не равно), <, >, >=, <= или like (подобно). И наконец, значение это то значение, с которым вы проводите сравнение. Если вы работаете с адресной книгой из одного из предыдущих примеров и хотите увидеть данные о тех, кто не живет в Нью-Йорке (интересно, а много ли ваших друзей там живет), вам надо ввести следующую строку:

State <> "NY"

Здесь используется поле State базы данных, оператор <> (не равно) и строка NY. Отметьте, что поскольку строка в результате будет заключена в двойные кавычки, то если вы используете строку (в данном случае NY), то должны заключить ее в одинарные кавычки.

Вот полный код для кнопки применения фильтра, который надо добавить в обработчик события для нее OnClick:

void __fastcall TForm1::Button1Click(TObject *Sender)

{

if (Table1->Active == true)

{

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

Table1->Filtered = true; Table1->Filter = Edit1->Text; RecordCount->Caption = AnsiString((int)Table1->RecordCount);

}

}

Этот код просто устанавливает фильтр в таблице; установкой флага Filtered в true сообщает таблице, что надо воспользоваться свойством Filter. Это заставит таблицу отфильтровать все записи, у которых соответствующие поля не удовлетворяют заданному значению. Кроме того, мы заново переопределяем количество записей, которое указано у нас на форме. Очень приятной вещью является то, что при фильтровании автоматически пересчитывается количество записей, — считаются лишь те, которые удовлетворяют критерию фильтра.

На этом написание универсальной программы просмотра баз данных закончено; чуть больше десятка строк кода, парочка положенных на форму компонентови готово! Этот пример, как ни один другой в этой главе, демонстрирует действительную мощь компонентов VCL CBuilder, работающих с базами данных.

Просмотр нескольких таблиц

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

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

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

Для того чтобы позволить пользователю открыть первый файл базы данных, добавьте следующий код для обработчика события OnClick кнопки ВыборТаблицы1 :

void __fastcall TForm1::Button1Click(TObject *Sender)

{

OpenDialog1->Filter = "Файлы dBase|*.dbf"; if (OpenDialog1->Execute())

{

// Пытаемся открыть выбранную базу данных

Table1->DatabaseName =

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

ExtractFilePath(OpenDialog1->FileName); // Устанавливаем имя таблицы

Table1->TableName = ExtractFileName(OpenDialog1->FileName); // Делаем ее активной

Table1->Active = true;

for (int i=0; i<Table1->FieldCount; ++i)

{

ListBox1->Items-> Add(Table1->FieldDefs->Items[i]->Name);

}

}

}

Рис. 7.7. Форма просмотра нескольких таблиц

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

нажатия на кнопку ВыборТаблицы2 выглядит практически так же. Вот его конкретный вид:

void __fastcall TForm1::Button2Click(TObject *Sender)

{

OpenDialog1->Filter = "Файлы dBase|*.dbf"; if (OpenDialog1->Execute())

{

//Пытаемся открыть выбранную базу данных

Table2->DatabaseName = ExtractFilePath(OpenDialog1->FileName);

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

Table2->TableName = ExtractFileName(OpenDialog1->FileName); // Делаем ее активной

Table2->Active = true;

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