Главная » C++, C++ Builder » Обобщенная программа просмотра баз данных в CBuilder

0

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

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

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

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

{

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 =

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;

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

{

ListBox1->Items->

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

}

}

}

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

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

void __fastcall TForm1::ListBox1Click(TObject *Sender)

{

if (ListBox1->ItemIndex != -1 && ListBox2->ItemIndex != -1) DoSQLProcess();

}

Теперь все, что нам осталось сделать, — это определить метод DoSQLProcess. Этот метод создает динамический запрос SQL (Structural Query Language, структури рованный язык запросов) и связывает его с объектом TQuery, который мы используем для загрузки данных в сетку. Давайте сначала взглянем на код метода DoSQLProcess, а потом поговорим о том, как он работает:

void  TForm1::DoSQLProcess(void)

{

int nList1Idx = ListBox1->ItemIndex; AnsiString s1 =

ListBox1->Items->Strings[nList1Idx].c_str();

int nList2Idx = ListBox2->ItemIndex; AnsiString s2 =

ListBox2->Items->Strings[nList2Idx].c_str(); AnsiString s = "SELECT * FROM \"";

s += Table1->DatabaseName.c_str(); s += Table1->TableName.c_str();

s += "\" as T1";

s += ",";

s += "\"";

s += Table2->DatabaseName.c_str(); s += Table2->TableName.c_str();

s += "\" as T2";

s += " WHERE ";

s += "T1." + s1 + " = T2. " + s2;

// Присваиваем строку SQL Query1->SQL->Add(s); Query1->Active = true;

}

Как это работает?

Свойство SQL объекта TQuery представляет собой список строк, образующих команду SQL, которую мы хотим передать базе данных. Язык SQL как таковой не относится к предмету рассмотрения данной книги, но детали этого примера представляют для нас интерес. Сначала мы создаем строку, состоящую из стандартно го оператора SQL SELECT:

SELECT * from db1, db2

где db1 — это имя первой таблицы базы данных, а db2 — имя второй. Беда в том, что мы не используем таких простых обозначений, как db1 или db2. Вместо этого имена таблиц имеют вид диск:\каталог\имятаблицы.dbf . Это существенно усложняет работу с именами таблиц. Язык SQL максимально нагляден, поэтому мы заключаем имена баз данных в кавычки. Первая часть оператора выглядит как

SELECT * from "d:\directory1\table1.dbf", "d:\directory2\table2.dbf"

что является обычным оператором SQL. Следующая часть оператора — предложение WHERE, которое выглядит примерно как db1.field = db2.field. Это предложение возвращает все записи, в которых поле в db1 идентично полю в db2. Проблема в том, что SQL сломается и умрет на утверждении типа:

WHERE d:\directory1\table1.dbf.field = d:\directory2\table2.dbf.field

Для того чтобы справиться с этой проблемой, мы используем оператор SQL, определяющий псевдонимы (alias statements, AS), который позволяет нам обращаться к таблице по более короткому имени. В нашем случае мы именуем обращение к первой таблице как t1, а ко второй — как t2. После этого мы можем обращаться к полям как t1.field и t2.field.

После того как строки SQL созданы, мы добавляем их в поле Strings объекта TQuery, а затем делаем запрос активным. Это повлечет за собой выбор заданных нами записей из таблиц и их загрузку в сетку. Все очень просто, не так ли? Да, просто, но только если вы знаете, что и как сделать.

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

По теме:

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