Вы находитесь на странице: 1из 318

Borland/!

Builderv
разработка приложений баз данных

Базовые принципы и рекомендации для проектирования реляционных баз данных


Варианты подключения к базам данных различных типов при помощи механизмов
BDE, InterBase Express, ADO Express (dbGo) и dbExpress
Максимальное использование возможностей компонентов-наборов данных
(таблиц, запросов)
Возможности управления сеансами связи и сессиями
Приемы использования элементов управления и визуализации данных
Описание языков запросов локального SQL и SQL стандарта ANSI-92
Стандартные и нестандартные приемы использования однонаправленных наборов
данных из набора dbExpress
Клиентские наборы данных и методы их использования с различными
механизмами доступа к БД
Портфельная модель данных и файловая модель MyBase
Разработка отчетных и аналитических форм при помощи компонентов из
наборов Quick Report и Decision Cube
Большое количество примеров, иллюстрирующих описанные концепции
ББК 32.973.2
УДК 681.3.06(075)
П-68
Послед Б.С.
П-68 Borland C++ Builder 6. Разработка приложений баз данных —
СПб.: ООО «ДиаСофтЮП», 2003 — 320 с.
ISBN 5-93772-094-6
Данная книга предназначена для тех, кто предполагает использовать один из самыз
популярных продуктов Borland C++ Bulder 6 для проектирования приложений баз данных
Рассматриваются основные вопросы создания клиентских приложений, взаимодействую
щих как с локальными, так и с клиент-серверными базами данных. На примере баз дан
ных Paradox и Interbase изучаются различия между локальными БД и БД в формате SQL
Приводится большое количество примеров.
Книга состоит из пяти глав. Первая глава вводит основные понятия, связанные с ба-
зами данных. Здесь приводится классификация БД, принципы построения реальных Б^
и рассматриваются этапы разработки. Следующие две главы посвящены использовании:
мезанизма BDE для проектирования приложений, взаимодействующих с локальными
базами данных. Подробно описаны основные компоненты из набора BDE, приведены ис-
черпывающие сведения о локальном SQL. В четвертой главе на примере использования
сервера Interbase рассматриваются особенности БД в формате SQL, приведены сведения
о языке запросов стандарта SQL-92. Далее описываются приемы проектирования прило-
жений БД с использованием механизма dbExpress, рассмотрены модели данных MyBase и
Briefcase. Последняя, пятая глава, посвящена описанию способов построения отчетных и
аналитических форм с использованием компонентов Quick Report и Decision Cube.
Книга рассчитана на пользователей средней квалификации.
ББК 32.973.2

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

ISBN 5-93772-094-6 © Б.С.Послед, 2003


© ООО «ДиаСофтЮП», 2003
Гигиеническое заключение №77.99.6.953.П.438.2.9. от 04.02.1999
"

От автора 7

Глава 1. Базы данных и вспомогательные


инструментальные средства C++ Builder 6 10
Базы данныхгклассификация и этапы разработки 12
Подходы к классификации 12
Основные этапы разработки 14
SQL Explorer 20
Создание и использование псевдонимов 21
Словари и наборы атрибутов 24
Database Desktop 26
Создание и модификация таблиц 26
Резюме 28

Глава 2. Использование механизма ВОЕ 29


Создаем простое приложение.. 30
Использование компонента ТТаЫе 34
Активация и деактивация набора данных 35
Свойства подключения к таблице 36
Таблица Items 37
Навигация по набору данных 40
Поля (Fields) 47
Фильтрация 50
Поиск 60
Сортировка набора данных 66
Изменение данных 67
Использование закладок 70
Состояние набора данных и компонент TDataSource 72
Объект TField и редактор полей 75
Свойства, методы и события класса TField 78
Постоянные и динамические поля. Редактор полей 82
Вычисляемые поля (Calculated Fields) 85
Borland C++ Builder 6. Разработка приложений баз данных

Поля подстановки (Lookup Fields) 8i


Организация связи между таблицами 9
Режим кэширования изменений 9J
Создание таблиц «на лету» 10(
Резюме 102

Глава 3. Использование механизма ВОЕ


(продолжение) 103
Модули данных 101
Управление подключением к базе данных
при помощи объекта TDatabase 10Е
Основные свойства компонента TDatabase 106
Транзакции и BDE 10S
Получение имен таблиц и полей базы данных 111
События компонента TDatabase 113
Управление сеансами связи при помощи компонента TSession — 114
Свойства и события компонента TSession 115
Использование паролей для доступа к таблицам Paradox и dBASE.... 117
Управление сеансами связи 119
Получение информации о соединениях 121
Элементы визуализации и управления данными 128
Простейшие средства управления данными 128
Списки, комбинированные списки и группы 130
Диаграмма (компонент TDBChart) 131
Компонент TDBCtrlGrid 135
Использование сетки (компонент TDBGrid) 136
Свойство Columns. Объекты TColumn 136
Параметры сетки 144
Приемы использования событий сетки 149
Пример использования события OnTitleClick 149
Оформление отдельных ячеек сетки при помощи события
OnDrawColumnCell 153
Запросы и компонент TQuery 159
Краткие сведения о языке запросов SQL 159
Некоторые соглашения 160
Встроенные комментарии .' 160
Форматы констант даты и времени 760
Формат логических констант 161
Оглавление 5

Язык манипуляции данными (DML) 161


Оператор выборки (Select) 161
Предложение Select 161
Предложение From 762
Предложение Where 765
Предложение Group By. 768
Предложение Having 768
Предложение Order By 769
Оператор удаления (Delete) 169
Оператор вставки (Insert) 169
Оператор обновления (Update) 170
Оператор объединения (Union) 170
Запросы с параметрами 171
Несколько полезных функций SQL 171
Язык определения данных (DDL) 173
Оператор создания таблицы (Create Table) 173
Оператор модификации таблицы (Alter Table) 175
Оператор удаления таблицы (Drop Table) 175
Оператор создания вторичного индекса (Create Index) 175
Оператор удаления индекса (Drop Index) 176
Свойство SQL компонента TQuery.
Построитель запросов (SQL Builder) 176
«Живые» запросы и свойство RequestLive 179
Использование параметров 180
Выполняемые запросы 182
Компонент TUpdateSQL 182
Компонент TBatchMove 189
Клиентские наборы данных 192
Резюме 193

Глава 4. Клиент-серверные базы данных 195


Работа с Interbase 196
Консоль Interbase (IBConsole) 197
Утилита ISQL 202
Базы данных Interbase 204
Типы данных, поддерживаемые Interbase 204
Несколько слов о языке SQL 205
Работа с базами данных 206
Borland C++ Builder 6. Разработка приложений баз данных

Домены (Domains) 209


Работа с таблицами 211
Использование индексов 216
Генераторы (Generators), триггеры (Triggers)
и хранимые процедуры (Stored Procedures) 277
Представления (Views) 226
Исключения (Exceptions) 229
Планирование системы безопасности 230
Создание клиентских приложений 232
Несколько слов о механизмах доступа к данным 233
Подключение к базам данных с помощью dbExpress 234
Использование IBExpress для подключения к БД 237
Подключение к БД при помощи механизма dbGo (ADO) 240
Однонаправленные наборы данных dbExpress 245
Компонент TSQLTable 246
Компоненты TSQLQuery, TSQLStoredProc и TSQLDataSet 260
Клиентский набор данных TSQLCIientDataSet 263
Компонент TSQLMonitor 270
Резюме 273

Глава 5. Документированием анализ информации 275


Создание отчетов при помощи компонентов QuickReport 275
Мастер создания отчетов 276
Создание простого отчета в режиме конструктора 279
Компонент TQuickRep 282
Полосы отчета 291
Краткий обзор компонентов Quick Report 296
Decision Cube 297
Резюме 302

Предметный указатель 304


В
последнее время все больше и больше программистов, как начинающих, так
и достаточно опытных, приобщается к работе с базами данных различного форма-
та. Например, программиста, всю жизнь проработавшего с базами данных Oracle,
могут привлечь к сопровождению или созданию приложений, взаимодействующих
с локальными таблицами (Paradox, dBASE и др.). Или в связи с приобретением новой
бухгалтерской программы придется срочно переквалифицироваться из разработчика си-
стем мониторинга и управления технологическими процессами в специалиста по клиент-
серверным базам данных.
Практически во всех таких или подобных ситуациях ощущается острая нехватка вре-
мени. Возможность самостоятельно в кратчайшие сроки найти решение новых задач вряд
ли представится. Бесконечные блуждания по справочной системе в поисках нужной ин-
формации могут довести до отчаяния. Неоценимую помощь в таком случае может оказать
исчерпывающее описание решения нужной задачи, включающее изложение основных
концепций и примеры, эти концепции иллюстрирующие. Зачастую достаточно просто
взглянуть на удачно спроектированный пример, чтобы понять, как все работает.
Предлагаемая вашему вниманию книга посвящена основным вопросам программи-
рования приложений баз данных с использованием шестой версии Borland C++ Builder.
Начиная с первой версии, когда был представлен универсальный механизм доступа
к данным BDE, в каждой очередной версии C++ Builder разработчикам приложений баз
данных предлагались все новые и новые возможности. Не стала исключением и версия 6.
Наряду с целым рядом усовершенствований и дополнений, Borland C++ Builder 6 предо-
ставляет и новый механизм доступа к данным. Механизм dbExpress дает возможность
легко и быстро получать доступ к базам данных в формате SQL (DB2, Oracle, Interbase
и др.). За счет использования специализированных драйверов БД (для каждого формата
БД применяется свой драйвер) механизм dbExpress позволяет создавать наиболее эффек-
тивно работающие приложения БД, в том числе и кросс-платформенные (CLX).
На данный момент C++ Builder поддерживает четыре разных механизма доступа к дан-
ным. Кроме упомянутых механизмов BDE и dbExpress, к их числу относятся и появившиеся
в пятой версии механизмы IB Express и ADO Express (в шестой версии последний называется
dbGo). IB Express — это специализированный механизм для доступа к базам данным в форма-
те Interbase, позволяющий воспользоваться всеми особенностями, характерными именно для
этого формата БД. Что касается механизма dbGo, который использует для доступа к данным
разработанный в Microsoft провайдер OLE DB, то, несмотря на объявленную .универсаль-
ность, пользоваться им удобно только для подключения к базам данных MS Access и MS SQL
Server (или к другим источникам данных, формат которых разработан Microsoft).
Благодаря тому, что BDE — самый старый (а значит, и наиболее проверенный на прак-
тике), самый универсальный и распространенный механизм доступа к данным, именно
на его основе в этой книге и объяснялись важнейшие концепции проектирования баз
данных (модели данных), а также принципы построения простейших приложений БД.
Параллельно описывались особенности локальных баз данных (Paradox, dBASE). При
описании клиент-серверных баз данных на примере БД Interbase преимущественно ис-
пользовался механизм dbExpress.
Borland C++ Builder 6. Разработка приложений баз данных

Книга разделена на пять глав.


Первая глава, как обычно бывает, служит вводной. Вначале обсуждаются различны
подходы к классификации баз .данных, рассматриваются основные понятия, использ)
емые при дальнейшем изложении, а также приводятся общие рассуждения о том, ка
должна выглядеть идеально спроектированная база данных. Подробно описываются ХЕ
рактерные этапы разработки модели данных (структуры базы данных). Вместе с Borlan
C++ Builder поставляются две полезные утилиты, при помощи которых можно создават
базы данных, модифицировать их структуру, заполнять таблицы данными и делать много
другое. Часть этих действий удобнее выполнять при помощи утилиты SQL Explorer, дру
гие — при помощи Database Desktop. Описание возможностей этих утилит и завершав
материал первой главы.
Во второй главе рассматриваются основы механизма BDE. На примере одного из про
стейших наследников класса TDataSet — компонента ТТаЫе — описываются основны<
действия с наборами данных. В частности, речь идет о таких задачах, как навигация ш
набору данных, поиск нужной информации, фильтрация и тому подобное. Обсуждение
всех этих простейших задач сопровождается примерами приложений, в которых неслож-
ные действия с наборами данных представлены несколько нестандартно. Кроме того
подробно рассматриваются компоненты TDataSource и TField, описываются постоянные
и динамические объекты полей, вычисляемые поля и поля подстановки. В заключение
рассматриваются создание связей типа Master/Detail между таблицами, режим кэширова-
ния изменений, приемы создания таблиц программным способом.
В третьей главе книги продолжается обсуждение механизма BDE и основных концеп-
ций создания приложений баз данных. Здесь рассматриваются модули данных, компонен-
ты TDatabase и TSession. Большое внимание уделено использованию элементов визуали-
зации и управления данными, которые можно найти на вкладке Data Controls. Особое вни-
мание уделено компоненту TDBGrid (сетка), очень часто используемому в приложениях
БД. Достаточно подробно описывается вариант языка SQL, который используется для до-
ступа к данным локальных БД (Paradox, dBASE, Foxpro и таблицы в текстовом формате).
На основе этой информации рассматриваются варианты применения наиболее важного
наследника класса TDataSet — компонента TQuery. Именно экземпляры этого компонента
чаще всего используются в приложениях БД. В заключение рассматриваются компоненты
TUpdateSQL и TBatchMove, а также основы использования клиентских наборов данных.
Четвертая глава посвящена созданию приложений, взаимодействующих с клиент-
серверными базами данных. В качестве примера используется сервер БД Interbase.
Вначале рассматриваются утилиты IBConsole и Interactive SQL. Описывается, как при
помощи утилиты IBConsole зарегистрировать локальный или удаленный сервер Interbase,
создать и зарегистрировать базу данных, а также выполнить другие действия по админи-
стрированию БД Interbase. Особое внимание уделено утилите Interactive SQL, при помощи
которой можно выполнить любой запрос SQL к открытой базе данных.
Далее в этой главе рассматриваются основы БД Interbase. В частности, расска-
зывается о типах данных Interbase, описывается встроенный язык SQL Interbase, из-
учается работа с основными объектами Interbase, обсуждается планирование системы
безопасности. Рассматриваются основные вопросы создания клиентских приложений,
взаимодействующих с клиент-серверными базами данных: использование механизмов
dbExpress, IBExpress и dbGo (ADO) для подключения к БД Interbase, использование одно-
направленных наборов данных dbExpress, способы обновления необновляемых наборов
данных и перемещения по однонаправленным наборам данных в любом направлении,
От автора У

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


и файловой модели MyBase. В заключение приводятся примеры использования компонен-
та TSQLMonitor.
В последней, пятой главе книги речь идет о способах создания отчетных и аналити-
ческих форм для обычного и многомерного анализа данных. Рассматриваются компонен-
ты из наборов Quick Report и Decision Cube. Приводится много примеров, облегчающих
понимание основ конструирования отчетов различной сложности.
Надеюсь, вы сможете почерпнуть из этой книги нужную вам информацию. Удачи!
Базы данных и
вспомогательные инструментальные
средства C++Builder б
В ЭТОЙ ГЛАВЕ
Ф Классификация баз данных
Ф Основные понятия
ф Что такое «правильная» база данных
Ф Этапы разработки
Ф Утилиты SQL Explorer и Database Desktop
ф Резюме

Д
авно ушли в прошлое те времена, когда программисту, всю свою сознательную
жизнь проработавшему с языками программирования С или C++, приходилось
отказываться от любимого инструмента только потому, что возникала необходи-
мость проектировать базы данных. Еще свежи в памяти судорожные попытки этих не-
счастных найти библиотеки функций, написанных неизвестными героями, с помощью
которых можно было бы создать базу данных никому неизвестного или малоизвестного
формата с использованием языков C/C++. В такой ситуации почти никто не задумывался о
гарантиях безопасности и целостности данных. Понимание важности этих вопросов при-
ходило гораздо позже, когда большая часть приложения уже была сделана и отлажена, и
изменить что-либо не было никакой возможности.
Весь этот ужас для любителей языков программирования C/C++ закончился с выходом
в свет первой версии продукта C++ Builder от фирмы Borland. Кроме всех прочих пре-
лестей этот продукт содержал и так называемый механизм баз данных Borland (Borland
Database Engine, или, сокращенно, BDE). BDE — это набор системных библиотек DLL,
являющихся интерфейсом между приложением пользователя и базой данных выбранного
формата. Механизм BDE обеспечивал доступ к локальным базам данных (Paradox, dB-
ASE, текстовые файлы) и базам данных SQL (Oracle, DB2, Informix, Interbase, MS SQL
Server и др.). При помощи драйверов ODBC (Open Database Connectivity), разработанных
различными фирмами, можно было подключаться к файлам Access, Excel, FoxPro, Btrieve
и другим. Благодаря тщательно продуманным компонентам для доступа к базам данных и
их объектам можно было легко создать приложение, работающее со всеми популярными
в то время источниками данных.
С момента появления Borland C++ Builder прошло уже немало времени. C++ Builder
менялся от версии к версии, совершенствовался, становился более гибким и удобным, со-
Глава 1. Базы данных и вспомогательные инструментальные средства C++ Builder 6

ответствуя все возрастающим потребностям сообщества программистов. Вместе с основ-


ным продуктом менялись, совершенствуясь, и механизмы доступа к различным источни-
кам данных. Новейшая на момент написания этих строк версия C++ Builder — шестая.
Кроме BDE эта версия содержит и другие механизмы доступа к данным:
G dbExpress, предоставляющий очень быстрый и удобный доступ к базам данных в фор-
мате SQL.
Q ADOExpress — набор компонентов, позволяющий получать доступ к реляционным
и нереляционным источникам данных при помощи механизма ADO. Этот механизм,
разработанный фирмой Microsoft, для доступа к данным использует провайдер данных
OLE DB.
G IBExpress — набор компонентов, позволяющий получить доступ к базам данных
Interbase и воспользоваться всеми преимуществами СУБД Interbase, часть которых не-
доступна при использовании механизма BDE.
Выбрав подходящий механизм доступа к данным, можно воспользоваться стандарт-
ными компонентами для отображения и редактирования содержимого полей источника, а
также для перемещения по его записям. К числу таких компонентов относятся поля ввода,
списки и комбинированные списки, флажки, сетки и другие хорошо известные пользова-
телям Windows элементы управления. Для создания различных отчетных форм (в виде
таблиц, перекрестных таблиц, диаграмм и т.д.) предназначены наборы компонентов Quick
Report (QReport) и Decision Cube.
Кроме стандартных (привычных) средств работы с базами данных различного формата
C++ Builder 6 содержит средства для создания многоярусных приложений (так называе-
мая технология MIDAS), средства для интеграции информационных систем различных
предприятий (технология В2В, или Business-To-Business, реализованная при помощи на-
бора компонентов Web Services), а также поддерживает другие новые технологии.
Предлагаемая вашему вниманию книга поможет вам разобраться с различными ва-
риантами подключения к источникам информации (базам данных) и функциональными
возможностями большинства компонентов и классов, которые вы сможете использовать в
своих приложениях баз данных для отображения, просмотра и редактирования информа-
ции, а также для представления информации из различных таблиц выбранных источников
данных в удобной для анализа форме. Ознакомившись с этим материалом, вы сможете
самостоятельно создать базу данных, подготовить необходимые таблицы и заполнить их
информацией, подключиться к нужному реляционному или нереляционному источнику
данных, а также, воспользовавшись преимуществами той или иной схемы подключения,
создать информационное приложение, соответствующее вашим запросам или запросам
ваших заказчиков.
Среди прочего в этой главе представлены вводные сведения о базах данных, а так-
же описаны некоторые полезные инструментальные средства Borland C++ Builder 6.
Начальные сведения о базах данных позволят вам ознакомиться с основными терми-
нами, которые будут использоваться при дальнейшем изложении. К числу инструмен-
тальных средств, рассматриваемых в этой главе, относятся SQL Explorer и Database
Desktop. Ознакомившись с материалом главы, вы сможете со знанием дела создавать
простейшие базы данных, таблицы и другие объекты. Вы сможете также добавлять
или удалять записи таблиц, редактировать содержащуюся в этих записях информа-
цию, и многое другое.
I2 Borland C++ Builder 6. Разработка приложений баз данных

Базы данных:
классификация и этапы разработки
Прежде чем говорить о возможностях Borland C++ Builder 6 по созданию приложены!
баз данных, следует сделать несколько замечаний о базах данных как таковых. Основна
цель этого раздела — дать небольшой обзор терминологии, которая будет использоватьс;
в дальнейшем, а также обсудить общепринятые подходы к разработке баз данных.
Под базой данных (в дальнейшем иногда будет использоваться сокращение БД
обычно понимают хранилище информации, описывающей некую часть реального мира
Это означает, что в базе данных должно храниться описание определенного количеств!
реальных объектов, а также процессов, происходящих с этими объектами. Например, Б/
для учета движения товаров в магазине может содержать описание объектов товары, по
ставщики, покупатели, сотрудники и процессов поступление товаров, продажа това
ров, поступление денег, выплата денег.
С другой стороны, данное определение подразумевает, что хранящиеся в одной базе
данных объекты должны быть так или иначе связаны между собой при помощи про-
цессов, описание которых хранится в этой же базе данных. Другими словами, в одной
базе данных должны храниться объекты и процессы, представляющие собой целостную,
связную картину части реального мира. Если объекты и процессы распадаются на две не
связанные друг с другом группы, их следует хранить в разных базах данных. И объекты, и
процессы мы в дальнейшем будем называть объектами.
Приложение БД (Database Application) — это программа, которая в процессе выполне-
ния может подключаться к БД для манипуляций или структурой самой базы данных, или
хранящимися в ней данными. Одно приложение БД может подключаться как к одной, так
и (одновременно) к нескольким базам, расположенным или на одном из локальных или
сетевых дисков, или на удаленном сервере. Верно и обратное: к одной базе данных могут
быть одновременно подключено несколько приложений, в том числе через локальную
сеть или удаленное соединение.
Исходя из данного выше определения, базой данных можно называть как совокупность
хранящейся в ней информации о различных объектах (например, список всех товаров,
клиентов или банковских операций), так и описание структуры отдельных объектов или
самой БД. Чтобы избежать двусмысленности, описание БД (структуры) часто называют
моделью данных (Data Model). Модель данных создается или изменяется в процессе раз-
работки базы данных или при модификации ее структуры во время работы приложения
БД. Условно модель данных можно считать шаблоном, который заполняется информацией
при использовании БД.

Подходы к классификации
Существует несколько общепринятых подходов к классификации баз данных, характе-
ризующих различные аспекты их использования. Важнейшие из них два. В соответствии
с первым подходом БД разделяются на реляционные и нереляционные. Если информация в
БД хранится упорядоченно, такая база данных называется реляционной, в противном слу-
чае — нереляционной. Упорядоченной называется информация, доступ к которой можно
получить унифицированным способом. Если метод доступа к информации зависит от ее
содержания, то такая информация называется неупорядоченной.
Если быть ближе к практике, можно считать, что если информация в базе данных хра-
Глава 1. Базы данных и вспомогательные инструментальные средства C++ Builder б

нится в виде обычных двумерных таблиц, то она является реляционной. В качестве при-
мера нереляционных баз данных можно упомянуть хранилища файлов различных типов
(текстовых, графических и т.д.), для доступа к которым применяются различные методы,
архив сообщений электронной почты, база данных, в которой хранится список файлов
наряду с самими файлами, и тому подобные источники информации. В дальнейшем речь
будет идти только о реляционных базах данных, хотя провайдер данных OLE DB, который
используется для доступа к данным по технологии ADO, позволяет работать и с нереля-
ционными источниками информации.
Второй из упомянутых подходов к классификации — комбинированный. При его ис-
пользовании учитывается способ доступа к базе данных (не к информации!), способ физи-
ческого хранения информации и другие характеристики, касающиеся размещения и хране-
ния данных. В соответствии с этим подходом можно выделить четыре типа баз данных.
Q Локальные — предназначены для решения простых задач обработки информации. БД
этого типа хранятся в одном из каталогов локального компьютера (отсюда и название)
в виде отдельных файлов. Каждому объекту (таблице) БД соответствует как минимум
один файл. Кроме файла таблицы каждому объекту может соответствовать один или
несколько служебных (например, индексных) файлов. Если учесть эту особенность,
становится понятно, почему создать сложную базу данных этого типа и управлять ею
— невероятно трудно, а зачастую и просто невозможно. Кроме того, хотя к локальным
базам данных возможен доступ через локальную сеть или через удаленное соединение,
они все же не предназначены для многопользовательского доступа. Типичные приме-
ры локальных баз данных — dBASE, FoxPro, Paradox.
О Файл-серверные БД, в отличие от локальных, предназначены для многопользователь-
ского доступа через сеть. Вся информация, включая служебную, в БД файл-серверного
типа хранится в одном файле. При доступе через сеть на локальном компьютере (т.е. на
компьютере, с которого осуществляется доступ) создается собственная копия данных.
Если используется большой объем данных, сеть может испытывать значительную на-
грузку, так как при этом заметно увеличивается сетевой трафик. Кроме того, поддерж-
ка многопользовательской работы в базах данных этого типа не реализована в полной
мере. В связи с этим файл-серверные базы данных используются в основном для ра-
боты на локальном компьютере или в небольшой локальной сети. В качестве примера
СУБД этого типа можно привести Microsoft Access.
Q Клиент-серверные БД предназначены для создания сложных, корпоративного уров-
ня информационных систем, предоставляющих доступ как по локальной сети, так и
через удаленное соединение. К клиент-серверной БД может получить доступ огром-
ное количество пользователей одновременно. При клиент-серверной организации
информационных систем сама БД хранится на удаленном компьютере, называемом
удаленным сервером. Именно на этом компьютере и происходит вся обработка ин-
формации, включая проверку допустимости различных действий пользователя (до-
бавление, удаление или редактирование информации). Клиент посылает удаленному
серверу определенным образом сформулированный запрос на выборку информации
или на производство каких-либо действий, сервер обрабатывает запрос и отсылает
ответ обратно на компьютер пользователя. При таком подходе значительно снижается
сетевой трафик (количество передаваемой в обоих направлениях информации в еди-
ницу времени), уменьшается время ожидания ответа каждым из клиентов (из-за равно-
мерного распределения ресурсов), возрастает надежность и устойчивость работы всей
14 Borland C++ Builder6. Разработка приложений баз данных

информационной системы. Серверы БД общаются с клиентскими приложениями пр


помощи стандартного языка SQL-запросов, поэтому их часто называют SQL-сервер;
ми. В настоящее время на рынке доступно большое количество SQL-серверов; к и
числу относятся, в частности, Interbase, Oracle, DB2, Sybase, Informix, MS SQL Serv<
и многие другие, менее известные и распространенные.
Q Многоярусные БД. Концепция многоярусных (multi-tiered) БД — дальнейшее развита
концепции клиент-серверных БД. Многоярусные БД — это новая и очень nepcnei
тивная архитектура информационных систем. Наиболее развита в настоящее врем
трехъярусная архитектура. При использовании такой модели на самом нижнем ярус
находится так называемый тонкий клиент, формирующий запросы к удаленному cej
веру и отображающий полученную от сервера информацию. Другими словами, тонки
клиент предназначен в основном для ввода и отображения информации. Требования п
производительности к тонким клиентам достаточно низкие, что ведет к существенно
му удешевлению каждого рабочего места.
На втором ярусе располагается так называемый сервер приложений. Он производи
основные вычисления, в том числе, наиболее громоздкие, формирует окончательны
запросы к серверу БД, а также обслуживает подключенных к нему клиентов. На это!
уровне должен находиться достаточно мощный компьютер, так как к нему может быт
подключено большое количество клиентов, одновременно запрашивающих сложную об
работку больших объемов информации. Серверов приложений в одной информационно]
системе может быть также большое количество (хотя и намного меньше, чем клиентов)
На третьем ярусе располагается сервер БД. Обычно используется один сервер БД
управляющий одной базой данных. Сервер БД обрабатывает поступающие к нему за
просы, формирует ответ и отсылает его запросившему серверу приложений. Серве{
приложений, получив ответ, отсылает необходимые данные клиенту, выступая также i
роли коммутатора. Такая архитектура очень надежна, устойчива и, при определенны?
условиях, не требовательна к аппаратному обеспечению и используемым операцион-
ным системам.
В последнее время, однако, чаще применяются упрощенные варианты приведенной
выше классификации. Эти упрощенные классификации также будут использоваться ъ
этой книге. В соответствии с первым упрощением, базы данных, в зависимости от метода
доступа к ним, делятся на локальные и клиент-серверные (удаленные). В большинстве
случаев такой подход оправдан. При втором упрощении все базы данных разделяются в
зависимости от количества составляющих их звеньев. При таком подходе локальные и
файл-серверные базы данных считаются одноярусными системами, а клиент-серверные
— двухъярусными. -

Основные этапы разработки


К сожалению, не существует какой-либо одной, теоретически обоснованной методики
разработки баз данных, следуя которой можно было бы без всяких проблем сконструиро-
вать необходимую информационную систему. С другой стороны, практически всем из-
вестно, что БД будет высокопроизводительной, устойчивой к ошибкам ввода, масштаби-
руемой и максимально удобной, если с самого начала не допустить ошибок при проекти-
ровании модели данных. К счастью, базы данных уже давно разрабатываются множеством
опытных программистов по всему миру, поэтому к настоящему времени существует набор
Глава 1. Базы данных и вспомогательные инструментальные средства C++ Builder 6 15

рекомендаций, выполняя которые даже начинающий или малоопытный разработчик БД


сможет не допускать грубых ошибок с самого начала.
Следует заметить, что совершенно избежать ошибок и неточностей при проектировании
модели данных не удается никому. Даже опытные программисты могут допускать неточ-
ности, не до конца продумав все нюансы. Поэтому вам придется смириться с тем, что не
раз и не два вы будете возвращаться в самое начало разработки, вновь и вновь перекраивая
структуру БД. Таким образом, конструирование модели данных — процесс итеративный.
Прежде чем перечислять то, что необходимо для разработки «правильной» базы дан-
ных, неплохо было бы выяснить, какую базу данных считать таковой. В большинстве
источников «правильной» считается база данных, которая соответствует следующим
четырем условиям.
Q Удовлетворяет всем запросам пользователя по содержанию. Конечно, это условие
кажется само собой разумеющимся и потому излишним, но это не совсем так. Прежде
чем приступать к разработке модели данных, вам следует на сто процентов удостове-
риться, что вы четко представляете себе, какие именно данные хотят видеть в создава-
емой информационной системе потенциальные пользователи. Кроме того, вы должны
догадываться о том, какую еще информацию возможно потребуется включить в базу
данных в процессе разработки информационной системы. Это связано с тем, что в
большинстве случаев заказчики базы данных не вполне представляют все с необходи-
мой точностью в момент постановки задачи.
О Удовлетворяет требованиям пользователя по производительности. На самом деле
не нужно спрашивать у пользователя, нравится ли ему, что его запрос выполняется
полчаса или час. Ответ и так понятен: это никому не понравится. Поэтому на всех
этапах разработки БД необходимо заботиться о наилучшей производительности.
Например, если таблица может содержать очень много строк, не стоит делать в ней
слишком много индексов, иначе после каждого ввода новой записи время индексации
может превысить 1-2 секунды, что будет сильно раздражать пользователя и замедлять
его работу. С другой стороны, если индексов будет мало, то слишком долгим будет
время отбора записей при отображении. Особое внимание следует уделить фильтрации
записей из больших таблиц при отображении. Например, если таблица содержит мил-
лион записей, то не стоит отображать сразу все. Достаточно отобразить небольшую
часть записей, например, соответствующих определенному периоду или диапазону
значений. Много внимания следует уделить и оптимизации запросов. Если запрос вы-
полняется медленно, попробуйте сформулировать его по-другому.
Q Не позволяет вводить данные, нарушающие целостность уже имеющейся инфор-
мации. Вы должны программно или при помощи настройки свойств объекта в режиме
конструктора запретить ввод данных, которые могут вызвать ошибочную ситуацию (на-
пример, деление на ноль), появление двусмысленности и тому подобные неприятности.
Это достигается за счет задания ограничений на диапазон вводимых значений, правиль-
ной организации связей между объектами (таблицами), использования транзакций и
других мер. Как правило, в остальном поддержание целостности информации берут на
себя системы управления базами данных (СУБД), управляющие конкретной базой.
Q Предоставляет естественную, интуитивно понятную организацию данных.
Хорошо и понятно организованная структура информации помогает потенциальным
пользователям легко и быстро разобраться в существе вопроса, что позволит им само-
стоятельно строить различные отчетные и аналитические формы, первоначально не
10 Borland C++ Builder 6. Разработка приложений баз данных

предусмотренные в проекте. С другой стороны, и сам разработчик при необходимости


легко может вспомнить все детали проекта, над которым он когда-то работал.
Конечно, приведенные выше условия — не догма, но учесть их все же стоит.
Вооружившись информацией о том, «что такое хорошо, а что такое плохо», можно
приступить к изучению этапов разработки БД. Обычно выделяют восемь таких этапов.
1. Определение типа информации, которая должна быть учтена в базе данных.
На самом деле это скорее не этап разработки, а этап подготовки к ней. Прежде чем
приступить к созданию модели данных, вы должны опросить всех предполагаемых
пользователей, чтобы получить полное представление об их требованиях. Всю эту
информацию необходимо тщательно задокументировать. На ее основе вы должны
четко представить себе целостную картину той части реального мира, которую вы со-
бираетесь моделировать. Чтобы убедиться в том, что вы все учли, вам нужно ответить
на несколько вопросов.
• Все ли объекты связаны между собой надлежащим образом? Нет ли противоречий
между связями? Не распадаются ли все объекты на несколько изолированных друг
от друга групп?
• Правильно ли вы представляете себе уровень детализации каждого из объектов?
При ответе на этот вопрос нельзя полагаться только на информацию, полученную
от предполагаемых пользователей, так как они могут не представлять всей специ-
фики разработки информационных систем.
• Какие из характеристик объектов уникальны и однозначно определяют конкрет-
ный объект? Например, можно ли, опираясь на информацию о дате накладной и
ее номере, однозначно идентифицировать накладную, или может одновременно
существовать несколько накладных, имеющих одинаковый номер и дату? Эта ин-
формация понадобится при создании системы индексов и ключей.
• Сколько и каких отчетных форм необходимо предусмотреть в БД? Имеются ли для
этого все необходимые объекты?
• Сколько и каких категорий пользователей будет работать с БД? В зависимости от
выполняемых функций у каждой категории пользователей должен быть свой уро-
вень доступа. Например, администраторы должны иметь полный доступ ко всем
объектам БД, операторы — доступ к вводу и просмотру информации из определен-
ных (или всех) таблиц, а для группы анализа и принятия решений достаточно прав
на просмотр информации, и т.д.
• Каким образом будет осуществляться доступ к базе данных — только с локаль-
ного компьютера, через локальную сеть, с использованием удаленного соедине-
ния? Сколько пользователей могут одновременно получать доступ к базе данных
— один, два, сто, тысяча?
• Какие из таблиц могут иметь большое количество записей, а какие нет? Какие
данные критичны ко времени обработки? (Например, таблицы могут иметь очень
большое количество строк и при этом подвергаться очень частым изменениям дан-
ных. В этом случае для организации нормальной работы и безопасности данных
необходимо предпринимать специальные меры: разбиение, архивирование и т.д.)
• Нужна ли архивация информации из основных (громоздких) таблиц, и если да, то
какова должна быть ее периодичность?
Глава 1. Базы данных и вспомогательные инструментальные средства C++ Builder б 17

Если ответ на какой-либо из вопросов вам не ясен, следует повторно опросить предпо-
лагаемых пользователей. Таким образом, сбор информации также может быть итера-
тивным.
2. Разбиение информации на объекты (сущности) и атрибуты. На этом этапе необхо-
димо проанализировать всю информацию на концептуальном уровне и распределить
ее на объекты (сущности) и атрибуты. Объекты представляют собой конкретные
реальные объекты или процессы, которые вы собираетесь описывать в базе данных.
Например, если вы создаете базу данных для учета деятельности небольшого магази-
на, то к объектам (сущностям) можно отнести клиентов, поставщиков, сотрудников,
приобретение, расход и т.д. Атрибуты — это свойства объекта, при помощи которых
он и описывается. Например, для объекта клиент атрибутами могут быть название
клиента, его адрес, банковские реквизиты, контактные телефоны, Web-адрес и тому
подобная информация. В базе данных объектам соответствуют таблицы, а атрибутам
объектов — столбцы (поля) таблицы.
На этом этапе также необходимо удалить из объектов излишнюю информацию и пе-
ренести ее в другие объекты. Например, если объект описывает расходные накладные,
то нет смысла хранить подробную информацию о клиенте в каждой записи. Вместо
этого нужно создать новый объект, описывающий клиентов, а в объекте расходные
накладные достаточно оставить ссылку на конкретного клиента. При помощи этой
ссылки объект клиент может быть связан с объектом расходные накладные. Разбиение
больших разнородных блоков информации на более мелкие однородные структуры и
установление связей между ними называется нормализацией (Normalization).
5. Отображение системы объектов (сущностей) и их атрибутов на таблицы и столб-
цы. На этом этапе в соответствии с планом, разработанным в предыдущем пункте,
создаются таблицы. Для каждой таблицы определяются первичные и внешние ключи,
а также индексы. Именно на этом шаге проявляется большая часть ошибок, допущен-
ных в процессе нормализации.
t. Определение атрибутов, однозначно идентифицирующих каждый объект. На этом
этапе для каждой таблицы необходимо определить столбец или группу столбцов, при
помощи которых можно однозначно отделить одну запись от другой. Такой столбец (или
группа столбцов) служат в качестве первичного ключа (Primary Key). В качестве первич-
ного ключа можно указать один или несколько уже существующих атрибутов или соз-
дать новый атрибут специально для этих целей. Например, если у вас имеется таблица
Персонал, то в качестве первичного ключа можно использовать столбцы Фамилия, Имя,
Отчество. Однако в этом случае вы не сможете ввести в таблицу запись о сотруднике, у
, которого фамилия, имя и отчество совпадают с соответствующими атрибутами сотруд-
ника, принятого на работу ранее. Другими словами, такая таблица не приемлет полных
тезок. Из этой ситуации можно выйти, сделав допущение, что полные тезки не могли
родиться в один день. Конечно, это не исключено, но практически можно считать такое
совпадение невероятным. Вам остается только добавить к столбцам Фамилия, Имя,
Отчество, составляющим первичный ключ, еще и столбец Дата рождения.
Чаще, однако, в качестве первичного ключа используют дополнительный служебный
столбец, который не несет никакой другой смысловой нагрузки, кроме уникальной
идентификации каждой записи. Обычно это просто номер записи. Каждая новая за-
пись в простейшем случае получает в качестве номера увеличенный на единицу номер
предыдущей (последней) записи. Таким образом обеспечивается уникальность каждой
10 Borland C++ Builder 6. Разработка приложений баз данных

записи. Следует обратить внимание на то, что каждая СУБД имеет свои собственнь
средства поддержки первичных ключей.
Кроме первичных ключей на этом этапе вы должны также задать, если это нужн
столбцы, которые будут использоваться в качестве внешних ключей (Foreign Keys,
Определив на этом шаге столбцы, которые будут ключами (первичными или внешт
ми), вы сможете впоследствии использовать их для задания связей между таблицам:
Каждому первичному ключу одной таблицы при такой связи будет соответствова!
любое количество (теоретически от нуля до бесконечности) записей из связанной Т!
блицы. О связях между таблицами будет рассказано чуть позже.
5. Разработка системы правил, устанавливающих для каждой таблицы вариант!
доступа, модификации и добавления новых записей. На этом этапе вы должш
уточнить для каждого из атрибутов (столбцов) характеристики, связанные с выбран
ным для него типом данных. Например, для столбца, содержащего текстовые значе
ния, необходимо указать максимально допустимую длину строки, для числовых типо.
указать, какое число может быть допустимым — целое, длинное целое, с плавающее
точкой и т.д. Кроме того, вы должны задать, если это необходимо, и остальные ха
рактеристики столбцов: какие значения допустимы, может ли в какой-либо из ячее!
данного столбца содержаться значение NULL (т.е. допускается ли для данного столбцг
отсутствие значения хотя бы в одной ячейке), какое значение является для столбцг
значением по умолчанию (т.е. что будет подставлено в соответствующую ячейку ново*
записи, если вы не введете в нее определенного значения).
6. Фактическое задание связей между таблицами при помощи первичных и внешни}!
ключей, запланированных на ранних стадиях проектирования. На этом этапе устанав-
ливаются связи между таблицами. В реляционных базах данных это делают при помощи
первичных и внешних ключей, зарезервированных на этапе конструирования таблиц. Как
уже упоминалось, первичный ключ служит для однозначной идентификации каждой за-
писи таблицы. Таким образом, в таблице может быть только одна запись с конкретным
значением первичного ключа. Каждому значению первичного ключа таблицы может соот-
ветствовать несколько записей с совпадающим значением внешнего ключа. Предположим,
например, что таблица Клиенты связана с таблицей Отгрузки. Для связи используется
столбец Номер клиента, являющийся в таблице Клиенты первичным ключом, и столбец
Клиент, который служит внешним ключом в таблице Отгрузки. Предположим далее, что
в ячейке столбца Номер клиента, соответствующей записи о клиенте Big Technologies, со-
держится число 5. Тогда любая запись таблицы Отгрузки, в которой в столбце Клиент
содержится число 5, будет относиться именно к клиенту Big Technologies. С другой сто-
роны, если внешний ключ какой-либо записи в таблице Отгрузки содержит число 5, то
в таблице Клиенты должна существовать только одна запись, первичный ключ которой
имеет значение 5. Если такой записи нет, значит, нарушена ссылочная целостность дан-
ных (Referential Integrity). Таким образом, используя совпадающие значения первичного
и внешнего ключа, можно для каждой записи из таблицы Отгрузки получить подробную
информацию о клиенте, которому была произведена отгрузка.
Существует несколько вариантов фактического задания связей. Их можно задать при
помощи визуальных инструментов (как в Microsoft Access или CASE-инструментах)
или при помощи соответствующего SQL-оператора. Независимо от способа задания
связей, системы управления базами данных автоматически поддерживают правила
ссылочной целостности, задаваемые этой связью.
Глава 1. Базы данных и вспомогательные инструментальные средства C++ Builder 6

Описанная выше связь между таблицами Клиенты и Отгрузки — это связь типа
«один-ко-многим». При такой связи каждой записи одной из таблиц может соот-
ветствовать несколько записей (от нуля до бесконечности) из другой таблицы. Это
наиболее распространенный тип связей. Существует еще два типа связей — «один-к-
одному» и «многие-ко-многим». Связь типа «один-к-одному» означает, что каждой за-
писи из одной таблицы соответствует только одна запись из другой таблицы. Этот тип
связи используется очень редко. Единственный практический смысл его применения
— разбиение таблицы с очень большим количеством столбцов на несколько таблиц
меньшего размера.
При связи «многие-ко-многим» нескольким записям из одной таблицы могут соот-
ветствовать несколько записей из второй. Связь этого типа организуется при помощи
третьей, вспомогательной таблицы и, по сути, является совокупностью двух связей ти-
па «один-ко-многим». В качестве примера связи типа «многие-ко-многим» можно при-
вести связь между таблицами Сотрудники и Проекты. Над проектом может работать
несколько сотрудников, и сотрудник может работать над несколькими проектами сразу.
Для реализации связи в этом случае необходимо создать новую таблицу, например,
Сотрудники-Проекты. Каждая запись этой таблицы ставит в соответствие номер со-
трудника номеру проекта, в котором он участвует. Таким образом, первичному ключу
Номер сотрудника таблицы Сотрудники соответствует внешний ключ Сотрудник та-
блицы Сотрудники-Проекты, а первичному ключу Номер проекта таблицы Проекты
соответствует внешний ключ Проект таблицы Сотрудники-Проекты.
На этом же этапе следует указать условия целостности данных и задать необходимость
каскадного обновления и удаления связанных данных. Подробнее об этом будет сказа-
но далее в этой книге.
7. Выбор индексов для каждой таблицы. Хотя набор индексов проектируется при
создании каждой конкретной таблицы, на этом, практически завершающем этапе
разработки БД нужно окончательно решить, сколько и каких индексов необходимо
для каждой из таблиц. С одной стороны, чем больше индексов, тем быстрее будут
выполняться поиск, фильтрация и сортировка. С другой стороны, если таблица имеет
большое количество индексов, то будут медленно выполняться вставка, удаление и
модификация записей. Особенно это заметно для больших таблиц, имеющих порядка
ста тысяч записей или больше. В связи с этим для каждой таблицы следует определить
оптимальный состав индексов. Особенно тщательно это нужно делать для тех таблиц,
которые могут содержать большое количество записей.
Несмотря на простую формулировку задачи, определить оптимальный состав ин-
дексов в каждом конкретном случае не так просто. Исходя из опыта, можно посовето-
вать не создавать в одной таблице больше трех индексов. При этом следует учесть, что
большинство систем управления базами данных автоматически создают индекс, соот-
ветствующий первичному ключу. Приняв во внимание такие ограничения, необходимо
создать несколько индексов на основе тех столбцов, которые чаще всего участвуют в
операциях сортировки, поиска и фильтрации. Например, для таблицы Персонал мож-
но создать составной индекс на основе столбцов Фамилия, Имя, Отчество и простой
индекс на основе столбца Дата приема на работу.
Конечно, это только рекомендации. Более точно определить необходимость того
или иного индекса можно при помощи эксперимента. Для этого необходимо измерить
время выполнения запросов на выборку, вставку и модификацию данных до того, как
Borland C++ Builder б. Разработка приложений баз данных

вы определите новый индекс. Естественно, все эти запросы должны активно исполь
зовать столбцы, которые вы предполагаете включить в индекс. Далее, необходимс
измерить время выполнения тех же запросов, но уже после определения индекса. Не
основании полученной информации можно сделать вывод о целесообразности его ис
пользования. Следует помнить о том, что если таблица, над которой вы проводите экс
перимент, может содержать большое количество записей (например, порядка 100 000)
то испытывать индекс нужно примерно при таком же количестве записей. Эксперимеш
над таблицей, содержащей всего 100 строк, не даст вам объективной информации.
8. Планирование системы безопасности. Этот этап разработки БД, хотя и не имеет
непосредственного отношения к формированию модели данных, все же достаточно
важен, особенно если вы разрабатываете многопользовательскую информационную
систему. На этом этапе определяется перечень пользователей, которые могут опериро-
вать с базой данных. Для каждого пользователя устанавливается допустимый уровень
разрешений: может ли пользователь только просматривать конкретную таблицу базы
данных или он может вносить изменения в записи таблицы; может ли он модифициро-
вать макет таблицы или ему разрешен только просмотр. Организация системы безопас-
ности зависит от типа разрабатываемой БД (скажем, система безопасности, поддержи-
ваемая Microsoft Access, отличается от системы для Interbase). Так что на этом этапе
вам следует обратиться к соответствующей справочной документации, четко уяснив
себе все имеющиеся в вашем распоряжении возможности.
Успешно пройдя все восемь этапов разработки базы данных, не спешите сразу при-
ступать к непосредственному воплощению продуманной схемы. Вначале нужно оценить
логическую структуру получившейся базы данных как единого целого с высоты получен-
ного при разработке опыта (к концу разработки обычно начинаешь понимать те аспекты
описываемой картины реального мира, о которых не имел никакого понятия в самом на-
чале). Найдя какие-либо логические ошибки, недочеты или несоответствия, необходимо
вернуться к первому этапу и перепланировать модель данных с самого начала. Таким
образом, разработка модели данных итеративна. Не стоит экономить на это время: потра-
ченное на планирование и разработку модели данных, оно с лихвой окупится позднее, при
ее непосредственной реализации и эксплуатации.
Далее будут описаны два очень полезных инструмента, поставляемых вместе с C++
Builder 6, — SQL Explorer (Database Explorer) и Database Desktop. С их помощью вы смо-
жете создавать, просматривать или модифицировать структуру и данные БД различных
форматов. Конечно, эти утилиты не могут составить конкуренцию специализированным
средствам управления базами данных конкретного типа, однако, возможно, это будут
единственные инструменты, которые окажутся в вашем распоряжении.

SQL Explorer
SQL Explorer — иерархически организованный браузер, основное назначение которого
— в подробном исследовании структуры баз данных. Вы не можете с его помощью соз-
давать базы данных, а также создавать, модифицировать или удалять таблицы, индексы
и другие объекты. Однако вы можете модифицировать записи в выбранных таблицах, а
также вводить и выполнять SQL-операторы. Кроме того, с помощью SQL Explorer'a мож-
но создавать и модифицировать псевдонимы (Alias) базы данных, словари (Dictionary) и
наборы атрибутов (Attribute Set). Но об этом чуть позже.
Глава 1. Базы данных и вспомогательные инструментальные средства C++ Builder 6 21
Утилита SQL Explorer поставляется вместе с C++ Builder 6 версии Enterprise.
Соответствующая утилита есть и в версии поставки Professional, однако называется она
Database Explorer, и ее возможности несколько ограничены. В дальнейшем речь пойдет
именно об SQL Explorer.
Запустить SQL Explorer можно двумя способами: из самой среды C++ Builder (IDE
C++ Builder, или просто IDE) и как отдельное приложение. В первом случае необходимо
выбрать пункт Explore меню Database, а во втором — запустить приложение dbexplor.exe,
размещенное в подкаталоге BIN каталога, в который установлен C++ Builder (для такого
каталога принято обозначение $(ВСВ)). Оба варианта запуска практически равнозначны,
однако первый предпочтителен. Если вы использовали первый вариант, то окно приложе-
ния будет обозначать свое присутствие не кнопкой на панели задач Windows, а отдельным
пунктом меню Window в списке открытых окон. В любом случае, на экране будет отобра-
жено окно, показанное на рис. 1.1.

•Qbtecl йсболюу

е, X .- : ':
I All Database Aliase
Detebese s ) DicSoi Definition 1
) Databases Type STANDARD
- DEFAULT DRIVER PARADOX
+. Ho DefauHDD ENABLE BCD FALSE
+ *o IBLocal PATH Deprogram Files\Common Files\Boriwid Shoied\Data
t; TJ myAccees
P. ТГ База данныхМЗ Access
:*j Ti Бвзадвнных Visual FoxPro
"S "о Та&лииы Visual FoxPro
-f. TJ Файлы dBASE
jj TS Файлы Excel

Рис. 1.1. Окно приложения SQL Explorer


Как видно на рис. 1.1, основная рабочая область SQL Explorer'a разбита на две части
— левую панель (Left Pane) и правую панель (Right Pane). Обе панели представляют собой
набор вкладок. Левая содержит две вкладки: Databases (базы данных) и Dictionary (сло-
варь). На правой панели может содержаться переменное число вкладок — в зависимости
от выбранного на левой панели объекта.

Создание и использование псевдонимов


Вкладка Databases содержит список зарегистрированных в системе псевдонимов
(Aliases). Псевдоним (Alias) — это специализированный именованный объект, содер-
жащий информацию, которую BDE (Borland Database Engine) использует для подклю-
чения к базам данных. Псевдоним можно создать при помощи утилит SQL Explorer,
BDE Administrator или Database Desktop, а можно его и не создавать. В последнем
случае, если ваше приложение использует BDE, временный псевдоним будет создан
автоматически, как только вы укажете в приложении всю необходимую для подключе-
ния информацию.
При первом запуске SQL Explorer вы можете обратить внимание на то, что в системе
уже есть несколько зарегистрированных псевдонимов. В частности, при инсталляции C++
Builder автоматически регистрируются псевдонимы примеров баз данных BCDEMOS,
DefaultDD, IBLocal (см. рис. 1.1). Первые два ссылаются на базу данных формата Paradox,
22 Borland C++ Builder 6. Разработка приложений баз данных

а последний — на базу данных Interbase (этот псевдоним будет инсталлирован, если в


установили на своем компьютере или клиент, или сервер Interbase). Кроме того, автомат!
чески регистрируются псевдонимы баз данных, соответствующие установленным драйв<
рам ODBC (на рис. 1.1 — последние пять строк).
Замечание
Чтобы воспользоваться псевдонимами, которые соответствуют драйверам ODBC
необходимо сначала их настроить. В частности, нужно создать DSN-файл (Data Sourc
Name). Для этого следует прибегнуть к приложению Источники данных ODBC (32
(ODBC Administrator), доступ к которому можно получить, воспользовавшись системныг
диалоговым окном Панель управления (Control Panel) или соответствующим пунктог
меню Object утилиты SQL Explorer.

Создать новый псевдоним довольно просто. При выборе пункта New меню Object Hi
экране будет отображено диалоговое окно, позволяющее указать в выпадающем списю
тип драйвера базы данных (см. рис. 1.2). Обычно доступны следующие драйверы (в за-
висимости от варианта инсталляции): STANDARD, MSACCESS, SYBASE, ORACLE,
MSSQL, INFORMIX, DB2, INTRBASE. Тип драйверов, возможно, кроме первого, поня-
тен из их названия. Драйвер STANDARD поддерживает локальные базы данных Paradox
(таблицы в файлах с расширением db), dBASE и FoxPro (таблицы в йй/^файлах), а также
таблицы в текстовом формате (файлы с расширением txt). Выбрав нужный драйвер, на-
жмите кнопку ОК. Чтобы зарегистрировать новый псевдоним, нужно выбрать пункт Apply
все того же меню Object.

Рис. 1.2. Диалоговое окно New Database Alias


Для дальнейшего использования псевдонима его необходимо соответствующим об-
разом настроить. Для начала следует задать для него подходящее информативное имя,
так как автоматически ему будет присвоено имя наподобие STANDARD! или INTRBASE1.
Для этого существует команда Rename контекстного меню псевдонима. На правой панели
окна SQL Explorer, на вкладке Definition, перечислены остальные свойства псевдонима.
Количество и тип этих свойств зависит от выбранного типа драйвера. Например, если вы
выбрали тип STANDARD, то в списке будет всего четыре свойства: Туре (тип драйвера),
DEFA ULTDRIVER (конкретный драйвер базы данных — PARADOX, DBASE, FOXPRO или
ASCIIDRV), ENABLE BCD (нужно ли преобразовывать числа с плавающей точкой в двоич-
но-десятичный код BCD) и PATH (полный путь к таблицам базы данных выбранного типа).
Тип драйвера базы данных изменить нельзя, а остальные свойства можно редактировать.
У псевдонимов, соответствующих драйверам SQL-серверов, свойств гораздо больше.
В основном это свойства, специфичные для конкретного типа базы данных. Не все из
отображенных свойств можно менять, некоторые из них предназначены только для про-
смотра. Чтобы не ошибиться в их настройке, следует тщательно изучить документацию,
поставляемую вместе SQL-сервером. Если такой возможности у вас нет, то можно просто
оставить для всех свойств их значения по умолчанию. Исключением является свойство,
задающее имя базы данных (или каталог — для локальных баз данных). Например, для
Глава 1. Базы данных и вспомогательные инструментальные средства C++ Builder 6 23
баз данных Informix это свойство называется DATABASE NAME, для Interbase — SERVER
NAME, а для псевдонимов типа STANDARD — PATH. В любом случае, прежде чем ис-
пользовать псевдоним, вы должны указать базу данных, для подключения к которой он
предназначен.
После создания и настройки псевдонима вы можете использовать его в своем при-
ложении, например, чтобы указать базу данных для объекта ТТаЫе (компонент, инкап-
сулирующий таблицу базы данных). Для этого достаточно выбрать имя псевдонима из
выпадающего списка. Это очень удобно, так как при помощи одного щелчка мышкой вы
получаете нужным образом настроенное подключение. С другой стороны, изменив имя
целевой базы данных или переместив ее в другой каталог, вы должны будете внести изме-
нения только в определение псевдонима, не меняя приложения, которые этот псевдоним
используют.

Це!р

Databases jpidronaryj
;-; Q) Databases _*j Nome : Type Siie Prnls H,y
EV ЭД BCDEMOS И NAME CHARACTER 10 10
г о г
:
в & Tables •SEE NUMERIC
- И animals dbt EWEIGHT NUMERIC 2 о г
Я ©Ms fflAREA CHARACTER 20 го
: : Ot NAME _J ШВ BMP BINARY 10 10
: ilH SIZE
i ; t DB WEIGHT
: ф Щ AREA
; • ж и BMP
Т ЁВ Indices
.fiUfl VelidiV Checks
В Ё1 Referential Constraints
i ,*i gg Securrty Specs
| b'fil FamiV Members »j Ч . _. i. j i J Jj
5F«ld,,™»,™l-:.db., —• -

Рис. 1.3. Список полей таблицы animals базы данных,


соответствующей псевдониму BCDEMOS
При помощи псевдонимов и SQL Explorer можно легко исследовать структуру любой
базы данных. Щелкнув на элементе развертывания слева от имени псевдонима, вы по-
лучите доступ к списку объектов, содержащихся в соответствующей БД. Количество объ-
ектов на этом уровне иерархии зависит от типа драйвера БД. Например, для псевдонимов
типа STANDARD вы получите доступ только к списку таблиц, а для псевдонимов типа
MSACCESS — к списку таблиц и списку процедур. Наибольшее количество объектов
соответствует псевдонимам SQL-серверов. В частности, для Interbase объектов перво-
го уровня иерархии восемь. Кроме списка таблиц вы можете получить здесь доступ к
следующим объектам: Domains (домен), Views (представления), Procedures (процедуры),
Functions (функции), Generators (генераторы), Exceptions (исключения) и Blob Filters
(фильтры объектов Blob).
Замечание
При попытке открыть некоторые из псевдонимов вы можете получить диалоговое
окно с требованием ввести имя пользователя и пароль для доступа к соответствующей
базе данных. Для БД Interbase по умолчанию используется имя пользователя SYSDBA
и пароль masterkey. Для некоторых баз данных можно оставить поля для ввода имени
пользователя и пароля пустыми. Кроме того, для баз данных в формате SQL должен,
быть предварительно запущен соответствующий сервер. Например, для доступа к базам
данных Interbase необходимо сначала запустить или IBConsole, или InterBase Guardian.
24 Borland C++ Builder 6. Разработка приложений баз данных

Замечание
Не забудьте, перед тем как использовать новый псевдоним, сохранить его. Для этог
предназначена команда Apply меню Object. Соответствующая клавиатурная комбинаци
- Ctrl + A.B противном случае вы получите сообщение об ошибке.

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


любого интересующего вас объекта БД: таблицы, поля, индекса, ограничений на да^
ные и на ссылочную целостность, спецификаций безопасности и т.д. На рис. 1.3 н
вкладке Summary отображены все поля таблицы animals базы данных (псевдонима
BCDEMOS. Для каждого поля отображены также все его свойства, включая тип поля
его размер и т.д.
При выборе тех или иных объектов на левой панели окна SQL Explorer на правой па
нели могут появляться различные вкладки. Например, если вы выбрали одну из таблиц
то станет доступной вкладка Data. С помощью этой вкладки вы сможете просмотреть шп
отредактировать информацию, хранящуюся в выбранной таблице. Обратите внимание HI
панель навигации, которая расположена в верхней правой части окна SQL Explorer. ITpi
помощи кнопок этой панели вы сможете удалять или добавлять записи, сохранять или от
менять сделанные изменения, перемещаться по записям таблицы.

! Аи Detnbane Aliases

Detebages j Dicjionaty
Databases
« 6CDEWOS
В Ш Tables
!*• H animals dbf
1 ahcce Category-' Sheik' jj ^


-IJ . ""Г
•*. В bk)lTte,db
<l 1 ^i
1
QD countrydb {Category |Cornnion_Nani( jSpeeiasName., . ~*j
И cusioiy.dQ HShark Swell Shark Cephatoscyilium ventrtosur {
OS customer db J Shark Nurse Shark Ginglyrnostoma cirratum ^ i
!__ И employee. db
tj <} •' -M-:- _>J

Рис. 1.4. Результаты выполнения запроса на выборку


Заслуживает особого внимания и вкладка Enter SQL. Она становится доступна, если
вы хотя бы раз открывали псевдоним. В верхней части этой вкладки расположено окошко
для ввода текста оператора SQL. Кнопка Execute Query с изображением желтой молнии
предназначена для запуска запроса на выполнение. На рис. 1.4 отображен результат вы-
полнения запроса на выборку. Две кнопки с синими стрелками вверх и вниз позволят вам
вернуться к введенным ранее запросам. Таким образом вы можете отредактировать текст
и выполнить запрос снова, не вводя текст SQL-оператора целиком.

Словари и наборы атрибутов


Словарь (Dictionary) — это специальная таблица, предназначенная для хранения на-
боров атрибутов базы данных. Хранится эта таблица в самой базе данных среди других
таблиц. Набор атрибутов (Attribute Set) — это именованный объект, описывающий
свойства отдельных столбцов (полей) таблицы. На рис. 1.5 отображен набор атрибутов
CustNo. В частности, заданы формат отображения, минимальное и максимальное допу-
стимые значения, выравнивание данных в ячейке.
Глава 1. Базы данных и вспомогательные инструментальные средства C++ Builder 6 25

Ока.»*/ SampteOea DfOreroy

СйаЬэ,» Oirionay)
i -Ш Dictioneiy
r-: El Database!
: «-П BCDEMOS
; ш-flB DBDEMOS
• -v Я Фейлы dQASE
S И Attribute Sets
•«•И Company
:+ H Continent

Referencing Fielt
И BCDEMOS.O.:
-•BCOEMOS or
EOHOEMuviujd

Рис. 1.5. Набор атрибутов CustNo


Использование наборов атрибутов напоминает использование псевдонимов. Однажды
создав и настроив именованный набор свойств для столбца, вы сможете многократно
использовать его в своих приложениях, просто ассоциировав с ним поле набора данных
(таблицы или запроса). Если вдруг понадобится изменить одно из свойств, вам нужно
будет внести изменения только в набор атрибутов, а не просматривать все приложения,
рискуя допустить неточность. Таким образом, использование наборов атрибутов позволя-
ет унифицировать и упростить разработку информационных систем, вынося часть логики
за пределы приложений.
Чтобы создать новый словарь, следует воспользоваться пунктом New меню Object.
Если этот пункт недоступен, значит, один из словарей уже открыт, и его нужно закрыть
(пункт Close меню Object). На экране будет отображено диалоговое окно Create a new
Dictionary (см. рис. 1.6). В этом окне нужно указать имя нового словаря (под которым он
будет числиться в списке доступных словарей), из выпадающего списка выбрать для сло-
варя один из псевдонимов (они перечислены на вкладке Databases) и задать имя таблицы,
в которой будет храниться вся информация о словаре. Не обязательно, но желательно опи-
сать вкратце назначение нового словаря.

:". (Словарь для базы ценных IBLocal

Рис. 1.6. Диалоговое окно Create a new Dictionary


Первоначально новый словарь содержит два узла—Databases и Attribute Sets. Существует
несколько способов создания новых атрибутов. Можно щелкнуть правой клавишей мышки
на названии одного из имеющихся атрибутов или на названии узла Attribute Sets и выбрать из
контекстного меню пункт New. В список атрибутов будет добавлен новый атрибут под одним
из стандартных имен типа EXTFIELD1. Вам остается только задать для него подходящее имя
и настроить нужным образом требующиеся свойства.
Еще один вариант создания атрибутов — наследование от существующего столбца
(поля) таблицы. В этом случае необходимо сначала импортировать базу данных в сло-
варь. Для этого предназначен пункт Import to Dictionary контекстного меню псевдонимов
26 Borland C++ Builder 6. Разработка приложений баз данных

(вкладка Databases). В результате этой операции имя выбранного псевдонима появится в


списке Databases на вкладке Dictionary.
Более подробную информацию о работе со словарями и наборами атрибутов можно
почерпнуть из справочной системы SQL Explorer.

DatabaseDesktop
Возможности Database Desktop несколько больше, чем у SQL Explorer. С помощью
этой утилиты вы сможете создавать и модифицировать таблицы в форматах Paradox,
dBASE и SQL, добавлять, удалять, редактировать записи, сортировать информацию, вы-
полнять запросы, и многое другое. Database Desktop поставляется как отдельная утилита.
Запустить ее можно или при помощи пункта Database Desktop меню Tools, или найдя
файл dbd32.exe. Обычно этот файл расположен в каталоге /Common Files/Borland Shared/
Database Desktop/. В этом разделе кратко описаны основные возможности, предоставляе-
мые Database Desktop. За подробной информацией обратитесь к справочной системе.

Создание и модификация таблиц


Для того чтобы инициировать создание новой таблицы, нужно выбрать пункт New \
Table в меню File. Затем в маленьком диалоговом окошке Create Table следует указать тип
таблиц (точнее, тип драйвера БД). Вашему вниманию предлагается набор из драйверов
различных версий Paradox и dBASE, а также набор доступных в вашей системе ODBC-
и SQL-драйверов. К последним обычно относятся следующие: FOXPRO, MSACCESS,
SYBASE, ORACLE, INFORMIX, MSSQL, DB2 и INTRBASE. Выбрав нужный драйвер и
нажав кнопку ОК, вы получите доступ к диалоговому окну Create Table. На рис. 1.7 изо-
бражено окно для создания таблицы Interbase.

:teble>_StoreNo

s-tor,.

Рис. 1.7. Диалоговое окно для создания таблицы Interbase


В центральной части диалогового окна Create Table расположена таблица, при помощи
которой можно создать набор полей, задав для каждого из них имя (Field Name) и тип
(Туре). Для числовых типов с плавающей или фиксированной точкой может понадобиться
задать размер значений (Size) и количество знаков после запятой (Dec). Для различных
строковых типов нужно задать размер (Size). Тип поля можно выбрать из списка допу-
стимых вариантов, который отображается при щелчке правой кнопкой мышки в соответ-
ствующей ячейке. Обратите внимание на подсказку в нижней части окна. Она достаточно
информативна, чтобы вы не растерялись и сделали все правильно.
Глава 1. Базы данных и вспомогательные инструментальные средства C++ Builder б 27
Замечание
При вводе значений свойств поля необходимо руководствоваться правилами, принятыми
для баз данных выбранного формата. Особенно это касается баз данных в SQL-формате. Сама
утилита Database Desktop не всегда контролирует корректность введенного вами значения.
Например, если для поля StoreNo указать какой-либо размер (значение свойства Size), то
никаких сообщений об ошибке не будет. Однако ошибка произойдет позже, например,
когда вы попытаетесь сделать на основании этого поля индекс.

Но не спешите создавать новую таблицу «с нуля». У вас есть возможность воспользо-


ваться результатами работы, уже проделанной ранее вами или другими программистами.
Если уже существует таблица, структура которой похожа на ту, которую вы собираетесь
построить, вы можете заимствовать структуру (Borrow Struct). Если вы еще не ввели в
макет новой таблицы никакой информации, то в нижней части диалогового окна Create
Table будет доступна кнопка Borrow Struct. При нажатии этой кнопки на экране будет ото-
бражено диалоговое окно Select Borrow Table, напоминающее обычное диалоговое окно
Windows для открытия файла. В первую очередь нужно указать псевдоним (Alias), ссыла-
ющийся на базу данных, которой принадлежит нужная таблица. Затем необходимо указать
имя таблицы и нажать кнопку Открыть (Open). В результате в диалоговое окно Create
Table будет импортирована структура выбранной таблицы, при этом форматы исходной
и целевой таблицы могут не совпадать. Например, вы можете позаимствовать структуру
таблицы в формате dBASE для таблицы в формате Interbase. Далее можно часть информа-
ции удалить или модифицировать, часть информации добавить, добиваясь соответствия
структуры таблицы вашим целям.
Замечание
Если вы хотите позаимствовать структуру таблицы в формате *.db или *.dbf, то можно
обойтись и без псевдонимов. В таком случае из списка Alias окна Select Borrow Table
выберите значение None. После этого остается только указать каталог и имя файла, в
котором хранится нужная вам таблица.

Замечание
В нижней части диалогового окна Select Borrow Table расположено несколько
флажков. Устанавливая эти флажки, вы сможете добиться импорта следующих объектов:
Первичный ключ/индекс (Primary index), Проверка на допустимость (Validity check),
Таблицы просмотра (Lookup table), Вторичные индексы (Secondary index), Ссылочная
целостность (Referential integrity).

Рис. 1.8. Диалоговое окно для определения индексов


28 Borland C++ Builder 6. Разработка приложений баз данных

Дополнительные возможности диалогового окна Create Table зависят от выбранного д)


БД типа драйвера. Например, для БД в формате SQL можно задать и настроить индекс!
Соответствующее диалоговое окно представлено на рис. 1.8. Работать с ним достаточв
просто. Нужно выбрать одно или несколько полей, которые будут составлять индекс, а так»
настроить порядок сортировки и уникальность. Для таблиц Paradox вы сможете задать пар.
метры ссылочной целостности (Referential integrity), вторичные индексы (Secondary indexes),
также настроить другие объекты. К сожалению, вам не удастся сделать то же для баз данных
формате SQL. Но для этого можно воспользоваться SQL Explorer.
Замечание
Вообще, следует привыкнуть к мысли, что если вы не пользуетесь какими-либ|
инструментами от сторонних разработчиков, то вам постоянно придется переключала
от SQL Explorer к Database Desktop и обратно. Это связано с тем, что не все функцш
одного из этих инструментов реализованы во втором и наоборот. После выхода первой
версии C++ Builder предполагалось, что в последующих все необходимые функции буду
доступны в SQL Explorer, a Database Desktop больше не будет поставляться с пакетом
Однако, как видим, этого не произошло.

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


*.db и * dbf можно просто сохранить в соответствующих файлах, а для сохранения таблиц
в остальных форматах нужно выбрать подходящий псевдоним (Alias). Тип таблицы дол-
жен совпадать с типом драйвера псевдонима.
К сожалению, описание всех возможностей утилиты Database Desktop выходит за рам-
ки этой книги. Замечу только, что в ней имеется неплохой редактор запросов по образцу
(QBE — Query By Example), который, по сути, является визуальным построителем запро-
сов, а также множество дополнительных возможностей манипулировать структурой та-
блиц и данными. Узнать об этом подробно можно в справочной системе Database Desktop.
Кроме того, в следующих главах, при описании конкретных примеров, мы еще не раз
будем возвращаться и к Database Desktop, и к SQL Explorer.

Резюме
Эта глава посвящена вводным замечаниям о БД. В частности, рассматриваются
несколько основных подходов к классификации, вводятся основные понятия, исполь-
зуемые при дальнейшем изложении вопросов проектирования БД. Не имея опыта,
практически невозможно создать устойчиво работающую, надежную и удобную БД.
Рекомендации, представленные в этой главе, помогут не делать ошибок с самого на-
чала проектирования.
В конце главы описываются две утилиты, поставляемые вместе с C++ Builder 6. SQL
Explorer и Database Desktop могут оказаться очень полезными, особенно если у вас нет
специализированных инструментов от сторонних разработчиков. Описывается, как с по-
мощью этих утилит создать таблицу, настроить должным образом поля, индексы и другие
объекты. Рассказывается о способах создания и использования псевдонимов, словарей и
наборов атрибутов.
Использование механизма BDE
В ЭТОЙ ГЛАВЕ
Основы BDE
Компонент liable
Навигация, поиск, фильтрация и другие элементарные действия с набором данных
Определение состояния набора данных и объект TDataSource
Постоянные и динамические поля
Объект TField и редактор полей
Вычисляемые поля и поля подстановки
Создание связи Master/Detail
Режим кэширования изменений
Создание таблиц программным способом
Резюме

В
предыдущей главе были рассмотрены основные понятия, связанные с БД. Это
вполне логично — прежде чем начинать разработку приложений БД, необходимо
получить некоторое представление о самих базах данных, а также научиться кон-
струировать хотя бы простейшие таблицы. Следующие несколько глав посвящены разным
аспектам создания приложений, взаимодействующих с БД различных типов: вариантам
подключения к локальным и клиент-серверным базам, использованию стандартных
и клиентских наборов данных, элементов визуализации и управления данными и другим
связанным с этими вопросам.
В этой главе будут обсуждаться вопросы создания приложений, которые для связи
с базами данных используют механизм BDE. BDE часто называют также процессором
баз данных Borland. Фактически BDE представляет собой библиотеку функций API, при
помощи которых разработчик сможет подключаться к БД, получать выборки данных, ма-
нипулировать информацией и структурой таблиц и осуществлять практически все харак-
терные для информационных систем действия.
Механизм BDE предоставляет разработчику единый интерфейс для взаимодействия
приложений с БД различных типов. Для подключения к базе данных может использовать-
ся один из трех вариантов. Для подключения к локальным БД (FoxPro, dBASE, Paradox,
текстовые таблицы) и к базам данных Microsoft Access используются так называемые
естественные (Native) драйверы. Естественными они называются потому, что являют-
ся для BDE базовыми. Использование именно этих драйверов дает наиболее полный
контроль над подключением к источникам данных. Группа драйверов, называемая SQL
Links, служит для подключения к удаленным серверам баз данных (Interbase, DB2, Oracle,
Informix, MS SQL Server и Sybase). Третью группу составляют драйверы ODBC, при по-
мощи которых BDE может подключиться к любой базе данных (при условии, что для нее
существует работоспособный драйвер ODBC).
30 Borland C++ Builder 6. Разработка приложений баз данных

Есть много преимуществ использования в приложениях механизма BDE. В первую


очередь следует заметить, что это наиболее отлаженный и проверенный механизм до-
ступа к данным, так как он применялся во всех версиях C++ Builder на протяжении мно-
гих лет. Следующий аспект, на который стоит обратить внимание: BDE вплоть до пятой
версии C++ Builder был единственным механизмом доступа к данным. Это значит, что
все компоненты визуализации и управления данными (Data Controls) библиотеки VCL из-
начально оптимизировались именно под BDE. Это дает некоторый выигрыш в скорости.
Еще одно, из преимуществ дает BDE ее универсальность. Так как при помощи BDE можно
подключаться практически к любой базе данных, то затраты при переводе приложения на
базу данных другого типа минимальны. Другими словами, приложения, использующие
BDE, легко масштабируемы. И последнее преимущество, которое хотелось бы упомянуть,
— экономического характера. Формат локальных баз данных является открытым, поэто-
му за него не надо никому платить. Вследствие этого цена приложений, использующих
локальные базы данных, почти на порядок меньше точно таких же приложений, основан-
ных на базах данных SQL-форматов. В качестве примера можно привести бухгалтерские
и складские программы от фирмы 1C. Если вы разрабатываете небольшое приложение,
то вполне можно обойтись локальными базами данных, драйверы для которых являются
базовыми для BDE.
Впрочем, есть у BDE и недостатки. Универсальность BDE, предоставляя ряд пре-
имуществ, вместе с тем порождает и проблемы. Давно известно, что универсальность
и оптимальность несовместимы. Механизмы доступа, специально настроенные на кон-
кретные форматы БД, позволяют более полно и гибко использовать заложенные в них воз-
можности. Следствием универсальности является и чрезмерная требовательность к ресу-
рсам компьютера. Производительность систем на основе BDE может быть значительно
ниже, чем у систем, построенных на одном из специализированных механизмов доступа.
Не добавляют популярности BDE и разговоры о том, что в дальнейшем Borland не будет
поддерживать этот механизм.
Несмотря на все эти недостатки и слухи о скорой кончине, изучение приложений БД
решено было начать именно с использования BDE. На это есть несколько причин. Во-
первых, не очень верится, что BDE в ближайшее время потеряет значение. Во-вторых,
универсальность и стандартность подходов, вызывающие отвращение у опытных профес-
сиональных разработчиков, очень импонируют начинающим и даже достаточно опытным
программистам. Поэтому армия пользователей BDE вряд ли в скором времени уменьшит-
ся. Третья причина— чисто методическая. Изучив принципы построения приложений
на основе BDE, впоследствии гораздо проще обсуждать более сложные и специфичные
концепции, связанные с другими механизмами доступа к данным.
В следующем разделе попробуем создать простое приложение и рассмотрим его
структуру.
-

Создаем простое приложение


Прежде чем создавать приложение, использующее информацию из какой-либо ба-
зы данных, эту базу данных нужно где-то взять. Например, создать. Откройте Database
Desktop и создайте новую таблицу в формате Paradox 7. Введите определение трех полей
(см. рис. 2.1):
1. Field Name — CategoryNo, Type — Autoincrement (+), Key — * .
Глава 2. Использование механизма ВОЕ 31
2. Field Name — Category, Type — Alpha (A), Size — 25.
3. Field Name — Description, Type — Alpha (A), Size — 50.

Рис. 2.1. Структура таблицы Category


Таблица, структура которой представлена на рис. 2.1, предназначена для хранения
информации о категориях товаров, продаваемых некой гипотетической фирмой Big
Computer. Первое поле этой таблицы исключительно служебное — оно является пер-
вичным ключом, т.е. служит для однозначной идентификации каждой записи таблицы.
В качестве типа этого поля удобно выбрать Autoincrement (в соответствующей ячейке
столбца Туре отображается только символ «+»). Это значит, что задачу заполнения
поля данными берет на себя Paradox. При добавлении в таблицу новой записи в поле
CategoryNo будет подставлено числовое значение, на единицу превышающее текущее
максимальное значение этого поля. Отредактировать значение поля типа Autoincrement
невозможно.
Замечание
При вставке новой записи в таблицу новое значение не будет отображено в поле
CategoryNo. Это произойдет только после того, как вы сохраните запись в таблице.
Для этого служит кнопка Post (кнопка с галочкой) навигационной панели утилиты SQL
Explorer.

Чтобы сделать поле CategoryNo ключевым, нужно дважды щелкнуть мышкой в соот-
ветствующей ячейке столбца Key. Тогда в этой ячейке появится символ звездочки (*).
Любое поле, для которого в столбце Key отображен символ звездочки, входит в состав
первичного ключа. При создании ключа Paradox автоматически создает и первичный
индекс, называемый <Primary>. Остальные индексы, создаваемые программистом, на-
зываются вторичными (Secondary Indexes).
Следующие два поля имеют текстовый тип (Alpha). Поле Category размером 25 сим-
волов будет содержать наименование категории товаров, а поле Description размером 50
символов предназначено для краткого описания каждой категории. Поле Category явля-
ется обязательным, поэтому для него необходимо установить флажок Required Field. Это
приведет к тому, что для этой таблицы будет создано объект Проверка допустимости
(Validity Checks).
Теперь таблицу можно сохранить. Для начала создайте какой-нибудь каталог, напри-
32 Borland C++ Builder 6. Разработка приложений баз данных

мер, D:\Data. Далее, нажмите кнопку Save As, выберите созданный ранее каталог и укаж]
те для таблицы имя — Category. Кроме файла таблицы category.db в этом же каталоге б;
дут еще два файла — category.px (для хранения информации о primary key) и category. v>
(объекты Validity Checks). Кроме этих файлов могут быть и другие. Например, вторичнь
индексы будут храниться в файлах category.xgO и category.ygO (первый индекс), categi
ry.xgl и category.ygl и т.д. Поля типа MEMO будут храниться в файле category, mb и т.^
1
Таким образом, для одной таблицы Paradox может быть создано много файлов, что, коне
но же, затрудняет работу над большими проектами, содержащими множество таблиц.
Замечание
Прежде чем сохранить таблицу на диске, нужно выбрать язык, который буде
использован при вводе в таблицу текстовой информации. Для этого из комбинированной
списка Table properties следует выбрать пункт Table Language и нажать кнопку Modify. Hi
экране будет отображено диалоговое окно, в котором нужно выбрать подходящий язык
Если вы собираетесь вводить информацию в таблицу Paradox на русском, то подойде
Paradox Cyrr 866. К сожалению. Database Desktop, скорее всего, не будет правильнс
отображать русский текст в режиме просмотра или модификации таблицы. Зато SQI
Explorer будет отображать русский текст вполне корректно.

В дальнейшем понадобится псевдоним, ссылающийся на каталог, в котором была со-


хранена таблица. Все остальные таблицы, которые будут храниться в этом же каталоге,
также будут доступны при помощи этого псевдонима. Напомню, базой данных в случае
локальных форматов (Paradox, dBASE, FoxPro) является набор таблиц, которые хранят-
ся в одном каталоге. Итак, откройте SQL Explorer и создайте новый псевдоним типа
STANDARD. В качестве значения свойства DEFAULT DRIVER укажите PARADOX (хотя
оно является значением по умолчанию), а для свойства РАТИ задайте путь и имя катало-
га, выбранного для хранения таблицы Category (например, D:\Datd). Задайте для нового
псевдонима какое-нибудь имя (например, MyData) и сохраните изменения (пункт Apply
меню Object).
Для дальнейшего использования таблицу Category нужно заполнить данными. Для
этого также удобнее использовать SQL Explorer. Откройте созданный чуть ранее псевдо-
ним MyData и выберите таблицу (пока единственную) Category. На правой панели окна
SQL Explorer выберите вкладку Data. Заполните таблицу данными, например так, как на
рис. 2.2. Для вставки, удаления и сохранения изменений вам понадобится панель навига-
ции (в верхней правой части окна). Для вставки новой записи предназначена кнопка Insert
record (символ «+»), для удаления — кнопка Delete record (символ «-»), а для сохранения
изменений — Post edit (символ галочки).

1;Принтеры ! Принтеры струйные, матричные, лазерные


2 Сканеры
З'Ппаты материнские Платы материнские дли Intel и AMD
Л Процессоры Процессоры Intel. Amd и VIA
5 .Модули памяти Моду пи памяти DIMM. DIMM DDR и RlMM
Б Видеокарты Видеокарты Radeon и GeForce
7 Мониторы ЭПТ- и ЖКИ-мониторы
8 Винчестеры Винчестеры Fujitsu, Samsung и Western Digrtel
9.Устройства ввода Клавиатуры, мышки, джойстики и т.д.
10 Мультимедиа Эеуиоеые карты, колонки, приводы CD-ROM
11 'Модемы Внешние и внутренние модемы
t? Сетевое оборудование Сетевые карты, жабы, коммутаторы

Рис. 2.2. Данные таблицы Category


Глава 2. Использование механизма ВОЕ 33

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


данных. Откройте в C++ Builder новый проект (приложение) и запустите утилиту
SQL Explorer. Открыв псевдоним MyData, перетащите на форму приложения таблицу
Category. На форме появятся три компонента: TDBGrid (сетка) и два невизуальных
компонента— ТТаЫе (таблица) viTDataSource (источник данных). Невизуальные ком-
поненты обычно размещаются один над другим, так что на форме виден только один.
Перетащив его в другую область формы, вы получите доступ и ко второму. Выберите объ-
ект типа ТТаЫе и в инспекторе объектов задайте для свойства Active значение true. Все,
приложение готово. В режиме конструктора приложение будет выглядеть примерно так,
как на рис. 2.3. Обратите внимание на то, что информация из таблицы будет отображаться
в объекте типа TDBGrid даже без компиляции самого приложения.
Попробуем проанализировать, что же на самом деле произошло при простом пере-
таскивании таблицы. Каким образом была построена цепочка, в результате активиза-
ции которой компонент TDBGrid отобразил информацию из базы данных? Выберите
на форме компонент TDBGrid и просмотрите в инспекторе объектов его свойства.
Обратите внимание на свойство DataSource. Его значением является другой компо-
нент (об этом сигнализирует красный цвет наименования свойства), а именно объект
DataSourcel типа TDataSource, также добавленный на форму. Обратившись теперь
к свойствам объекта DataSourcel, заметим, что его свойство DataSet имеет значение
Table]. Свойство DatabaseName объекта Tablel типа ТТаЫе указывает на созданный
нами псевдоним MyData, а свойство TableName имеет значение Category.db. При по-
мощи этих двух свойств осуществляется подключение к нужной таблице. Активизация
цепочки таблица БД - компонент TDBGrid происходит при переключении свойства
Active компонента ТТаЫе в значение true.

1 Принтеры Принтеры струйнв ••чтриччыо i


2 Сканеры Сканеры планшетные
3 Ппаты материнские ПЛАТЫ материнские ала Intel и AMD

Рис. 2.3. Простое приложение БД в режиме конструктора

Замечание
В C++ Builder б инспектор объектов предоставляет возможность просмотреть целиком
всю цепочку между элементом управления данными (например, TDBGrid) и таблицей БД.
Щелкая мышкой на элементе развертывания слева от свойства DataSource, мы получаем
доступ к списку свойств объекта DataSourcel, среди которых есть и свойство DataSet,
имеющее значение Table?. Аналогично, разворачивая список свойств объекта Tablel, мы
можем получить доступ к его свойствам DatabaseName и TableName. Таким образом,
инспектор объектов предоставляет возможность в удобной иерархической форме
увидеть связь между конкретным элементом управления данными и таблицей БД.

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

2 Зак. 319
34 Borland C++ Builder 6. Разработка приложений баз данных

компонент ТТаЫе, который является одним из наследников класса TDataSet (набор да


ных). Класс ТТаЫе инкапсулирует таблицу БД, подключаясь к ней при помощи свойс
Database и TableName. Кроме ТТаЫе среди наследников TDataSet наиболее важны TQue,
(запрос) и TStoredProc (хранимая процедура).
Компонент ТТаЫе инкапсулирует таблицу БД, включая ее структуру и данные, Крол
сведений о конкретной таблице, компонент ТТаЫе содержит методы, позволяющие Heni
средственно решать разнообразные задачи, в частности, обрабатывать пароли, манипул:
ровать индексами, записями и т. д. Результаты действия большинства из этих функци
используются компонентом TDataSource, который является посредником между наборе
данных (ТТаЫе, TQuery или TStoredProc) и компонентами визуализации и управлет
данными (TDBGrid, TDBEdit и др.). На первый взгляд непонятно, зачем нужен такой п<
средник, однако со временем становится очевидно, что на самом деле его существовав
вполне естественно.
Далее в этой и следующей главе будут подробно обсуждаться наследники класс
TDataSet — ТТаЫе и TQuery, а также компоненты TDataSource, TDatabase, класс TFiek
Будет рассмотрено и множество других вопросов, что поможет создавать приличные при
ложения, основанные на использовании механизма BDE.

Использование компонента ТТаЫе


В шестой версии C++ Builder компонент ТТаЫе расположен на новой вкладке, котора!
называется BDE. Таким образом в Borland видимо решили подчеркнуть обособленное^
и неперспективность механизма BDE, перенеся на эту вкладку с вкладки Data Access вс<
связанные с BDE компоненты.
Компонент ТТаЫе — наследник класса TDataSet, использующий большую част!
объявленных в нем свойств, методов и событий. Сам класс TDataSet абстрактен, т. е
экземпляр этого типа создать нельзя. TDataSet предназначен для того, чтобы предо-
ставить интерфейс, состоящий из методов, событий и свойств, для манипуляции дан-
ными, представленными в виде строк и столбцов (т. е. в табличной форме). Большая
часть этих методов, событий и свойств чисто виртуальны, т. е. они объявлены, но не
реализованы. Любой наследник объекта TDataSet должен переобъявить и реализовать
все виртуальные методы.
Компонент ТТаЫе переопределяет виртуальные методы объекта TDataSet, вводя
при этом много новых свойств и методов, позволяющих напрямую работать с любой
таблицей БД. Можно считать ТТаЫе программной оболочкой таблицы БД. Контекстное
меню объекта ТТаЫе, подключенного к какой-либо таблице, позволяет выполнить сле-
дующее: удалить таблицу (Delete Table), переименовать ее (Rename Table), обновить
определение таблицы (Update Table Definition), открыть SQL Explorer на соответству-
ющей таблице (Explore).
Планомерное и полное изучение интерфейса ТТаЫе (стиль, предлагаемый справочной
системой) хотя и интересно, но не очень эффективно. Поэтому в этом разделе предпри-
нята попытка рассмотреть основные свойства, методы и события ТТаЫе, исходя из по-
требности в них при решении тех или иных задач. Следует заметить, что большая часть
рассматриваемых задач решается одинаково для всех основных наследников TDataSet
(ТТаЫе, TQuery, TStoredProc), поэтому, если не оговорено обратное, речь будет идти
о наборах данных. Под набором данных будет пониматься не объект TDataSet, а один из
его наследников (ТТаЫе, TQuery, TStoredProc).
Глава 2. Использование механизма BDE 35

Активация и деактивация набора данных


Изучение функциональных возможностей любого объекта вполне естественно на-
чинать с простейших задач. Именно такова активация и деактивация набора данных
(или открытие и закрытие). Если в режиме конструктора задать для свойства Active
компонента-наследника TDataSet значение true, то набор данных сможет обеспечить
все зависящие от него элементы управления данными. Задание значения false деакти-
вирует (закроет) набор данных. Все элементы управления, связанные с набором дан-
ных, очистятся. Кроме того, при изменении многих свойств набора данных требуется,
чтобы он был деактивирован (закрыт). При изменении в режиме конструктора таких
свойств, как DatabaseName или TableName (для ТТаЫе), свойство Active автоматиче-
ски принимает значение false.
Однако в подавляющем большинстве случаев свойством Active в режиме конструктора
пользуются только при отладке, или если набор данных заведомо очень мал. Например,
в таблице Category не может быть больше полусотни записей, поэтому открытие формы, эле-
менты управления которой подключены к этой таблице, не вызовет проблем. Если в режиме
конструктора задать свойству Active значение true для таблицы, имеющей сотни тысяч запи-
сей, может возникнуть несколько проблем. Например, форма для отображения данных будет
открываться неоправданно долго или будет поддерживаться связь с базой данных тогда, когда
это не нужно. В любом случае, это может привести к излишнему расходованию ресурсов.
Общий подход к использованию свойства Active таков: для него оставляют значение
false, а затем в приложении по мере необходимости открывают или закрывают набор
данных программным путем. Существует два способа управления активностью набора
данных — с использованием свойства Active или пары методов Open и Close. Объявление
свойства Active в классе TDataSet выглядит так:
property bool Active =
(read=GetActive, write=SetActive, default=0};
В программе свойство Active можно использовать для чтения состояния набора дан-
ных. В следующем фрагменте кода определяется состояние набора данных и выводится
соответствующее сообщение.
if(Tablel->Active) ShowMessage("Ha6op данных открыт!");
else ShowMessage("Набор данных закрыт!");
Так же просто используется свойство Active и для активации/деактивации набора дан-
ных. Чтобы показать это, можно воспользоваться простым приложением, созданным нами
ранее. Разместите в верхней правой части формы кнопку (компонент TButton с вкладки
Standard) и задайте для нее имя (Name) DeactButton и подпись (Caption) Deactivate. В код
обработчика ее события OnClick введите следующий текст:
Tablel->Active=!Table1->Active;
if(Tablel->Active) DeactButton->Caption="Deactivate";
else DeactButton->Caption="Activate";
Переключение состояния набора данных происходит в первой строке: значение свой-
ства Active просто меняется на противоположное. Остальные две строки кода нужны толь-
ко для того, чтобы менять соответствующим образом надпись на кнопке. Разместите на
форме еще одну кнопку. Задайте для нее надпись Status, а в тело ее обработчика события
OnClick включите код проверки состояния, приведенный чуть выше. Таким образом, на-
36 Borland C++ Builder 6. Разработка приложений баз данных

жимая кнопку Deactivate, можно отключать или включать набор данных, а при помош
кнопки Status проверять его состояние (см. рис. 2.4).
Второй способ активации/деактивации набора данных заключается в применении м<
тодов Open и Close. Объявление этих методов имеет следующий вид.
void fastcall Open(void);
void fastcall Close(void);
Вызов любого из этих методов соответствующим образом меняет и значение свойств
Active. Метод Open устанавливает для Active значение true, метод Close устанавливав
значение false. Учитывая это, обработчик события OnClick для кнопки DeactButton мож»
представить в таком виде:
if(Tablel->Active)
{
Tablel->Close() ;
DeactButton ->Сарtion="Activate";
}
else
{
Tablel->0pen();
DeactBut ton->Caption="Deactivate";
}
Очевидно, что использование свойства Active во многих случаях позволяет писать
более компактный код, зато применение методов Open и Close позволяет сделать код не-
сколько производительней.

1 ;Принт9ры Принтер» струйны


2-Сканеры 'Сканеры лланшетн:
3'Платы матер Ни ИШНЗгски
' -1!Проиессоры""р НЕ*"!"!^.
Б;Мряупи псмч-" .I^»fio^:aeHM*'0'TXjjt#?{'' " j, DIMM. DIMM DDR и RIN-f^
Ь Видеокарты ' ":/
^Мониторы'

Рис. 2.4. Иллюстрация возможностей свойства Active

Свойства подключения к таблице


Непосредственно за подключение компонента ТТаЫе к таблице отвечают свойства
DatabaseName и TableName. Есть еще свойство Exclusive, которое позволяет открывать та-
блицы Paradox и dBASE в монопольном режиме. Пришло время познакомиться поближе
с вариантами использования этих свойств.
В качестве значения свойства DatabaseName в конструкторе обычно указывается имя
одного из зарегистрированных в системе псевдонимов. Вы можете выбрать любой псев-
доним из выпадающего списка, ассоциированного со свойством. Однако использование
псевдонимов — не единственный способ задания целевой базы данных для локальных
таблиц (для таблиц dBASE, FoxPro, Paradox). Для локальных таблиц в качестве значения
свойства DatabaseName можно указать каталог, в котором хранятся таблицы. Например,
Глава 2. Использование механизма ВОЕ 37

если бы мы не зарегистрировали псевдоним MyData, то доступ к таблице Category


можно было бы получить, указав в качестве значения свойства DatabaseName строку
«D:\Data» (или тот путь, который вы выбрали для хранения таблицы Category).
Независимо от того; каким образом была указана база данных, выпадающий список, ас-
социированный со свойством TableName, будет содержать список доступных в базе данных
таблиц. Если в свойстве DatabaseName указан каталог, то в списке могут быть одновременно
таблицы различных типов: dBASE, FoxPro, Paradox. Как ни парадоксально это звучит, но
в этом случае непонятен будет тип базы данных. Так как для локальных таблиц базой данных
является совокупность таблиц, находящихся в одном каталоге, то вполне логично с этой точки
зрения, что в одну базу данных будут входить таблицы трех совершенно разных типов (не го-
воря уже о различиях в версиях). Конечно же, такую ситуацию допускать нежелательно.
Гораздо большие возможности предоставляются при использовании свойств
DatabaseName и TableName программным путем. Объявления этих свойств выглядят так:
property AnsiString DatabaseName =
{read=FDatabaseName, w r i t e = S e t D a t a b a s e N a m e } ;
property AnsiString TableName =
{read=FTableName, write=SetTableName};
Ниже приведено два отрывка кода, иллюстрирующих применение свойств
DatabaseName и TableName. В первом отрывке используется псевдоним, а во втором —
каталог с таблицами.
Tablel->Active = false;
Tablel->DatabaseName = "MyData";
Tablel->TableName = "Category";
Tablel->Active = true;
Tablel->Active = false;
Tablel->DatabaseName = "D:\Data";
Tablel->TableName = "Category";
Tablel->Active = true;
В обоих случаях в первой строке закрывается набор данных, так как при открытом
наборе данных следующие действия производить нельзя. Если вы уверены, что набор
данных уже закрыт, то первая строка не нужна.

Таблица Items
Для дальнейшего изложения нам понадобится таблица, имеющая в своем составе
достаточно много разнообразных записей. Вполне подойдет таблица, содержащая
перечень всех товаров, которыми торгует вышеупомянутая гипотетическая фирма Big
Computer. Каждый из товаров должен принадлежать к одной из категорий, перечис-
ленных в таблице Category.
Для создания нужной нам таблицы снова воспользуемся утилитой Database Desktop.
Создайте новую таблицу типа Paradox 7 и добавьте определения следующих полей:
Q ItemNo. Тип (Туре) — Autoincrement. Это поле следует также отметить звездочкой
в столбце Key, так как его предполагается использовать в качестве первичного ключа.
Q Item. Тип (Туре) —Alpha, размер (Size) — 50. Предназначено для хранения наименований
товаров. Установите флажок Required Field, так как это поле не может быть пустым.
38 Borland C++ Builder 6. Разработка приложений баз данных

Q Category. Тип (Туре) — Long Integer. Флажок Required Field должен быть установле
Это поле предназначено для связи с таблицей Category, т. е. оно будет внешним клк
чом. Значение, хранящееся в поле Category, должно ссылаться на первичный клк
1
таблицы Category. Обратите внимание на следующее: первичный и внешний клю
связываемых таблиц должны иметь один и тот же тип, иначе связь установить i
удастся. В данной ситуации мы поступили правильно, так как тип Long Integer эквив;
лентен типу Autoincrement.
Q Description. Тип (Туре) —Alpha, размер (Size) — 100. Необязательное поле, в которо
может быть размещено краткое описание товара.
Q Index. Тип (Туре) — Alpha, размер (Size) — 5. Это текстовое поле длиной 5 символе
служит для однозначной идентификации товаров, если учет ведется в нескольких н
связанных между собой БД. Важность поля Index становится понятной, если учест
тот факт, что в разных таблицах Items одному и тому же товару будут соответствоват
различные первичные ключи. В такой ситуации тщательно разработанная систем
индексов (артикулов и т.д.) поможет избавиться от всех проблем, связанных с синх
ронизацией данных из различных источников. В частности, в состав индекса можн<
ввести информацию о категории, подкатегории и т.д. В данной таблице поле Inde
имеет длину пять символов. Предполагается, что первый символ указывает на катего
рию, второй — на подкатегорию, третий может обозначать разновидность товара шн
его производителя. Последние два (или три) символа указывают на конкретный товар
Будем считать, что поле Index является обязательным, так что флажок Required Fields
нужно установить.
О Price. Тип (Туре) — Money. В поле Price будет храниться цена товара. Это поле также
обязательно.
Таблица Items в режиме конструктора изображена на рис. 2.5.

Рис. 2.5. Таблица Items в режиме конструктора.


На следующем этапе нужно связать таблицы Items и Category. Для этого выбери-
те в комбинированном списке Table properties (в правом верхнем углу окна) позицию
Referential Integrity и нажмите кнопку Define. На экране будет отображено диалоговое
окно, которое поможет вам задать необходимую связь. В левой части этого окна находится
Глава 2. Использование механизма ВОЕ 39
список полей таблицы Items (см. рис. 2.6), а в правой — список таблиц базы данных (т.е.
таблиц, расположенных в выбранном каталоге). Между этими списками расположены два
поля: слева — Child fields, а справа — Parent s key. Выберите в левом списке поле Category
и нажмите кнопку с жирной стрелкой вправо. В результате его название и тип появятся
в поле Child fields. В правом списке выберите таблицу Category и нажмите на кнопку
с жирной стрелкой влево. Database Desktop подставит в поле Parent s key имя первичного
ключа таблицы Category. Далее, выберите переключатель Cascade в группе Update rule,
чтобы при изменении первичного ключа таблицы Category каскадно обновлялись значе-
ния связанного с ним внешнего ключа таблицы Items. Наконец, установите флажок Strict
referential integrity. Это заставит Paradox следить за выполнением всех предусмотренных
правил целостности данных.
Замечание
Если первичный ключ является составным, т.е. содержит несколько полей, то
и внешний ключ должен содержать столько же полей соответствующих типов (в порядке
их перечисления).

Рис. 2.6. При помощи диалогового окна Referential Integrity


утилиты Database Desktop можно задать связь между таблицами
После всех операций по установке связи нажмите кнопку ОК. Вам будет предложено
указать имя для созданного ограничения на ссылочную целостность. Можно указать, на-
пример, имя Category — по имени внешнего ключа, участвующего в связи. Чтобы под-
черкнуть, что речь идет именно о ссылочной целостности (Referential Integrity), в конец
имени можно добавить буквы R и I — CategoryRI.
Замечание
После того как вы установите связь между таблицами Items и Category, закройте
таблицу Items и откройте таблицу Category. В комбинированном списке Table properties
выберите пункт Dependent tables (зависимые таблицы). Вы можете убедиться в том, что
таблица Items.db появилась в списке таблиц, зависящих от таблицы Category.

Так как предполагается, что таблица Items может быть достаточно большой, для уско-
рения поиска, выборки и сортировки нужно определить индексы. Для этого выберите
в комбинированном списке Table properties пункт Secondary Indexes. Обратите внимание
на то, что в списке вторичных индексов уже присутствует индекс Category. Этот индекс
был создан автоматически на основе внешнего ключа при создании связи.
40 Borland C++ Builder 6. Разработка приложений баз данных

Предполагая, что основные операции поиска и сортировки будут проводиться с участие\


поля Item, именно по этому полю и создадим еще один вторичный индекс. Нажмите кнопк>
Define и в появившемся диалоговом окне Define Secondary Index в списке полей (Fields) выбе-
рите поле Item. Нажмите кнопку с жирной стрелкой вправо, чтобы перевести выбранное поле
в список индексированных (Indexedfields) и нажмите кнопку ОК. Вам будет предложено веот
для нового индекса имя. Укажите что-нибудь вроде ItemNDX(cM. рис. 2.5).
Замечание
Чтобы проверить, как изменилась производительность при появлении нового индекса,
можно добавить еще один индекс, например составной, включающий поля Category
и Item. Однако для таблиц, содержащих не более двух-трех тысяч записей, изменение
производительности при добавлении такого индекса вряд ли будет заметным.

Сохраните таблицу в том же каталоге Data под именем Items и закройте Database
Desktop. Чтобы заполнить таблицу Items данными, откройте для нее SQL Explorer и перей-
дите на вкладку Data. При этом не забудьте, что все поля, кроме Description, — обязатель-
ны. Поэтому попытка оставить любое из этих полей пустым (кроме поля ItemNo, которое
является автоинкрементным) при переходе на другую запись вызовет ошибку. Вызовет
ошибку и ввод в поле Category значения, отсутствующего в поле CategoryNo таблицы
Category.
Таблица Items, содержащая все необходимые данные, располагается на поставляемом
с книгой диске в каталоге Data.

Навигация по набору данных


Сразу после открытия непустого набора данных активной становится его первая за-
пись. Для навигации по таблице кроме стандартных инструментов (мышки, полос про-
крутки, клавиш управления курсором) есть и специфические средства. К ним относится
компонент TDBNavigator и набор методов навигации.
Чтобы проиллюстрировать использование компонента TDBNavigator, создайте но-
вое приложение, добавьте в форму Form] компоненты TTable, TDataSource и TDBGrid
и свяжите их так, как описано в предыдущем примере. В качестве источника данных для
компонента ТТаЫе укажите таблицу Items и задайте для свойства Active значение true.
В верхнюю правую часть окна формы добавьте компонент TDBNavigator. Этот компо-
нент, как и большинство других компонентов, ориентированных на данные из таблиц
(вкладка Data Controls), имеет свойство DataSource. При помощи этого свойства свяжите
TDBNavigator с компонентом TDataSource. Скомпилируйте приложение и запустите его
на выполнение.
Строго говоря, панель навигации TDBNavigator, как и соответствующая панель
утилиты SQL Explorer, предназначена не только для навигации по записям. Собственно
для навигации используются только первые четыре кнопки (крайние слева). Функции
этих кнопок, если двигаться слева направо, таковы (см. рис. 2.7): переход к первой
записи (First), к предыдущей записи (Prior), к следующей (Next), к последней (Last).
Остальные кнопки панели навигации используются для манипуляций записями, так
что изображенное на рис. 2.7 приложение вполне можно использовать для заполнения
таблицы Items данными.
Если какие-либо из кнопок на панели навигации не нужны, их можно удалить. Для
этого существует свойство панели навигации VisibleButtons. Это агрегатное свойство
Глава 2. Использование механизма BDE 41
представляет собой набор свойств логического типа. Каждое из этих свойств соответству-
ет конкретной кнопке на панели навигации. Установив значение такого свойства в false,
вы удалите соответствующую кнопку с панели. При помощи набора этих свойств можно
управлять видом панели, как в режиме конструктора, так и программно.

1;SamsungML-4SQO
'

3 Epson EFt-590QL
:
4 CanonLBP-810 GDI, 600dp,, a:',
5 Brother HL-1 240 Mono Laser А ОЗУ гмб, еось;';
6 Epson AcuLoserCIQOO !т, ОЗУ 16(5;-;
7 Epson AcuLsssrC200D
B:HPLaserJet1000w :А4 ОЗУ! Мб. GD '/
А^ озув(?г)Мб/''
10:HP Laser JetlZZG "А4. сканер 60'J dp
11 HP LaseiJet 3200 -М. сканер BCD dp
12:HP Laser JelZZQG
13 -Epson Stylus Color CJOSX ;A4. а т. 720 dpl.
HJEpsor. Stylus Color «DUX
15 lEpson Stylus Color C60 -A1. цвет. 2883x72^
• ' - H

Рис. 2.7. Компонент TDBNavigator позволяет не только перемещаться


по набору данных, но и манипулировать записями
Для навигации по набору данных в процессе выполнения программы в компоненте
ТТаЫе предусмотрены пять методов и два свойства:
void _fastcall Fi rst(void) // Методы
void _fastcall Prior(void)
void _fastca!1 Next(void);
void fastcall Last(void) ;
int fastcall MoveBy(int Distance);
property bool Bof = (read = FBof, nodefault}; //Свойства
property bool Eof = (read=FEof, nodefault};
Первые четыре метода по функциям аналогичны соответствующим кнопкам панели
навигации. Метод MoveBy перемещает позицию курсора в наборе данных на Distance за-
писей вперед. Значение Distance может быть как положительным, так и отрицательным.
В последнем случае курсор будет перемещаться по набору данных в сторону первой за-
писи (назад). В частности, вызов метода MoveBy(1) эквивалентен вызову метода Next{),
а вызов MoveBy(-l)— вызову Prior(). Метод MoveBy() возвращает количество записей,
на которые был перемещен указатель активной записи. В большинстве случаев возвра-
щаемое значение совпадает со значением Distance. Но если вы хотите перейти за пределы
набора данных, указатель будет установлен на первую или последнюю запись набора,
в зависимости от направления перехода, а возвращено будет количество записей, на кото-
рые был осуществлен переход.
Свойства Bof (Begin of file) и Eof (End of file) позволяют определить, достигнута ли пер-
вая и последняя запись набора данных соответственно. Свойство Bof принимает значение
true в одном из четырех случаев:
U при открытии набора данных;
Q при вызове метода First (или при нажатии соответствующей кнопки панели навигации,
что одно и то же);
42 Borland C++ Builder 6. Разработка приложений баз данных

О при вызове метода Prior, если активной была первая запись;


Q при вызове метода SetRange для пустого диапазона или набора данных.
Во всех остальных случаях свойство Яо/имеет значение false. Почти то же можно ска
зать и о свойстве Eof, которое принимает значение true также в четырех случаях:
G при открытии пустого набора данных;
G при вызове метода Last;
О при вызове метода Next, если активной была последняя запись;
1_1 при вызове метода SetRange для пустого диапазона или набора данных.
Свойства Во/ и Eof доступны только во время выполнения программы. С их по-
мощью, например, можно организовать цикл просмотра всех записей набора данных.
Когда происходит определенное событие (например, событие AfterScroll компонента
TTable) можно проверить значение свойств Во/и Eof, чтобы, например, разрешить или
запретить доступ к тем или иным кнопкам или другим элементам управления. Еще
один, менее очевидный вариант — если загружена не вся таблица, то при попытке
перейти за первую (Bof = = true + метод Prior) или последнюю (Eof ' = = true + метод
Next) запись загруженного набора данных можно организовать подкачку предыдущей
или последующей части таблицы.
Чтобы проиллюстрировать некоторые способы использования методов навигации
и свойств Bof и Eof компонента TTable, сконструируем небольшое приложение. Итак,
создайте новое приложение VCL и расположите на пустой форме три компонента TPanel.
Выровняйте компоненты TPanel по высоте и расположите их друг за другом по горизон-
тали в верхней части рабочей области окна формы, очистив их свойства Caption. Чтобы
рельефно отделить каждую из трех групп элементов управления, которые планируется
разместить на панелях, можно установить для каждой панели свойство Bevellnner в значе-
ние bvRaised, а свойство BevelOuter в значение bvLowered.
На панель, расположенную слева, поместите четыре кнопки (компоненты TButton).
Задайте для их свойств Name значения (слева направо): BFirst, BPrior, BNext и BLast,
а для свойств Caption — значения First, Prior, Next и Last соответственно. Для свойства
Tag кнопок последовательно задайте значения 1 (BFirst), 2 (BPrior), 3 (BNext) и 4 (BLast)
Измените размеры кнопок так, чтобы на них помещались их надписи, и разместите их
компактно на панели. Подровняйте размер панели.
На среднюю панель поместите два компонента TEdit, а между ними кнопку TButton. Для
кнопки задайте имя BMoveBy (свойство Name) и подпись (Caption) MoveBy. Для компонентов
TEdit следует очистить свойство Text. Задайте для них следующие имена (слева направо):
EPlan и EReally. Подровняйте размеры и положение всех элементов. И, наконец, на третьей
панели расположите два компонента TEdit и слева от каждого объекта разместите метку
(TLabel). Для меток задайте подписи (Caption): Bof и Eof (слева направо). Для компонентов
TEdit очистите свойство Text и задайте имена: EBofu EEof. В заключение, как всегда, следует
подровнять размеры и положение всех элементов управления.
Ниже панелей разместите сетку (компонент TDBGrid(DBGridl)), а также компоненты
TDataSource (DataSourcel) и TTable (Tablet). Свяжите их между собой так, как это опи-
сывалось выше. Компонент Table 1 подключите к таблице Items, а для его свойства Active
задайте значение false. Получившаяся форма должна выглядеть примерно так, как изо-
бражено на рис. 2.8.
Глава 2. Использование механизма ВОЕ 43

* : i Fffrfj Pnpfj Ne^j: 1!шI ' ; i ~% McveByj j

Рис. 2.8. Приложение, иллюстрирующее применение


методов навигации и свойств Bof и Eof
На следующем шаге нужно создать код, который заставит работать все компоненты,
как единое целое. Выделите на форме кнопку BFirst, перейдите в инспекторе объектов
на вкладку Events и дважды щелкните мышкой напротив события OnClick. C++ Builder
сформирует заготовку обработчика события BFirstClick. Задайте этот же обработчик со-
бытий OnClick и для остальных кнопок группы (выбрав имя обработчика из выпадающего
списка). В тело обработчика события введите следующий код:
void fastcall T F o r m l : : B F i r s t C l i c k ( T O b j e c t *Sender)

TButton * Temp;
Temp=dynamic_cast<TButton *>(Sender);
if(Temp)

switch(Temp->Tag)

case 1:
Tablel->First(); break;
case 2 :
Tablel->Prior(); break;
case 3:
Tablel->Next(); break;
case 4:
Tablel->Last(); break;

BFirst->Enabled=!Tablel->Bof;
BPrior->Enabled=!Tablel->Bof;
BNext->Enabled=!Tablel->Eof;
Blast->Enabled=!Tablel->Eof;

В первой строке обработчика объявляется временная переменная Temp типа TButton.


Во второй строке этой переменной присваивается результат динамического приведения
к типу TButton аргумента обработчика Sender. Так проверяется, был ли вызван обработчик
44 Borland C++ Builder 6. Разработка приложений баз данных

события для кнопки или для другого компонента (например, по ошибке). Все эти дей
ствия нужны в связи с тем, что один и тот же обработчик события используется сразу дл:
четырех кнопок. Если динамическое приведение было успешным, проверяется свойств!
Tag кнопки. Напомню, что при создании мы задали для четырех кнопок первой группь
свойству Tag значения от 1 до 4. Определив таким образом, какая из кнопок была на
жата, выполняем соответствующий метод компонента ТТаЫе. В заключение при помощ!
свойств Во/ и £0/задается возможность доступа к кнопкам. Как только становится истин
ным свойство Во/, кнопки BFirst и BPrior будут недоступны, а при истинности свойств!
Eof недоступными становятся кнопки BNext и BLast. Как только Bofvam Eof приобретаю'
значение false, доступными становятся и соответствующие кнопки.
Вторая группа элементов управления состоит из кнопки BMoveBy и элементов редак
тирования EPlan и EReally. Чтобы переместиться по набору данных на нужное количестве
записей вперед или назад, следует указать соответствующее значение в элементе редак-
тирования EPlan и нажать кнопку BMoveBy, Количество записей, на которое реальнс
переместился указатель в наборе данных, будет отображено в элементе редактирования
EReally. Чтобы обеспечить эту возможность, в тело обработчика события OnClick кнопки
BMoveBy введите следующий код:
void fastcall TForml::BMoveByClick(TObject *Sender)
{
EReally->Text=IntToStr
(Tablel->MoveBy(StrToIntDef(EPlan->Text,0))) ;
}
В коде этого обработчика функция StrToIntDef используется для преобразования
значения EPlan->Text типа AnsiString в значение целого типа. Второй аргумент функ-
ция StrToIntDef возвращает в случае, если попытка преобразования первого закончится
неудачей. Число записей, возвращаемое методом MoveBy, снова преобразуется к типу
AnsiString и отображается в элементе EReally.
Удобно было бы после ввода числа в поле EPlan запускать процесс перемещения по
набору данных и нажатием на клавишу Enter. Чтобы получить такую возможность, введи-
те в тело обработчика события OnKeyPress элемента EPlan следующий код:
void fastcall TForml::EPlanKeyPress(TObject «Sender, char &Key)
{
if(Key == VK_RETURN) BMoveByClick(NULL);
}
Как известно, это событие происходит в случае, если пользователь нажимает одну из
символьных клавиш (т. е. клавиш, которым соответствует один из символов ASCII) в то
время, когда элемент имел фокус ввода. В данном случае, если была нажата клавиша Enter
(VK_RETURN— константа из набора виртуальных клавиш, соответствующая клавише
Enter), то вызывается обработчик нажатия клавиши BMoveBy с аргументом NULL.
Элементы редактирования ЕВо/н ££о/должны отображать, соответственно, значения
свойств Во/ и Eof набора данных TTable. Для того чтобы информация в этих элементах
управления обновлялась своевременно, можно воспользоваться событием A/terScroll ком-
понента TTable. Это событие происходит всякий раз, как только перемещается указатель
активной записи, независимо от способа, которым это было сделано (при помощи мышки,
клавиш управления курсором или вызовом одного из методов навигации). В тело обработ-
чика события AfterScroll введите следующий код:
Глава 2. Использование механизма ВОЕ 45

void fastcall TForml::TablelAfterScroll(TDataSet *DataSet)

if(DataSet->Bof) EBof->Text="true";
else EBof->Text="false";
if(DataSet->Eof) EEof->Text="true";
else EEof->Text="false";

В тело обработчика события AfterScroll передается один параметр — указатель на ак-


тивный набор данных DataSet. Проверяя значения свойств .So/и .Ео/этого набора данных,
мы можем задать соответствующие значения для элементов редактирования ЕВо/н EEof.
В заключение остается задать действия, которые нужно произвести при создании формы,
а именно: открыть набор данных и запретить доступ к кнопкам BFirst и BPrior (так как
активной будет первая запись). Ниже приведен код обработчика события OnCreate формы
Forml:
void fastcall TForml::FormCreate(TObject *5ender)

Tablel->0pen();
BFirst->Enabled=false;
BPrior->Enabled=false;
}
Сохраните получившийся проект на диске (у меня он называется NavMethods) и запу-
стите его на выполнение. Попробуйте поэкспериментировать с набором данных — это по-
может вам четко выяснить все нюансы применения использованных в программе свойств
и методов.
Полностью текст программы приведен в листингах 2.1 и 2.2.
Листинг 2.1. Файл заголовка приложения NavMethods

#ifndef UnitlH
#defme UnitlH

#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <DB.hpp>
#include <DBCtrls.hpp>
#include <OBGrids.hpp>
#include <DBTables.hpp>
#include <ExtCtrls.hpp>
#include <Grids.hpp>

class TForml : public TForm

published: // IDE-managed Cdmponents


TTable *Tablel;
TDataSource *DataSourcel;
TDBGrid *DBGridl;
TButton *BFirst;
TButton *BPrior;
46 Borland C++ Builder 6. Разработка приложений баз данных

TButton *BNext;
TButton *BLast;
TPanel *Panell;
TPanel *Panel2;
TButton *BMoveBy;
TEdit *EPlan;
TEdit *EReally;
TPanel *Panel3;
TEdit *EBof;
TLabel *Labell;
TLabel *Label2;
TEdit *EEof;
void _ fastcall FormCreate(TObject *Sender) ;
void _ fastcall BFi rstClick(TObject *Sender) ;
void _ fastcall BMoveByClick(TObject *Sender);
void _ fastcall EPlanKeyPress(TObject *Sender, char &Key)
void _ fastcall TablelAf terScroll (TDataSet *DataSet);
private: // User declarations
public: // User declarations
_ fastcall TForml(TComponent* Owner);

extern PACKAGE TForml *Forml;


//-- ........ -
#endif

Листинг 2.2. Главный модуль приложения NavMethods

#include <vcl.h>
#pragma hdrstop
#include "Unitl.h"

#pragma package(smart_init)
#pragma resource "*.dfm"
TForml *Forml;
_fastcall TForml : :TForml(TComponent* Owner)
: TForm(Owner)

void _ fastcall TForml : :FormCreate(TObject *Sender)


{
Tablel->0pen() ;
BFi rst->Enabled=false;
BPrior->Enabled=false;

void _ fastcall TForml : :BFirstClick(TObject *Sender)


Глава 2. Использование механизма ВОЕ 47
TButton * Temp;

Temp=dynamic_cast<TButton *>(Sender);
if(Temp)

switch(Temp->Tag)
/
case 1:
Tablel->First(); break;
case 2:
Tablel->Prior(); break;
case 3:
Tablel->Next(); break;
case 4:
Tablel->Last(); break;

BFirst->Enabled=!Tablel->Bof ;
BP r i or ->Enabled=! Table l->Bof;
BNex t->Enabled=! Table! ->Eof;
Bias t->Enabled=! Table!- >Eof ;

void _ fastcall TForml: :BMoveByClick(TObject *Sender)


t
EReally->Text=IntToStr
(Tablel->MoveBy(StrToIntDef (EPlan->Text ,0)

void _ fastcall TForml : :EPlanKeyPress(TObject *Sender, char &Key)


{
if (Key == VK_RETURN) BMoveByClick(NULL) ;

void _ fastcall TForml: :TablelAf terScroll (TDataSet *DataSet)


if(DataSet->Bof) EBof->Text="true";
else EBof->Text="false"
if (DataSet->Eof) EEof->Text="true";
else EEof->Text="false"

Поля (Fields)
Одно из самых важных свойств набора данных — свойство Fields. Оно имеет тип
TFields и является контейнером, в котором содержится набор объектов TField. Каждый
из объектов TField представляет одно из полей набора данных. TField— очень полезный
и мощный объект, которым вы будете довольно часто пользоваться. Объекту TField будет
посвящен один из следующих разделов, здесь же мы кратко рассмотрим свойства и мето-
ды контейнера Fields.
48 Borland C++ Builder 6. Разработка приложений баз данных

Как и всякий контейнер, объект TFields не имеет событий, но имеет несколько свойст
и методов, в основном позволяющих управлять его содержимым. Ниже приводится опре
деление свойств объекта TFields:
property int Count = {read=GetCount, nodefault};
_property TDataSet* DataSet = {read=FDataSet};
property TField* Fieldsfint Index] =
{read=GetField, write=SetField};
Первые два свойства предназначены только для чтения. При помощи свойства DataSe
можно получить доступ к набору данных, содержащему этот контейнер Fields. Однакс
вряд ли вы будете когда-нибудь пользоваться этим свойством. Свойство Count содержи:
количество объектов TField, имеющихся в контейнере. Чаще всего это свойство исполь
зуется для организации итерации по всем полям набора данных. Свойство Fields предна-
значено для индексированного доступа к отдельным полям и равносильно применении:
оператора [].
Замечание
Контейнер TFields содержит список только неагрегатных полей. Агрегатные поля,
поддерживаемые некоторыми типами наборов данных (например, TCIientDataSet),
в отличие от обычных полей содержат итоговую информацию по группе записей. Для
доступа к агрегатным полям служит свойство набора данных AggFields.

Наиболее важные методы контейнера TFields — методы, предназначенные для досту-


па к объектам полей (TField). Вот их определение:
TField* operator [] (int Index) { return Fields[Index] ; }
TField* fastcall FieldByNumber(int FieldNo);
TField* fastcall FieldByName(const AnsiString FieldName);
Оператор [] служит для доступа к полям по их индексу с использованием синтакси-
са массива. Индексы присваиваются полям по мере их объявления в наборе данных (не
в таблице БД!). По умолчанию в наборе данных используются все поля из источника
данных, причем располагаются они в той же последовательности. Например, в таблице
Items индекс 0 имеет поле ItemNo, индекс 1 — поле Item, а полю Price соответствует ин-
декс 5. Свойство Count имеет значение 6. Все те же поля будут использоваться и в наборе
данных с теми же значениями индекса: полю ItemNo будет соответствовать индекс 0, полю
Item — индекс \ и т.д.
Состав и порядок следования полей в наборе данных по сравнению с таблицей
можно изменить. Для этого предназначен редактор полей (Fields Editor), который ото-
бражается на экране после двойного щелчка мышкой на объекте типа ТТаЫе. О работе
с редактором полей будет рассказано чуть позже, пока же ограничусь тем, что с его
помощью можно настроить набор полей для компонента ТТаЫе. Например, вы хотите,
чтобы поле Description было последним по счету в наборе данных, а не четвертым, как
в таблице. Чтобы добиться этого, необходимо дважды щелкнуть мышкой на компоненте
ТТаЫе, затем правой кнопкой мышки щелкнуть в рабочей области окна редактора полей
и выбрать из контекстного меню пункт Add fields. В следующем диалоговом окне вы-
берите все поля, кроме поля Description (чтобы выделять их вразброс, используйте кла-
вишу Ctrl) и нажмите кнопку ОК. Проделайте все то же снова, но на этот раз выберите
поле Description. В результате в набор данных будут добавлены определения всех полей
Глава 2. Использование механизма ВОЕ 49

таблицы, но в нужном порядке.


Вышеописанные действия нужны для того, чтобы пояснить разницу между операто-
ром /7 и методом FieldByNumber. Оба метода возвращают указатель на поле, принимая
в качестве аргумента его индекс. Однако метод FieldByNumber указывает на поле табли-
цы, являющейся источником данных для набора данных, в то время как оператор [] ука-
зывает на поле набора данных.
Для иллюстрации, как всегда, разработаем небольшое приложение. Создайте но-
вое приложение, и на пустую форму поместите метку (компонент TLabel), компонент
TListView, а также компонент ТТаЫе. Оставьте наименования всех объектов по умолча-
нию. Объект ТТаЫе подключите к таблице Items базы данных MyData. Далее, при помо-
щи редактора полей создайте определения всех полей таблицы, как это было описано вы-
ше (поле Description добавьте последним). Затем следует настроить компонент TListView.
Дважды щелкнув мышкой на самом компоненте, отобразите на экране редактор столбцов
и введите определение трех заголовков: Index, Fieldsflndex] и FieldByNumber [Index]. Чтобы
указанные столбцы отображались в компоненте, установите для свойства ViewStyle значение
vsReport. В тело обработчика события OnCreate формы введите следующий код:
void _ fastcall TForml: :FormCreate(TObject *Sender)
{
Tablel->0pen();
Labell->Caption=
"Таблица Items содержит " +
IntToStr(Tablel->Fields->Count)
+ " полей : " ;

TListltem *Temp;

for(int i=0;i<Tablel->Fields->Count;i++)
Temp=ListViewl->Items->Add() ;
Temp->Caption=i ;
Temp- > Subl terns ->Add (Table l->Fi eld s->Fi elds [i] ->FieldName) ;
Temp- >SubI terns ->Add (Table l->Fi elds ->FieldByNumber (i+1) ->FieldName) ;

Откомпилируйте приложение и запустите его на выполнение. Обратите внимание на


разницу в результатах работы оператора [] и FieldByNumber (см. рис. 2.9).
Замечание
Свойство Count класса TF/e/ds возвращает количество полей в наборе данных, а не
в нижележащей таблице. Если, например, в набор данных добавить пять полей из
таблицы Items, то свойство Tab/e7->F/e/ds->Count возвратит значение 5, в то время как
в самой таблице останется шесть полей. То же значение вернет и свойство F/eldCount
компонента ТТаЫе (точнее, объекта TDataSet).

В большинстве случаев, когда необходимо получить доступ к определенному полю,


удобней пользоваться методом FieldByName. Этот метод возвращает указатель на объект
TField, в качестве аргумента принимая имя поля. Это позволяет не задумываться о том,
какой индекс в наборе данных имеет поле — вы просто указываете его имя и получаете
50 Borland C++ Builder 6. Разработка приложений баз данных

доступ к его свойствам. Ниже приведено несколько примеров использования метод


FieldByName,
Tablel->FieldByName("Item") ' //Указатель на поле Item
Tablel->FieldByName("Description") //Указатель на поле Description

Таблица Hems cc

l( rr 4 0
Р ::
Item temNo ?. '' '
Iten
2 ' Category : Category
* Jridex .. _ .1 Description
4 . Price i Index
5 : Qeacriptipn : Price

, _

Рис. 2.9. Метод FieldByNumber обращается к полям в том порядке,


в котором они следуют в таблице, а не в наборе данных

Фильтрация
Чтобы не отображать для пользователя всю таблицу, что может быть сопряжено
с большими неудобствами и значительными затратами ресурсов, следует прибегнуть
к фильтрации. BDE предоставляет несколько ее вариантов. В первом варианте для филь-
трации должны использоваться только индексированные поля. Второй вариант, более
общий, позволяет использовать любые поля, в том числе неиндексированные.
Для фильтрации набора данных по индексированным полям применяются следующие
методы:
void fastcall ApplyRange(void);
void fastcall CancelRange(void);
void fastcall EditRangeEnd(void);
void fastcall EditRangeStart(void);
void fastcall SetRange(const System::TVarRec * StartValues,
const int StartValues_Size, const System::TVarRec * EndValues,
const int EndValues_Size);
void fastcall SetRangeEnd(void);
void fastcall SetRangeStart(void);
Следует заметить, что все эти методы объявлены в классе ТТаЫе, а не TDataSet.
Последовательность действий, необходимых для того чтобы установить для набора
данных (таблицы) фильтр с использованием методов этой группы, такова.
Q Вначале нужно задать для компонента ТТаЫе индекс, который будет использоваться
для фильтрации. Сделать это можно при помощи свойств — или IndexFieldNames, или
IndexName. Значением свойства IndexFieldNames является строка AnsiString, в кото-
рой перечислены через точку с запятой все поля, составляющие индекс. В свойстве
IndexName, как и следует из его названия, указывается имя индекса, заданное при
проектировании таблицы. Индекс, по которому будет проводиться фильтрация, можно
задать как в режиме конструктора, так и при выполнении программы.
Q На втором шаге необходимо задать начальное и конечное значение индекса, в соот-
ветствии с которыми будут отбираться данные. Для этого предназначены методы
Глава 2. Использование механизма ВОЕ 51

SetRangeStart и SetRangeEnd. Изменить граничные значения индекса можно при по-


мощи методов EditRangeStart и EditRangeEnd.
Q Чтобы применить фильтр, следует вызвать метод ApplyRange. Метод CancelRange слу-
жит для отмены фильтра.
Ниже приведен код, при помощи которого для таблицы Items устанавливается фильтр
по полю Category. Это поле — внешний ключ, поэтому Paradox автоматически создает
для него индекс. В итоговый набор попадут только те записи из таблицы Items, значение
поля Category для которых заключено в пределах от 3 до 5.
Table1 ->DatabaseName="MyData";
Tablel->TableName="Items.db";
Tablel->IndexFieldNames="Category";
Tablel->0pen();
Tablel->SetRangeStart();
Tablel->FieldByName("Category")->AsInteger=3;
Tablel->SetRangeEnd();
Table!->FieldByName("Category")->AsInteger=5;
Tablel->ApplyRange();
Вместо пары методов SetRangeStart и SetRangeEnd и метода ApplyRange можно ис-
пользовать метод SetRange, совмещающий их функции. Метод SetRange- получает набор
начальных и конечных значений индекса (если индекс составной) и на основе этой инфор-
мации применяет фильтр. Вот как можно переписать приведенный выше код:
Tablel->DatabaseName="MyData";
Tablel->TableName="Items.db";
Tablel->IndexFieldNames="Category";
Tablel->0pen() ;
Tablel->SetRange(OPENARRAY(TVarRec,(3)), OPENARRAY(TVarRec,(5)));
Замечание
Макро OPENARRAY предназначен для того, чтобы собрать несколько значений в так
называемый открытый массив, в конце которого указывается индекс последнего элемента.
Таким образом, выражение OPENARRAY(TVarRec,(3)) задает первых два аргумента
метода SetRange, а выражение OPENARRAY(TVarRec,(5)) - два последних. Конструкция
ОР£Л/АЯКАУспециально разработана для эмуляции массивов Open/Array из Object Pascal,
предназначенных для передачи в функцию переменного числа параметров. Несмотря
на то, что в C++ есть свои средства для передачи переменного числа параметров, VCL,
написанная именно на Object Pascal, требует использования открытых массивов. Более
подробно о макро OPENARRAY и похожем на него макро ARRAYOFCONST можно узнать
в справке Borland C++ Builder.

Для иллюстрации совместного использования методов навигации и фильтрования раз-


работаем небольшое приложение. Как всегда, создайте новое приложение, и на пустую
форму поместите компоненты TDBGrid, ТТаЫе и TDataSource, связав их между собой
и с таблицей Items так, как это описывалось ранее. Оставьте для этих элементов имена,
принятые по умолчанию. В верхней части формы расположите две панели (TPanel). На
одну из панелей поместите четыре кнопки (TButton). Последовательно задайте для этих
кнопок имена: BPriorCat, BNextCat, BPriorRec, BNextRec, и подписи: Prior Category, Next
Category, Prior Record, Next Record. На вторую панель поместите поле ввода (TEdii), метку
52 Borland C++ Builder 6. Разработка приложений баз данных

(TLabel) и две кнопки. Для кнопок задайте имена BApply, BCancel и подписи Apply Rang:
и Cancel Range соответственно. Поле ввода назовите ECat и очистите его свойство Text
для метки задайте подпись Category. Разместите все элементы управления примерно так
как на рис. 2.10.

tefrtNe|ft*m --. : '-I .,'... jCetago-ylDescrioion *]


t 1 MustekScariExpress1200UBPk
2 Mustek ScenExpress 1200 USB F 2 CCD. планшетный. А.4. 36 бит. f \.Т;Г"
• 3 Mustek ScnnExpress A3 EP 2 OS. планшетный. A3, 36 бит, 'Л — i.
A HP ScanJet 2200C 2 • CCD. планшетный. А4 True Col> : !:i
5 HP ScanJet Э4СОС 2 СШпленшетныЯА<ТгиеСоК;;:|
6 HP ScanJet 44COC 2 CCD, планшетный. А4, True Col' . ;,
7 HPScartJeH47QC 2 CCO, планшетный, А< True Col.; ^il
8 HP ScanJet 540QC 2 : CCO, планшетный. At True Coir .: ;.

iLJ

Рис. 2.10. Главная форма приложения ApplyRangeDemo


В дальнейшем нам понадобится переменная для хранения текущего значения катего-
рии. Откройте модуль формы, и ниже объявления переменной Forml, перед первой функ-
цией модуля, укажите следующее объявление:
long int C a t ;
Объявленная таким образом переменная Cat будет доступна из любой функции
модуля формы. Далее создайте обработчик события OnCreate формы и введите в него
следующий текст:
void _ fastcall TForml: :FormCreate(TObject *Sender)
{
Tablel->IndexFieldNames="Category" ;
Tablel->0pen() ;
Cat=l;
SetFilterCat(l) ;
BPriorCat->Enabled=false;
BPriorRec->Enabled=false;

В первых строках обработчика события FormCreate задается имя индексированного


поля, по которому предполагается установить фильтр (Category), и открывается набор
данных, связанный с таблицей Items. Далее переменной Cat присваивается начальное
значение, и по этому значению применяется фильтр (функция SetFilterCai). Так как сразу
после открытия активной становится первая запись набора, вполне логично запретить
доступ к кнопкам BPriorCat (переход к предыдущей категории) и BPriorRec (переход
к предыдущей записи). Для этого предназначены последние две строчки обработчика.
Функция SetFilterCat предназначена для установки фильтра по указанному значению
категории. Переключитесь на файл заголовка модуля формы (Unitl.h, если вы не пере-
именовывали модуль формы), и укажите следующее объявление в разделе private (можно
и в разделе public, но это будет дурным тоном, так как вы предоставляете стороннему
пользователю объекта доступ к его внутренней реализации):
void _ fastcall SetFilterCat (long int Cat);
Глава 2. Использование механизма ВОЕ 53

В самом модуле Unit 1.с введите следующее определение:


void fastcall TForml::SetFilterCat(long int Cat)
{
if(Cat == 0) return;
Tablel->SetRangeStart();
Tablel->FieldByName("Category")->AsInteger=Cat;
Tablel->SetRangeEnd();
Tablel->FieldByName("Category")->AsInteger=Cat;
Tablel->ApplyRange();
ECat->Text=IntToStr(Cat);
BApply->Enabled=false;
BCancel->Enabled=true;
}
В первой строке функции SetFilterCat проверяется, установлен ли фильтр. Если
переменная Cat равна 0, то фильтр не установлен. В этом случае ничего делать не
нужно. В последних трех строках функции SetFilterCat в поле ввода ECat помеща-
ется значение текущей категории и соответствующим образом настраивается доступ
к кнопкам Apply Range и Cancel Range. В остальных строках устанавливается фильтр,
благодаря которому в сетке TDBGrid выводятся записи, соответствующие только
указанной категории. Свойство Aslnteger класса TField преобразует значение указан-
ного поля к типу Integer. Кроме свойства Aslnteger класс TField имеет еще несколько
свойств, связанных с преобразованием значения поля к необходимому типу. Обо всем
этом будет рассказано чуть позже.
Замечание
В классе ТТаЫе объявлено свойство KeyExclusive типа boo/; в некоторых ситуациях
оно может быть очень полезно. С его помощью можно исключить из устанавливаемого
диапазона значение индекса, используемого в качестве начала или конца диапазона при
фильтрации. Например, в результате выполнения следующего отрывка кода в итоговый
набор данных попадут только записи, соответствующие категориям 4 и 5.
Tablel->SetRangeStart();
Tablel->FieldByName("Category")->AsInteger=3;
Tablel->KeyExclusive=false;
Tablel->SetRangeEnd();
Tablel->FieldByName("Category")->AsInteger=5;
Tablel->ApplyRange();

Для каждой из четырех кнопок, расположенных на левой панели, создайте обработ-


чики событий OnClick. Для кнопки BPriorCat в обработчике события OnClick укажите
вызов метода Tablel->First(), для кнопки BNextCat — метода Tablel->Last(), а для кнопок
BPriorRec и BNextRec — соответственно вызовы методов Tablel->Prior() и Tablel->Next().
Хитрость здесь в том, что при достижении начала или конца набора данных (т. е. при до-
стижении свойствами Во/или Eof значения true) изменяется текущее значение категории,
и по этому значению устанавливается фильтр. Остальные части обработчиков событий
OnClick кнопок навигации (BPriorCat, BNextCat, BPriorRec и BNextRec) связаны с отсл-
еживанием начала и конца отфильтрованного или неотфильтрованного набора данных
54 Borland C++ Builder 6. Разработка приложений баз данных

и с заданием соответствующих значений для свойства Enabled каждой из этих кнопок


Если фильтр снят с набора данных, то все кнопки ведут себя прогнозированным образом
перемещая к первой, последней, предыдущей или последующей записи набора.
Обработчики событий OnClick оставшихся двух кнопок, отменяют или вновь уста
навливают фильтр. Значение категории, в соответствии с которым будет устанавливать^
фильтр, можно задать в поле ввода ECat. Откомпилируйте приложение и запустите его нг
выполнение. Попробуйте перемещаться по набору данных при помощи кнопок навига-
ции. Обратите внимание на то, как будет переключаться набор данных с одной категориь
на другую.
Конечно, предлагаемое вашему вниманию приложение — не образец устойчивости
и надежности, но оно вполне приемлемо иллюстрирует основные принципы навигации
и фильтрации. Полностью код приложения приведен в листингах 2.3 и 2.4.
Листинг 2.3. Файл заголовка приложения ApplyRangeDemo
// -
#ifndef UnitlH
#define UnitlH
//
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <DB.hpp>
#include <DBGrids.hpp>
#include <DBTables.hpp>
^include <Gn'ds.hpp>
#include <ExtCtrls.hpp>
//
class TForml : public TForm
{
published: // IDE-managed Components
TDBGrid *DBGridl;
TTable Tablel;
TDataSource *DataSourcel;
TPanel *Panell;
TButton *BPriorCat;
TButton *BPriorRec;
TButton *BNextCat;
TButton *BNextRec;
TPanel *Panel2;
TEdit *ECat;
TLabel *Labell;
TButton *BApply;
TButton *BCan.cel;
void fastcall FormCreate(TObject «Sender);
void fastcall BPriorCatClick(TObject *Sender);
void fastcall BNextCatClick(TObject *Sender);
void fastcall BNextRecClick(TObject *Sender);
void fastcall BPriorRecClick(TObject *Sender) ;
void fastcall BCancelClick(TObject *Sender);
void fastcall BApplyClick(TObject *Sender);
Глава 2. Использование механизма ВОЕ 55

private: // User declarations


void _ fastcall SetFiUerCat(long int Cat);
public: // User declarations
_fastcall TForml(TComponent* Owner);

extern PACKAGE TForml *Forml;


//
#endif
Листинг 2.4. Главный модуль приложения ApplyRangeDemo

#include <vcl.h>
#pragma hdrstop
#include "Unitl.h"
#pragma package(smart_ini t)
#pragma resource "*.dfm"
TForml *Forml;
long int Cat;
_fastcall TForml: :TForml(TComponent* Owner)
: TForm(Owner)

void _ fastcall TForml: :FormCreate(TObject *Sender)


{
Tablel->IndexFieldNames="Category" ;
Tablel->0pen() ;
Cat=l;
SetFilterCat(l);
BPriorCat->Enabled=f alse;
BPriorRec->Enabled=false;

void _ fastcall TForml: :BPriorCatClick(TObject *Sender)


{
Tablel->First() ;
if (Cat != 0)
{
if(Cat > 1) Cat--;
SetFilterCat(Cat);
ECat->Text=IntToStr(Cat) ;
Tablel->Last() ;
}
if(Cat < 2) BPriorCat->Enabled=false;
if (BNextCat->Enabled == false) BNextCat->Enabled=true;
if (BNextRec->Enabled == false) BNextRec->Enabled=true;
56 Borland C++ Builder 6. Разработка приложений баз данных

void _ fastcall TForml : : BNextCatClick(TObject *Sender)


{
Tablel->Last() ;
if(Cat != 0)
{
if (Cat 12) Cat++;
SetFilterCat(Cat) ;
ECat->Text=IntToStr(Cat) ;
}
if(Cat == 12) BNextCat->Enabled=false;
if (BPriorCat->Enabled == false) BPriorCat->Enabled=true;
if (BPriorRec->Enabled == false) BPriorRec->Enabled=true;

void _ fastcall TForml : :BNextRecClick(TObject 'Sender)


{
Tablel->Next();
if (Tablel->Eof)
{
if ((Cat == 12) | | (Cat == 0))
{
BNextRec->Enabled=false;
BNextCat->Enabled=false;
}
if(Cat != 0)
{
if(Cat 12) Cat++;
SetFilterCat(Cat) ;
ECat->Tex.t = IntToStr(Cat) ;

if (BPn'orRec->Enabled == false) BPriorRec->Enabled=true;


if((Cat == 2) && (BPriorCat->Enabled == false))
BPriorCat->Enabled=true;
if((Cat == 12) && (BNextCat->Enabled == true))
BNextCat->Enabled=false;

void _ fastcall TForml : :BPriorRecClick(TObject *Sender)


{
Tablel->Prior() ;
if(Tablel->Bof)
{
if(Cat < 2) BPriorRec->Enabled=false;
else
{
if (Cat > 1) Cat--;
SetFilterCat(Cat) ;
ECat->Text=IntToStr(Cat) ;
Tablel->Last() ;
Глава 2. Использование механизма ВОЕ 57

if(BNextRec->Enabled == false) BNextRec->Enabled=true;


if((Cat == 11) && (BNextCat->Enabled == false))
BNextCat->Enabled=true;
if((Cat == 1) && (BPriorCat->Enabled == true^)
BPriorCat->Enabled=false;

void fastcall TForml::5etFilterCat(long int Cat)


if(Cat == 0) return;
Tablel->SetRangeStart();
Tablel->FieldByName("Category")->AsInteger=Cat;
Tablel->SetRangeEnd();
Tablel->FieldByName("Category")->AsInteger=Cat;
Tablel->ApplyRange();
ECat->Text=IntToStr(Cat);
BApply->Enabled=false;
BCancel->Enabled=true;

void fastcall TForml::BCancelClick(TObject *5ender)


Cat=0;
Tablel->CancelRange();
BApply->Enabled=true;
BCancel->Enabled=false;
ECat->Text="All";
BPriorCat->Caption="Fi rst Record";
BNextCat->Caption="Last Record";

void fastcall TForml::BApplyClick(TObject «Sender)


Cat=StrToIntDef(ECat->Text,0);
if ((Cat < 1) || (Cat > 12)) Cat=l;
SetFilterCat(Cat);
BPriorCat->Caption="Prior Category";
BNextCat->Caption="Next Category";

Еще один способ фильтрации набора данных — использование свойств Filter, Filtered
и FilterOptions. Все они объявлены в классе TBDEDataSet, который является промежуточ-
ным между классом TDataSet и ТТаЫе. Способ фильтрации с применением свойств Filter
и Filtered — наиболее общий, гибкий и, что немаловажно, очень простой. Этот способ не
требует применения индексов. Вы можете задавать фильтр с использованием любого поля
или произвольного набора полей набора данных.
Вначале необходимо задать в свойстве Filter строку условия, которому должны удо-
влетворять записи набора данных, чтобы попасть в итоговую выборку. Простейший вари-
58 Borland C++ Builder 6. Разработка приложений баз данных

ант такого условия имеет вид:


ИмяПоля=значение.
Например, условие Category=3, заданное в свойстве Filter, приведет к тому, что
в итоговую выборку попадут только записи, соответствующие категории 3. Значение мо-
жет быть или константой, или переменной, в частности именем поля, или допустимым
выражением. Константа должна указываться в соответствии с принятыми правилами, на-
пример, строку нужно заключать в одинарные кавычки. Если имя поля состоит более чем
из одного слова, оно должно заключаться в квадратные скобки: [Имя поля].
Кроме знака равенства можно использовать знак любой операции отношения: неравно
(<>), больше (>), меньше (<), и т.д.
Чтобы указанный фильтр вступил в действие, необходимо для свойства Filtered задать
значение true. Если набор данных открыт, то вы получите отфильтрованную выборку
прямо в режиме конструктора. Чтобы отключить фильтр, задайте для свойства Filtered
значение false.
На окончательный вид отфильтрованного набора данных влияет также свойство
FilterOptions, которое является множеством. Множество FilterOptions может содержать про-
извольный набор из двух элементов типа FilterOption: foCaselnsensitive KfoNoPartialCompa-
ге, или быть пустым. Оба элемента используются при сравнении значений текстовых полей
с указанным образцом. Если свойство FilterOptions содержит элемент foCaselnsensitive, то
при сравнении не будет учитываться регистр символов строки. Присутствие во множестве
FilterOptions элементаfoNoPartialCompare отменяет возможность сравнивать образец с часть-
ю значения поля. Если элемент foNoPartialCompare не задан, можно указывать символ звез-
дочки в качестве замены любого количества символов. В этом случае, например, условие вида
Item- 'S*' приведет к отбору всех записей, значения поля Item которых начинаются с символа
S. В режиме конструктора любую из опций FilterOption можно активизировать, задав для нее
значение true в инспекторе объектов.
Убедиться в простоте и гибкости метода фильтрации с помощью свойств Filter и Filtered мож-
но на примере. Создайте новое приложение, добавьте на пустую форму, как и в предыдущем при-
мере, сетку (TDBGricf), компоненты ТТаЫе и TDataSource, и объедините их в цепочку, подключив
к таблице Items. В верхнюю часть формы добавьте элементы управления, как изображено на рис.
2.11, и подровняйте их соответствующим образом. Для элемента редактирования (компонента
TEdit) задайте имя ECriteria, а для флажков (TCheckBox) задайте имена CBCaselnsensitive и CBNo-
PartialCompare соответственно. Для кнопок (TButton) задайте имена в соответствии с их подпися-
ми: В Apply и BCancel.

^rtteria Stfjpg: • & Сове ыет^е " r NO Р*


йепт'ер** CwcsiRterj

ttemNo |йвт jcafcij.!^ |''s *•]


i 3 Epson EPL-5900L A^ IJJ4,
6 Epson AcuUasef C1000 A4;>."'l
7 EpsonAcuLaserCZDOO •A4.-.',;:;^
3 Epson Stylus ColorCSDSX A4 . ,§
4 Epson Stylus ColorCMQUX A-f ,.|
:

5 Epson Stylus Color C60 A1 v.:^


6 Epson Stylus Color C70 :
A4 "- Й
7 Epson Stylus ColorCfiO A4..;.|
| В Epson Stylus Photo B1Q 'A4 •"'-&•

JLJJ

Рис. 2.11. Результат применения фильтра по полю Item


^
с использованием свойств Filter и Filtered
Глава 2. Использование механизма ВОЕ 59

В обработчики событий формы OnCreate и OnClose введите следующий текст:


void fastcall TForml::FormCreate(TObject *5ender)
{
Tablel->0pen();
}
void fastcall TForml::FormClose(TObject *Sender,
TCloseAction &Action)
{
Tablel->Close();
}
Смысл кода, выполняемого этими методами, предельно прост: при создании формы
набор данных Tablel открывается, а при закрытии — закрывается. Остается ввести код
в обработчики событий OnClick кнопок BApply и BCancei.
void fastcall TForml::BApplyClick(TObject *Sender)
{
Tablel->Filter=ECriteria->Text;
if(CBCaseInsensitive->Checked)
Table1->FiIterOptions foCaselnsensitive;
else Tablel->FilterOptions » foCaselnsensitive;
if(CBNoPartialCompare->Checked)
Table1->FiIterOptions foNoPartialCompare;
else Tablel->FilterOptions » foNoPartialCompare;
Tablel->Filtered=true;
}
void fastcall TForml::BCancelClick(TObject *Sender)
{
Tablel->Filtered=false;
}
При нажатии кнопки BCancei свойству Filtered компонента ТТаЫе задается значе-
ние false', тем самым отключается примененный ранее фильтр. Обработчик события
нажатия кнопки BApply выполняет противоположную задачу — устанавливает фильтр
в соответствии с условием, заданным в элементе редактирования ECriteria, Текст,
введенный в поле ECriteria, присваивается свойству Filter, а затем, в зависимости от
состояния флажков CBCasefnsensitive и CBNoPartialCompare, настраивается свойство
FilterOption. Чтобы непосредственно применить фильтр, свойству Filtered задается
значение true.
Вот и все! Вы получили приложение, в рамках которого можете применить к набору
данных самый изощренный фильтр. Компилируйте приложение и поэкспериментируйте
с ним. На рис. 2.11 изображена таблица Items, отфильтрованная по полю Item. Обратите
внимание на то, Что флажок Case Insensitive установлен, а флажок No Partial Compare
снят. Это позволит не учитывать регистр символов при сравнении с образцом и приме-
нять символ звездочки в качестве замены любого количества символов. С учетом изо-
браженного на рис. 2.11 в финальный набор данных войдут все записи, значения в поле
Item которых начинаются с символов sam или Sam (или других комбинаций этих символов
в разных регистрах) и включают любое количество других символов (конечно, с учетом
длины поля).
60 Borland C++ Builder 6. Разработка приложений баз данных

Еще более изощренный фильтр можно применить при помощи обработчика собьт
OnFilterRecord компонента ТТаЫе. Соответствующее объявление в классе TDataSet вь
глядит следующим образом:
typedef void _ fastcall ( _ closure *TFilterRecordEvent)
(TDataSet* DataSet, bool &Accept) ;
Обработчик события (если задан) вызывается для каждой записи набора DataSet пр
открытии или обновлении набора данных, если для свойства Filtered задано значение tru>
Если нужно, чтобы конкретная запись не попала в итоговый набор, следует задать да
аргумента Accept -значение false. Ниже приведен код обработчика события OnFilterRecon
в результате действия которого в итоговый набор данных будут включаться записи о ТОВЕ
pax указанного ценового диапазона. Границы диапазона задаются в полях ввода Edit
и Edit2.
void _ fastcall TForml: :TablelFilterRecord(TDataSet *DataSet,
bool &Accept)
{
Accept=
(DataSet->FieldValues["Price"J >= StrToIntDef (Edi tl->Text ,0))
&&
(DataSet->FieldValues["Price"] <= StrToIntDef (Edit2->Text, 1000)) ;

Поиск
Как и в случае фильтрации, для поиска нужного значения в наборе данных существует
несколько вариантов. Один из них основан на применении методов SetKey (или EditKey
и GotoKey и используется для поиска только по индексированным полям. Вот как объяв-
лены эти методы в классе ТТаЫе:
void _ fastcall SetKey(void) ;
void _ fastcall Edi tKey (void) ;
bool __ fastcall GotoKey (void) ;
void _ fastcall GotoNearest(void) ;
Прежде чем пользоваться этими методами, следует указать, какой из индексов табли-
цы будет применяться для поиска. Для этого можно прибегнуть к услугам или свойства
IndexFieldNames (имя одного или нескольких полей, участвующих в индексе), или свой-
ства IndexName (имя индекса). Если этого не сделать, вы получите сообщение об ошибке
наподобие следующего: Field 'Имя_Поля ' is not indexed and cannot be modified («Поле
'Имя_Поля 'не проиндексировано и не может быть изменено»). Далее нужно вызвать ме-
тод SetKey. Это приведет к тому, что набор данных перейдет в состояние dsSetKey. В этом
состоянии присвоение полю какого-либо значения будет воспринято как задание образца
для поиска и не приведет к изменению значения этого поля. Естественно, образцы поис-
ка необходимо задать для всех полей, входящих в индекс и указанных ранее при помощи
свойства IndexFieldNames или свойства IndexName.
Метод GotoKey выполняет поиск в соответствии с произведенной настройкой. Если
найдено соответствие образца значению поля (полей) одной из записей, метод возвращает
значение true и переводит на нее указатель активной записи. В противном случае указа-
тель активной записи не меняется, а метод GotoKey возвращает значение^а/^е.
Метод EditKey предназначен для тех же целей, что и метод SetKey, однако с его помо-
Глава 2. Использование механизма ВОЕ 61

щью можно поменять образцы поиска не для всех полей индекса, а только для некоторых.
Например, вы используете для поиска составной индекс, основанный на полях Фамилия, Имя,
Отчество. Вначале вам нужно вызвать метод SetKey и установить для каждого поля образец
поиска, например, для поля Фамилия значение «Иванов», для поля Имя — значение «Иван»,
а для поля Отчество — значение «Иванович». Если метод GotoKey не нашел нужную запись,
можно вызвать метод EditKey и затем изменить образец поиска, например, для поля Отчество
задав значение «Петрович». Следующий вызов метода GotoKey инициирует поиск записи, со-
держащий в соответствующих полях индекса значения «Иванов», «Иван», «Петрович».
Ниже приведен отрывок кода, описывающий поиск по полю Item таблицы Items.
Tablel->IndexName="ItemNDX";
Tablel->SetKey();
Tablel->FieldByName("Item")->AsString=ESample->Text;
Tablel->GotoKey();
Tablel->IndexName="";
В первой строке этого кода задается имя индекса, который будет использоваться для
поиска. Если вы помните, при построении таблицы Items индексу на основе поля Item
было присвоено имя ItemNDX. Далее вызывается метод SetKey, переводящий набор
данных в режим поиска, и в качестве образца поиска по полю Item выбирается текст,
введенный пользователем в элемент редактирования ESample. Метод GotoKey в случае
удачи переводит указатель активной записи на первую запись, соответствующую образцу
поиска. В последней строке кода в качестве имени индекса указывается пустая строка.
Дело в том, что после того как вы укажете для свойства IndexName имя индекса ItemNDX,
набор данных будет отсортирован по полю Item в соответствии с установками, заданными
при создании индекса ItemNDX (в данном случае по возрастанию). Если индекс для на-
бора данных не задан, то сортировка производится с использованием первичного ключа,
а если его нет — то записи будут отображаться по мере того, как они вводились в таблицу.
В нашем примере в результате выполнения последней строки приведенного выше отрыв-
ка кода набор данных вернется к первоначальному виду (т. е. записи не будут перемеша-
ны). В реальном приложении, чтобы избежать ошибочных ситуаций, в начале этой части
кода следует запомнить имя индекса во временной переменной, а в последней строке от-
рывка — восстановить предыдущее значение свойства IndexName.
Если вам точно не известно значение индексного пол», которое вы разыскиваете, то
вместо метода GotoKey можно воспользоваться методом GotoNearest. Он перенесет ука-
затель активной записи на ту запись, значение выбранного индекса которой ближе всего
к образцу поиска. Чаще всего этот метод используется для поиска строки по ее заданной
части. Метод GotoNearest не возвращает никакого значения, однако если подходящая за-
пись не найдена, активной становится последняя запись набора.
Есть еще пара методов, аналогичных GotoKey и GotoNearest, но более простых в использо-
вании. Это методы FindKey и FindNearest. Вот как они объявлены в классе ТТаЫе:
bool fastcall FindKey(const System::TVarRec * KeyValues,
const int KeyValues_Size);
void fastcall FindNearest(const System::TVarRec * KeyValues,
const int KeyValues_Size);
Первый аргумент обоих методов представляет собой массив значений полей (образ-
цов для поиска), перечисленных через запятую. Второй аргумент — последний индекс
динамического массива. Как всегда в таких случаях, вместо этих двух аргументов мож-
62 Borland C++ Builder 6. Разработка приложений баз данных

но использовать макро OPENARRAY или ARRAYOFCONST. При использовании метод


FindKey или FindNearest не нужно предварительно вызывать метод SetKey и задавать д
полей, составляющих индекс, образцы поиска. Вот как, например, можно переписать о
разец кода, приведенный выше:
Tablel->IndexName="ItemNDX";
Tablel->FindKey(OPENARRAY(TVarRec,(ESample->Text)));
Tablel->IndexName="";
Более удобная и универсальная технология поиска в наборе данных связана с прим
нением метода Locate. Он объявлен в классе TBDEDataSet (это непосредственный насле,
ник класса TDataSet) следующим образом:
virtual bool fastcall Locate(const AnsiString KeyFields,
const System::Variant &KeyValues, Db::TLocateOptions Options);
Этот метод — наиболее общий способ поиска информации в наборах данных, явл?
ющихся наследниками класса TBDEDataSet (TTable, TQuery, TStoredProc). Поиск можн
производить по любому полю или набору полей, не обязательно составляющим индекс
Первый аргумент метода Locate — это строка AnsiString, содержащая наименования по
лей для поиска. Поля перечисляются через точку с запятой. Второй аргумент представляв
собой массив значений типа Variant. Этот массив должен содержать список образцов по
иска, соответствующий списку полей в первом аргументе. Для динамического построена
массива типа Variant можно использовать процедуру VarArrayOf. Третий аргумент — мно
жество типа TLocateOptiom. В его состав могут входить элементы loCaselnsensitive (прр
поиске не будет учитываться регистр значений поля) и loPartialKey (разрешается сравне-
ние образца поиска с частью значения поля). При поиске по нетекстовым полям аргумею
Options игнорируется.
Ниже приводится небольшой отрывок программы, иллюстрирующий способ примене-
ния метода Locate.
void fastcall TForml::BLocateClick(TObject *Sender)
{
TLocateOptions SearchOptions;
SearchOptions.ClearQ;
if(CBCaselnsensitive->Checked) SearchOptions loCaselnsensitive;
if(CBPartialKey->Checked) SearchOptions loPartialKey:
Tablel->Locate(EField->Text, ESample->Text, SearchOptions);
}
В этом отрывке кода флажки CBCaselnsensitive и CBPartialKey используются для на-
стройки параметров поиска. Имя поля для поиска задается пользователем в элементе ре-
дактирования EField, а образец поиска — в элементе редактирования ESample.
Lookup — еще один полезный метод, пригодный для поиска нужной информации, но
не связанный с перемещением указателя активной записи. Этот метод объявлен в классе
TBDEDataSet так:
virtual System::Variant fastcall Lookup(const AnsiString KeyFields,
const System::Variant SKeyValues, const AnsiString ResultFields);
Метод Lookup разыскивает запись, удовлетворяющую указанным критериям, и возвр-
ащает значение указанного поля. В качестве критерия поиска используются первые два
Глава 2. Использование механизма ВОЕ 63

аргумента. Их смысл и назначение аналогичны таковым первых двух аргументов метода


Locate (см. предыдущие несколько абзацев). Третий аргумент представляет собой стро-
ку AnsiString. Эта строка содержит наименование поля, значение которого необходимо
вернуть. Например, если нужно отобразить индекс какого-либо товара из таблицы Items,
заданного наименованием, можно использовать следующую конструкцию:
EField->Text=Tablel->Lookup("Item","HP LaserJet 1200","Index");
И в заключение о самой гибкой, удобной и простой технологии поиска — о посл-
едовательном поиске при помощи методов FindFirst, FindPrior, FindNext и FindLast.
Они напоминают методы навигации по набору данных, однако, в отличие от последних,
переход осуществляется к первой, предыдущей, следующей или последней записи, удо-
влетворяющей указанному критерию. Методы навигации в этом смысле являются мето-
дами безусловного перехода, в то время как методы поиска семейства Find... — методами
условного перехода. Методы FindFirst, FindPrior, FindNext и FindLast объявлены в классе
TDataSet следующим образом:
bool fastcall FindFirst(void);
bool fastcall FindPrior(void);
bool fastcall FindNext(void);
bool fastcall FindLast(void);
Каждый из этих методов возвращает значение true в случае успешного поиска
и false в противоположном случае. Критерий поиска задается в обработчике события
OnFilterRecord так же, как и при фильтрации. Однако в отличие от фильтрации свойство
Filtered набора данных должно иметь значение false.
Пользоваться этими методами достаточно просто и удобно. Кроме того, позаботив-
шись об обработчике события OnFilterRecord, можно создать удобную и привычную для
пользователя процедуру поиска нужного значения. В качестве примера использования
методов группы Find... совместно с событием OnFilterRecord, создадим небольшое при-
ложение. Как и в предыдущих примерах, на пустую главную форму приложения поме-
стите компоненты TDBGrid, TTable и TDataSource, оставив для них имена по умолчанию.
Соедините эти компоненты друг с другом и подключите всю эту цепочку к таблице Items
псевдонима MyData. В верхней части формы разместите два элемента редактирования
(имена: EField и ESample), две метки (подписи: Field и Sample) и четыре кнопки (имена:
BFindFirst, BFindPrior, BFindNext, FindLast; подписи, соответственно: Find First, Find
Prior, Find Next, Find Last; значения свойства Tag, соответственно: 1,2,3, 4). Разместите
все элементы управления примерно так, как изображено на рис. 2.12.
Создайте для кнопки BFindFirst обработчик события OnClick и свяжите с этим обра-
ботчиком события OnClick всех кнопок. В тело обработчика события введите следующий
код:
TButton* Temp=dynamic_cast<TButton *>(Sender);
if(Temp)
{
swi tch(Temp->Tag)
{
case 1: Tablel->FindF1rst(); break;
case 2: Tablel->FindPrior(); break;
case 3: Tablel->FindNext(); break;
64 Borland C++ Builder 6. Разработка приложений баз данных

case 4: Tablel->FindLast(); break;


};
DBGridl->SetFocus();
]
Обработчик события имеет стандартный вид обработчиков, ассоциированных с один
ковыми событиями сразу нескольких однородных элементов управления. Вначале арг
мент Sender приводится к типу TButton *, затем, при успешном преобразовании, провер
ется свойство Tag, и в зависимости от его значения выполняются те или иные действ!
(вызывается соответствующий метод последовательного поиска). В заключение фою
ввода переводится на компонент TDBGrid.
Критерий поиска задается в обработчике события OnFilterRecord:
Accept=(DataSet->Fields->
FieldByName(EField->Text)->As5tring.AnsiPos(ESample->Text) 0) ;
В этом обработчике события для каждой записи извлекается значение поля, имя коте
рого задано в поле ввода EField. Это значение при помощи свойства AsString преобразуете
к строковому типу AnsiString. Затем при помощи метода AnsiPos класса AnsiString определи
ется позиция, начиная с которой текст, заданный в поле ввода ESample, входит в полученну!
строку. Если образец входит в эту строку, метод AnsiPos возвращает значение, большее (
Тогда аргумент Accept получает значение true, и методы семейства Find... останавливаютс
на этой записи, также возвращая значение true. Методы FindPrior и FindNext начинают nonet
отталкиваясь от текущей позиции в наборе данных, а не от первой или последней.

Hum
^5г :JFC-PGA • -
*SfjlS
•V.FiBdtosr;;::.! ' FBiti^fe<l :| i
:
tewnNo . jttem •: •-•*.- .- - ---:--' • "" ' ."-. "• jCetegor*}
63 Socket A GIGA-BYTE GA-7ZXE
• 63 Socket A GIGA-BYTE GA-7ZX Rev. 5X
r| - •••
6< Socket A ChaJnTBchCT-7AJA2 —'
65 Socket A GIGA-BYTE GA-7ZMMM
66 Socket A ASUS A7V133-C
HJfl Socket 370 FC PGA/PPGA Canvon 6LEBMS
68 Socket 370 FC-PGA/PPGA Canyon SLEBMS-T
• 69 Socket 370 FC-PGA/PPGA Menli ЭС899
7C Socket 370 FC-PGA/PPOA PC ParlnerT205C
71 Socket 37C FC-PGA/PPOA FoslFame 3SLAP
> 72 Socket 370 FC-PGA/'PPGA PC Partner C203T . :-?-<

«LJ , ... ;i :,.; .,.;;:.;j;,^


Рис. 2.12. Простое приложение, иллюстрирующее последовательный поиск в наборе
данных при помощи методов FindFirst, FindPrior, FindNext и FindLast
Сохраните приложение где-нибудь, например, в каталоге FindFirstDemo, а затем от-
компилируйте его и запустите на выполнение. На рис. 2.12 изображены результаты поиска
по полю Item образца 'FC-PGA'. Нажимая кнопки BFindPrior и BFindNext, размещенные
на форме, вы можете последовательно перемещаться по всем записям набора данных, удо-
влетворяющим критериям поиска. При помощи кнопок BFindFirst и BFindLast вы сможе-
те перейти, соответственно, к первой и последней такой записи. Полный текст профаммы
FindFirstDemo представлен в листингах 2.5 и 2.6.
Замечание
Конечно, описываемая процедура последовательного поиска нуждается
в существенной доработке, если вы хотите использовать ее в реальном приложении.
Глава 2. Использование механизма ВОЕ 65

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


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

Листинг 2.5. Файл заголовка приложения FindFirstDemo


// ------ ............................... - ............................
#ifndef UnitlH
#defme UnitlH
// ------ ......................... - ..... -------- ............. --------
#include <Classes . hpp>
#include <Controls . hpp>
#include <5tdCtrls.hpp>
#include <Forms.hpp>
#include <DB.hpp>
#include <DBGrids.hpp>
#include <DBTables.hpp>
#include <Grids.hpp>
// ..... - ..... - ............ - ............ ---- ............... ---- ......
class TForml : public TForm
{
_ published: // IDE-managed Components
TDBGrid *DBGridl;
liable "Tablel;
TDataSource *DataSourcel;
TEdit *EField;
TLabel *Labell;
TEdit *ESample;
TLabel *Label2;
TButton *BFindFirst;
TButton *BFindLast;
TButton *BFindPrior;
TButton *BFindNext;
void _ fastcall BFindFi rstClick(TObject *Sender);
void _ fastcall TablelFilterRecord(TDataSet *DataSet,
bool &Accept) ;
void _ fastcall FormCreate(TObject *Sender) ;
void _ fastcall FormClose(TObject *5ender, TCloseAction &Action);
private: // User declarations
public: // User declarations
_fastcall TForml(TComponent* Owner);

extern PACKAGE TForml *Forml;


/ / ----- .................. -
#endif

Листинг 2.6. Главный модуль приложения FindFirstDemo

#include <vcl.h>
#pragma hdrstop
3 Зап. 319
66 Borland C++ Builder 6. Разработка приложений баз данных

#include "Unitl.h"

#pragma package(smart_ini t)
#pragma resource "*.dfm"
TForml *Forml;
_fastcall TForml : : TForml (TComponent* Owner)
: TForm(Owner)

void _ fastcall TForml: :BFindFi rstClick(TObject *Sender)


{
TButton* Temp=dynamic_cast<TButton *>(5ender);
if (Temp)
{
swi tch(Temp->Tag)
{
case 1: Tablel->FindFi rst() ; break;
case 2: Tablel->FindPrior() ; break;
case 3: Tablel->FindNext() ; break;
case 4: Tablel->FindLast() ; break;
};
DBGridl->SetFocus() ;

void _ fastcall TForml : :TablelFilterRecord(TDataSet *DataSet,


bool &Accept)
{
Accept=
(DataSet->Fields->FieldByName(EField->Text)->
AsString.AnsiPos(ESample->Text) > 0);

void _ fastcall TForml: :FormCreate(TObject *Sender)


{
Tablel->0pen() ;

void _fastcall TForml: : FormClose(TObject *Sender, TCloseAction


&Action)
{
Tablel->Close();

Сортировка набора данных


Возможности сортировки набора данных типа ТТаЫе невелики. Такой набор сорти-
руется в соответствии с индексом, активным в данный момент. Активный индекс указы-
вается при помощи свойства IndexName или IndexFieldNames. В зависимости от свойств
Глава 2. Использование механизма BDE 67

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


в состав индекса, по возрастанию или убыванию значений, начиная с первого поля, вхо-
дящего в индекс. Если индекс для набора данных не задан, набор сортируется с использо-
ванием первичного ключа, конечно, если он задан. Если же не задан и он (и это позволяет
используемая СУБД), записи сортируются по мере их ввода в таблицу.
Это все, чего можно добиться от компонента ТТаЫе. Большие возможности сортиров-
ки — у компонента TQuery.

Изменение данных
Еще один очень важный момент, связанный с компонентом ТТаЫе,- изменение данных та-
блицы, к которой этот компонент подключен. В режиме конструктора, щелкнув правой клави-
шей мышки на значке компонента ТТаЫе, вы можете выбрать из контекстного меню пункты,
при помощи которых можно удалить или переименовать связанную таблицу. Тех же резуль-
татов в процессе выполнения программы можно добиться при помощи методов DeleteTable
нКепапгеТаЫе. Изменять данные в таблице при выполнении программы можно благодаря
методам, объявленным в классе TDataSet и его наследниках. К основным методам изменения
записей таблицы, объявленным в классе TDataSet, можно отнести следующие:
void fastcall Append(void);
void fastcall AppendRecord(const System::TVarRec * Values,
const int Values_5ize);
void fastcall Delete(void);
void fastcall Edit(void);
HIDESBASE void fastcall Insert(void);
void fastcall InsertRecord(const System::TVarRec * Values,
const int Values_Size);
В классе TBDEDataSet объявлены еще два важных метода:
virtual void fastcall Cancel(void);
virtual void fastcall Post(void);
При помощи методов первой группы можно вносить изменения в набор данных,
в частности, добавлять, вставлять, удалять или редактировать записи. Методы из второй
группы позволяют отменить сделанные изменения (метод Cancel) или окончательно их
принять (метод Post). Рассмотрим их все более подробно.
Метод Insert открывает в буфере записей набора данных новую пустую запись, и дела-
ет ее активной. Набор данных переходит в состояние dslnsert, в результате чего пользова-
тель получает возможность вводить в поля записи значения. Чтобы занести информацию,
введенную пользователем в новую запись, в набор данных (т. е. добавить эту запись
в таблицу), необходимо вызвать метод Post. Для отмены вставки записи следует вызвать
метод Cancel.
Если вы вносите изменения в набор данных после вызова метода Insert, тем или иным
образом вызывая метод Post (его можно вызывать и неявно), может произойти одно из
следующих событий.
Q Для таблиц Paradox, имеющих первичный индекс, запись вставляется в набор данных
на основании значения ее первичного ключа.
Q Если таблица Paradox не имеет первичного индекса, то запись вставляется в текущую
позицию набора данных.
Borland C++ Builder 6. Разработка приложений баз данных

G Для таблиц dBASE, FoxPro и Access запись вставляется в конец таблицы. Есл!
в данный момент какой-либо из индексов таблицы активен, то запись будет отображе
на в наборе данных в соответствии со значением ее индекса.
О Для таблиц баз данных в формате SQL реальное расположение новой записи может
быть различным (в зависимости от формата), а отображаться такая запись будет в соот
ветствии с текущим индексом.
Чтобы отменить вставку записи, следует воспользоваться методом Cancel.
Метод InsertRecord также вставляет в набор данных новую запись, заполняет ее поля
в соответствии с переданными в качестве аргумента значениями и вызывает метод Post.
Вместо открытого массива аргументов метода InsertRecord удобно использовать макро
OPENARRAYили ARRAYOFCONST. Значения, передаваемые методу InsertRecord, должны
следовать в том же порядке, в каком следуют поля набора данных.
Замечание
Метод InsertRecord нельзя использовать, если в качестве первичного ключа таблицы
применяется поле типа Autoincrement и для него установлен флажок Required Field (речь
идет о локальных базах данных). В этом случае вы получите сообщение о том, что для
поля требуется значение. В принципе, первичный ключ по определению обязателен,
поэтому требование обязательности ввода в его поля — не только излишне, но и, как
видим, даже вредно.

Методы Append и AppendRecord служат для тех же целей и имеют тот же формат, что
и методы Insert и InsertRecord соответственно, однако новая запись добавляется в конец
набора записей. Для удаления текущей записи служит метод Delete, не имеющий аргумен-
тов. После удаления текущей записи активной становится запись, следующая непосред-
ственно за ней. Если такой записи нет, активной становится предыдущая запись. В пустом
наборе данных активной записи нет.
Метод Edit переводит набор данных в состояние редактирования (dsEdif). Текущая за-
пись копируется в буфер, где вы можете редактировать содержимое входящих в нее полей.
Для сохранения в наборе данных сделанных в буфере изменений следует вызвать метод
Post. Вызовом метода Cancel отменяют все изменения, возвращая набор к первоначально-
му состоянию (режим просмотра dsBrowse). Ниже приведен отрывок кода для модифика-
ции содержимого поля Index текущей записи набора.
Tablel->Edit();
Tab!el->FieldByName("Index")->AsString=EIndex->Text;
Tablel->Post();
Каждый из методов Insert, Append и Edit прежде всего вызывает метод CheckBrowseMode
для сохранения всех изменений, которые еще не были сохранены.
С каждым из методов набора данных, предназначенных для изменения записей, свя-
зана пара событий. К этим событиям относятся: Beforelnsert, BeforeEdit, BeforeDelete,
BeforePost, BeforeCancel, Afterlnsert, AfterEdit, AfterDelete, AfterPost, AfterCancel. Первое
из каждой пары этих событий происходит в самом начале действия соответствующего
метода, а второе — в конце. Например, при вызове метода Delete сначала вызывается об-
работчик события BeforeDelete, затем запись удаляется и, в самом конце, вызывается об-
работчик события AfterDelete. Конечно же, обработчики вызываются только в том случае,
если они определены.
Глава 2. Использование механизма ВОЕ 69

Обработчики вышеупомянутых событий с приставкой Before могут быть использованы


для каких-либо действий, предваряющих событие. Например, можно запросить у пользо-
вателя подтверждение выполняемых действий и затем, в зависимости от его ответа, отме-
нить их или продолжить. Обработчики событий с префиксом After могут использоваться
для каких-либо действий, следующих непосредственно за событием. Ниже приведен об-
работчик события BeforePost. Он будет вызван перед тем, как запись будет сохранена. Вначале
на экран будет выдано диалоговое окно, запрашивающее подтверждение у пользователя. Если
пользователь нажал кнопку ОК, никаких действий не будет и запись будет сохранена. Если
пользователь решил вернуться назад, в режим редактирования записи, не сохраняя ее, он
может нажать Cancel. Тогда вызывается метод Abort, прерывающий выполнение текущей опе-
рации (сохранения записи), и набор данных остается в неизменном состоянии. Если пользова-
тель нажмет кнопку No, перед методом Abort, прерывающим текущую операцию сохранения
записи, вызывается метод Cancel. В результате все изменения, внесенные в текущий набор
данных и еще не сохраненные, будут отменены.
void _ fastcall TForml: :TablelBeforePost(TDataSet *DataSet)
{
int resp;
resp=Application->MessageBox("CoxpaHHTb текущую запись?",
"Сохранение записи",
MB_YESNOCANCEL+MB_ICONQUESTION);
swi tch (resp)
case IDCANCEL:
{ AbortQ; break; }
case IDNO:
{ DataSet->Cancel() ; AbortQ; break; }

Используя методы модификации набора данных, следует иметь в виду один немало-
важный фактор. Набор данных может быть немодифицируемым, т. е. предназначаться
только для чтения. В этом случае применение одного из методов модификации приве-
дет к исключительной ситуации и выдаче сообщения: Cannot modify a read-only dataset
(«Нельзя модифицировать набор данных только для чтения»). Чтобы избежать этого,
можно предварительно проверять значение свойства логического типа CanModify. Это
свойство только для чтения и доступно только при выполнении программы. Если оно име-
ет значение true, набор данных можно модифицировать, иначе набор может использоваться
только для чтения. В некоторых случаях набор данных нельзя модифицировать даже тогда,
когда свойство CanModijy имеет значение true, например, если пользователь при подключении
к базе данных SQL не имеет надлежащих прав. В этом случае следует использовать обработ-
чик соответствующей исключительной ситуации.
Набор данных может иметь статус только для чтения по нескольким причинам,
например, если набор данных открыт другим пользователем в эксклюзивном режиме
или пользователь не имеет прав доступа для модификации набора данных (в нашем
случае таблицы). Можно и самостоятельно перевести набор данных в режим только
для чтения. Для этого нужно задать для свойства Readonly значение true; свойство
CanModify при этом автоматически примет значение/а/яе. Свойством Readonly можно
пользоваться, чтобы определить, можно ли модифицировать набор данных, точно так
же как и свойством CanModify.
70 Borland C++ Builder 6. Разработка приложений баз данных

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

Использование закладок
В наборах данных, как и в обыкновенных книгах, можно пользоваться закладками
С их помощью вы сможете быстро открыть набор записей в нужном месте. Один из вари
антов применения закладок — использование свойства Bookmark. Это свойство объявлен!
в классе TDataSet следующим образом:
property AnsiString Bookmark {read=GetBookmarkStr,
wri te=SetBookmarkStr};
Чтобы запомнить текущую позицию в наборе данных, нужно сохранить значение
свойства Bookmark набора данных в переменной типа AnsiString. Для возвращения в пози-
цию, сохраненную ранее в переменной типа AnsiString, нужно присвоить значение этой
переменной свойству Bookmark. Например, можно расположить кнопки BSaveBookmark
и BRestoreBookmark на форме, содержащей обычный набор связанных между собой ком-
понентов TDBGrid, TDataSource и TTable, подключенных к таблице Items базы данных
MyData. Для сохранения текущей позиции в наборе данных будет использоваться первая
из этих кнопок. После этого можно смело перемещаться в любую позицию набора данных,
например, при применении методов поиска или в результате прокрутки набора данных. Для
возвращения в предварительно сохраненную позицию в наборе данных будет использоваться
кнопка BRestoreBookmark. Для реализации этих возможностей следует в модуле формы опре-
делить глобальную переменную типа AnsiString (например, MyBookmark), а в обработчики
событий OnClick кнопок нужно ввести следующий код:
void fastcall TForml::BSaveBookmarkClick(TObject *Sender)
{
MyBookmark=Tablel->Bookmark;
}
void fastcall TForml::BRestoreBookmarkClick(TObject *Sender)
{
Tablel->Bookmaгk=MyBookmark;
}
Альтернативный способ использования закладок — применение методов GetBoohnark,
GotoBookmark и FreeBookmark. Они объявлены в классе TDataSet следующим образом:
v i r t u a l void * fastcall GetBookmark(void);
void fastcall GotoBookmark(void * Bookmark);
virtual void fastcall FreeBookmark(void * Bookmark);
Каждый из этих методов может быть переопределен в наследниках класса TDataSet,
чтобы воплотить свою собственную идеологию использования закладок.
Обратите внимание на то, что методы GetBookmark, GotoBookmark и FreeBookmark
оперируют с переменными типа void*. Однако гораздо нагляднее пользоваться типом
TBookmark, который объявлен в модуле db.hpp следующим образом:
typedef void "TBookmark;
Глава 2. Использование механизма BDE

Чтобы запомнить текущую позицию в наборе данных, следует прибегнуть к методу


GetBookmark. При этом нужно использовать предварительно объявленную переменную
типа TBookmark. Для перехода к закладке применяется метод GotoBookmark. Каждому
вызову метода GetBookmark должен соответствовать вызов метода FreeBookmark, так как
именно этот метод освобождает ресурсы, занятые при вызове метода GetBookmark.
Возвращаясь к примеру использования свойства Bookmark, который был описан чуть
выше, можно на той же форме расположить еще две кнопки — с именами BGetBookmark
и BGotoBookmark. В модуле формы, чуть ниже объявления объекта формы Forml, нужно
разместить следующее объявление:
TBookmark MyTBookmark;
В обработчики событий OnClick кнопок BGetBookmark и BGotoBookmark поместите
следующий код:
void fastcalT TForml::BGetBookmarkClick(TObject *Sender)
{
MyTBookmark=Tablel->GetBookmark();
}
void fastcall TForml: :BGotoBoc»kmarkClick(TObject *Sender)
{
Tablel->GotoBookmark(MyTBookmark);
Tablel->FreeBookmark(MyTBookmark);
MyTBookmark=NULL;
}
В обработчике события нажатия кнопки BGotoBookmark при помощи вызова метода
FreeBookmark освобождаются все ресурсы, выделенные под переменную MyTBookmark
при вызове метода GetBookmark. После этого переменной MyTBookmark присваивается
значение NULL. Это делается для того, чтобы предотвратить возможную утечку ресурсов,
если будет нажата кнопка BGetBookmark и вслед за этим будет закрыта форма. В тело об-
работчика события OnClose формы нужно поместить следующий оператор:
if(MyTBookmark) Tablel->FreeBookmark(MyTBookmark);
Таким образом, при закрытии формы будет проверяться текущее значение переменной
MyTBookmark. Если ее значение не равно NULL, значит, для нее необходимо вызывать
метод FreeBookmark.
Прежде чем использовать закладку в реальном приложении, нужно убедиться в том,
что этой закладке было присвоено допустимое значение. Для этого служит метод
BookmarkValid, объявленный в классе TBDEDataSet следующим образом:
virtual bool fastcall BookmarkValid(void * Bookmark);
Этот виртуальный метод переопределен в наследниках класса TBDEDataSet, в частно-
сти, в компонентах ТТаЫе и TQuery. Если переменная типа TBookmark, передаваемая
методу в качестве аргумента, имеет допустимое значение, то метод BookmarkValid воз-
вращает значение true.
Замечание
Прежде чем использовать закладки, следует убедиться в том, что набор -данных
допускает их применение. Например, однонаправленный (unidirectional) набор данных
72 Borland C++ Builder 6. Разработка приложений баз данных

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


однонаправленным, можно использовать свойство логического типа IsUniDirectional.

Состояние набора данных и компонент TDataSource


Часто возникают ситуации, когда необходимо точно знать, что происходит в данный
момент с набором данных. Например, если вызвать метод Post в то время, когда набор
данных находится в режиме просмотра, будет возбуждена исключительная ситуация
и выдано сообщение: Dataset not in edit or insert mode («Набор данных не находится
в режиме редактирования или вставки»). Чтобы избежать подобной ошибочной ситуа-
ции, прежде чем вызывать метод Post, необходимо проверить, находится ли набор данных
в состоянии редактирования или вставки записи.
Для проверки состояния набора данных следует воспользоваться его свойством State.
Оно объявлено в классе TDataSet так:
property TDataSetState State = {read=F5tate, nodefault};
Свойство State, что вполне естественно, может использоваться только для чтения. Оно
может принимать одно из значений перечислимого типа TDataSetState:
О dslnactive — или набор данных закрыт, или данные по каким-то причинам недоступ-
ны.
О dsBrowse — режим открытого набора данных по умолчанию. Набор данных в этом
режиме можно только просматривать.
О dsEdit — текущую запись набора данных в этом режиме можно редактировать. Прежде
чем перевести набор данных в такой режим, проверяют, не пуст ли этот набор. Если
нет, то текущая запись копируется в буфер, где ее можно редактировать. После того
как запись отредактирована, следует вызвать метод Post (чтобы сохранить внесенные
в запись изменения) или метод Cancel (для отмены всех изменений). В любом случае
набор данных снова переводится в режим просмотра dsBrowse.
G dslnsert — набор данных переводится в этот режим, если в него добавлена новая за-
пись, например при помощи вызова методов Insert или Append. Для новой записи
отводится место под буфер редактирования, в котором можно задать значения для
входящих в ее состав полей. Как и в случае с состоянием dsEdit, в этом состоянии не-
обходимо вызвать (или явно, или неявно) метод Post или метод Cancel для перевода
набора данных в состояние просмотра dsBrowse.
О dsSetKey — набор данных переведен в состояние задания параметров поиска или зна-
чений пределов для фильтрации. В это состояние набор данных переходит, например,
после вызова методов группы SetRange. В состоянии dsSetKey могут находиться набо-
ры данных типа ТТаЫе и TClientDataSet.
О dsCalcFields — в этом состоянии набор данных находится при обработке события
OnCalcFields (т. е. в процессе обработки (расчета значений) вычисляемых полей на-
бора данных). В этом состоянии нельзя редактировать обычные (невычисляемые) поля
и вставлять новую запись.
Q dsFilter — - свойство State имеет это значение в процессе обработки события
OnFilter-Record. В этом состоянии отображается ограниченый набор записей. Нельзя
редактировать информацию набора данных и вставлять новые записи.
Глава 2. Использование механизма BDE 73

U dsNewValue, dsOldValue и dsCurVahie. Временное состояние набора данных, использу-


емое BDE для своих служебных целей. В одно из этих состояний набор данных пере-
ходит при доступе к свойствам компонента TField NewValue, OldValue или CurValue
соответственно.
О dsBlockRead— в этом состоянии набор данных бывает при переходе к другой запи-
си — до тех пор, пока ее содержимое не будет помещено во внутренний буфер. Пока
набор данных находится в этом состоянии, элементы управления данными не обновля-
ются и не происходят связанные с ними события.
Q dslnternalCalc— это временное состояние набора данных используется BDE для
своих служебных целей при вычислении полей, имеющих тип (свойство FieldKind)
fklnternalCalc. Поля Tvm&fklnternalCalc — это поля вычисляемого типа, однако, в отли-
чие от полей irniufkCalculated, они хранятся в самом наборе данных. Более подробно
о различных типах полей (компонент TField) будет рассказано далее в этой главе.
Q dsOpening. В этом состоянии набор данных находится при его открытии в асинхронно-
м режиме — до тех пор, пока открытие не завершится.
Свойство State есть не только у наборов данных. Компонент TDataSource также имеет
свойство State типа TDataSetState, описывающее состояние набора данных, к которому
подключен данный объект TDataSource. Во многих случаях удобнее пользоваться интер-
фейсом, который предоставляет именно объект TDataSource. Чтобы понять, почему это
так, следует вначале кратко ознакомиться с возможностями компонента TDataSource.
Компонент TDataSource предоставляет интерфейс между набором данных и элеме-
нтами управления, предназначенными для отображения или манипуляции инфор-
мацией из этого набора. В документации, поставляемой с C++ Builder, компонент
TDataSource представлен как канал (conduit) между набором данных и любым эле-
ментом управления данными. Любой элемент управления данными, расположенный
на форме, обязательно должен быть подключен к одному из объектов TDataSource,
иначе он будет оставаться пустым. С другой стороны, любой набор данных должен
быть подключен к одному из объектов TDataSource, если предполагается отображать
его данные, манипулировать его информацией при помощи элементов управления
данными (Data-aware Controls) или как-то еще использовать представляемые им
данные. Кроме основной функции канала передачи информации между набором дан-
ных и элементами управления данными, компонент TDataSource используется также
для организации между наборами данных связи типа один-ко-многим (или главный/
подчиненный (Master/Detail)).
Компонент TDataSource имеет четыре свойства: уже упоминавшееся State, а также
AutoEdit, DataSet и Enabled. Свойство State типа TDataSetState описывалось выше.
Единственное отличие в использовании свойства State компонента TDataSource в том, что
если его свойство Enabled имеет значение false или не присвоено значение для его свой-
ства DataSet, то свойство State будет иметь значение dslnactive независимо от состояния
самого набора данных.
От значения свойства Enabled зависит, будет ли открыт канал данных между набором
данных и подключенными к нему элементами управления. При значении этого свойства
false связь между набором данных и элементами управления разрывается. Свойство
DataSet содержит ссылку на объект типа TDataSet, к которому должен быть подключен
данный объект TDataSource. Таким образом, канал передачи данных будет нормально
работать, если его свойство Enabled имеет значение true, а свойство DataSet содержит
74 Borland C++ Builder 6. Разработка приложений баз данных

ссылку на имеющийся в приложении объект типа TDataSet (таблица, запрос, хранима:


процедура, пользовательский набор данных).
Если вы обратили внимание, при попытке внести изменения в ячейки объект;
TDBGrid набор данных, к которому подключен объект TDBGrid, автоматически пере
ходит в режим редактирования (dsEdit). Если в состав набора свойств сетки (свой
ство-коллекция Options компонента TDBGrid) не входит элемент dgAlwaysShowEditor
то для перехода к редактированию нужно нажать клавишу Enter или F2, иначе сетк;
постоянно будет находиться в режиме редактирования. Автоматический переход на
бора данных в режим редактирования возможен благодаря тому, что свойство AutoEdit
объекта TDataSource по умолчанию имеет значение true. Если задать для AutoEdit зна-
чение false, то для перевода набора данных в режим редактирования нужно будет явно
вызывать его метод Edit. Таким образом можно предотвратить непредумышленное
изменение данных.
К числу методов компонента TDataSource, кроме конструктора и деструктора,
относятся всего два: IsLinkedTo и Edit. Метод Edit переводит связанный с объектом
TDataSource набор данных в режим редактирования, если это возможно. Вначале про-
веряется свойство AutoEdit. Если его значение равно true, проверяется состояние набо-
ра данных. Если набор данных в состоянии просмотра (dsBrowse), вызывается метод
Edit набора данных. Таким образом, метод Edit компонента TDataSource берет на себя
проверку допустимости перевода набора данных в режим редактирования, поэтому
иногда пользоваться им удобнее.
IsLinkedTo — низкоуровневый метод, проверяющий подключение объекта TDataSource
к указанному в качестве аргумента объекту типа TDataSet. Этот метод может заинтересо-
вать разработчиков компонентов.
Наибольший интерес для программистов представляют события компонента
TDataSource. Их всего три: OnDataChange, OnStateChange и OnUpdateData. Обработчики
последних двух событий имеют стандартный тип TNotifyEvent, а тип обработчика собы-
тия OnDataChange объявлен следующим образом:
typedef void fastcall ( closure *TDataChangeEvent)
(TObject* Sender, TField* Field);
Событие OnDataChange происходит при каждом переходе на новую запись в наборе
данных, в том числе при его открытии, а также при модификации значения поля при пере-
ходе к другому полю (например, в сетке). Если запись модифицирована не была, то при
переходе к другой записи аргумент Field будет иметь значение NULL. To же значение ар-
гумент Field будет иметь, если были изменены значения сразу нескольких полей. Однако
это возможно только при модификации программным путем. Использовать это событие
можно, например, для изменения значений элементов управления, не связанных с набо-
ром данных, для обновления других открытых в данный момент окон приложения и для
прочих подобных действий.
Событие OnUpdateData происходит непосредственно перед тем, как данные будут
сохранены в базе данных, до того как произойдет событие BeforePost. Использовать об-
работчик этого события можно для дополнительной обработки данных перед их сохране-
нием или для проверки допустимости внесенных изменений.
И, наконец, событие OnStateChange происходит всякий раз при изменении состояния
набора данных, например, при его переходе из режима просмотра в режим редактирова-
ния или вставки. Это событие пригодится для проверки текущего состояния набора дан-
Глава 2. Использование механизма BDE 75

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


доступ к тем или иным элементам управления формы.
Чтобы проиллюстрировать применение события OnStateChange, можно использовать
приложение ModifyRec, которое мы создали ранее при описании методов модификации
записей набора данных. Откройте это приложение в IDE C++ Builder и добавьте на фор-
му компонент TStatusBar. Он автоматически будет выровнен по нижней части формы
(свойство Align будет иметь значение alBottom). Задайте для свойства SimplePanel объекта
TStatusBar значение true — это позволит пользоваться свойством SimpleText для отобра-
жения текста строки состояния.
В заключение создайте обработчик события OnStateChange и введите в него следую-
щий текст:
void fastcall TForml::DataSourcelStateChange(TObject *Sender)
AnsiString StatusText;
switch(DataSoureel->State)
case dslnactive: { StatusText="Inactive"; break; }
case dsBrowse: { StatusText="Browse"; break; }
case dsEdit: { StatusText="Edit"; break; }
case dslnsert: { StatusText="Insert"; break; }
case dsSetKey: { StatusText="SetKey"; break; }
case dsCalcFields: { StatusText="CalcFields"; break; }
case dsFilter: { StatusText="Filter"; break; }
case dsNewValue: { StatusText="NewValue"; break; }
case dsOldValue: { StatusText="01dValue"; break; }
case dsCurValue: { StatusText="CurValue"; break; }
case dsBlockRead: { StatusText="BlockRead"; break; }
case dsInternalCalc: { StatusText="InternalCalc"; break; }
case dsOpening: { StatusText="Opening"; break; }
default: 5tatusText="Unknown" ;
StatusBarl->SimpleText="DataSet Status: " + StatusText;

Думаю, нет смысла объяснять приведенный выше фрагмент программы.


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

Объект TField и редактор полей


Класс TField является программной оболочкой поля набора данных (таблицы, запроса
и т.д.). Этот класс — общий предок для специализированных классов полей, представля-
ющих различные типы данных. Объекты класса TField в приложении никогда не исполь-
зуются, вместо этого используются наследники, соответствующие всем типам данных,
применяемым в C++ Builder. Приложение может иметь дело с классами полей одного из
типов, описание которых приведено в таблице 2.1.
Borland C++ Builder 6. Разработка приложений баз данных

Таблица 2.1. Наследники класса TField

Класс Описание
TADTField Непосредственный наследник класса TObjectField. В наборе
данных представляет поля типа ADT (Abstract Data Type). Поля
ADT — это поля пользовательского типа (структуры), создан-
ные на стороне сервера. Не все форматы баз данных поддержи-
вают поля ADT.
TAggregateFi eld Непосредственный наследник класса TField. Представляет по-
ля агрегатного типа в клиентских наборах данных. Поля агре-
гатного типа — это совокупность записей и столбцов набора
данных. Для каждого агрегатного поля должна быть определена
итоговая операция, вычисляющая значение по данным, входя-
щим в поле.
TArrayField Непосредственный наследник класса TObjectField. Представляет
в наборе данных поля типа массив. Поле типа массив может со-
держать набор однородных полей. В этот набор могут входить
как поля скалярного типа (целые, с плавающей точкой), так
и нескалярные (например, поля типа ADT).
TAutoIncField Непосредственный наследник класса TInteger Field, который,
в свою очередь, является непосредственным наследником класса
TNumericField. Представляет поля автоинкрементного типа.
TBCDField Непосредственный наследник класса TNumericField. В наборе дан-
ных представляет поля в двоично-десятичном коде (Binary Coded
Decimal). Значения типа BCD часто используются для представле-
ния денежных величин в финансовых расчетах, так как позволяют
производить вычисления практически без потери точности.
TBinaryField Непосредственный наследник класса TField. В наборе данных
представляет нетипизированные бинарные поля.
TBlobField Непосредственный наследник класса TField. Представляет по-
ля, содержащие ссылку на объекты BLOB (Binary Large Object).
TBooleanField Непосредственный наследник класса TField. Представляет поля
логического типа.
TBytesField Непосредственный наследник класса TBinaryField. Представляет
поля типа Byte.
TCurrencyField Непосредственный наследник класса TFloatField, который яв-
ляется наследником класса TNumericField. Представляет поля
типа Currency.
TDataSetField Непосредственный наследник класса TObjectField. Представляет
поля, содержащие ссылку на набор данных, который является
вложенным (nested) для данного набора.
TDateField Непосредственный наследник класса TDateTimeField.
Представляет поля, содержащие значения даты.
Глава 2. Использование механизма ВОЕ 77
TDateTimeField Непосредственный наследник класса TField. Родовой класс для
представления полей, содержащих значения даты и времени.
TFloatField Непосредственный наследник класса TNumericField.
Представляет поля, содержащие числа с плавающей точкой.
TFMTBCDField Непосредственный наследник класса TNumericField.
Представляет поля в двоично-десятичном коде BCD (Binary
Coded Decimal). Класс поля TFMTBCDField отличается от клас-
са поля TBCDField большей точностью и меньшей производи-
тельностью. Этот класс появился в C++ Builder 6.
TGraphicField Непосредственный наследник класса TBlobField. Представляет
поля, содержащие графические данные.
TGuidField Непосредственный наследник класса TStringField. Представляет
поля, содержащие глобальный уникальный идентификатор
(GU1D — Globally Unique Identifier).
TIDispatchField Непосредственный наследник класса TInterfaceField. Представляет
поля, содержащие указатель на интерфейс IDispatch.
TIntegerField Непосредственный наследник класса TNumericField.
Представляет поля целого типа.
TInterfaceField Непосредственный наследник класса TField. Родовой класс,
представляющий поля, в качестве значений содержащие указа-
тели на интерфейс (lUnknown).
TLargelntField Непосредственный наследник класса TNumericField.
Представляет поля типа large integer.
TMemoField Непосредственный наследник класса TBlobField. Представляет
поля MEMO.
TReferenceField Непосредственный наследник класса TObjectField. Представляет
поля, содержащие указатели или ссылки на другие объекты.
TSmalllntField Непосредственный наследник класса TIntegerField, кото-
рый, в свою очередь, унаследован от класса TNumericField.
Представляет поля типа small integer.
TSQLTimeStampField Непосредственный наследник класса TField. Представляет поля
типа Date/Time в наборах данных dbExpress. Этот класс появил-
ся в C++ Builder 6.
TStringField Непосредственный наследник класса TField. Представляет поля
текстового типа.
TTimeField Непосредственный наследник класса TDateTimeField.
Представляет поля, содержащие значения времени.
TVarBytesField Непосредственный наследник класса TBytesField, который,
в свою очередь, унаследован от класса TBinaryField. В наборе
данных представляет нетипизированные бинарные поля пере-
менной длины.
78 Borland C++ Builder 6. Разработка приложений баз данных

TVariantField Непосредственный наследник класса TField. Представляет поля


типа Variant.
TWideStringField Непосредственный наследник класса TStringField. Представляет
поля типа WideString.
TWordField Непосредственный наследник класса TInteger Field, который
унаследован от класса TNumericField. Представляет значения
типа Word.

Класс TField задает общую функциональность всех классов полей. Этот класс вводит
свойства, методы и события, позволяющие редактировать значения полей в наборе дан-
ных, проверять допустимость вводимых в поле данных, преобразовывать значения полей
из одного типа в другой, задавать стиль отображения данных в соответствующих элемен-
тах управления, вычислять значение поля на основании значений других полей в том же
или в другом наборе данных. Большая часть свойств и методов TField переопределяется
в его классах-наследниках ради возможности манипулировать представляемыми этими
классами данными. Кроме того, каждый из классов-наследников определяет свои свойства
и методы. Например, наряду со свойствами и методами, определенными в классе TField,
в его наследнике, классе TStringField, объявлены также свойства FixedChar и Transliterate,
специфичные для значений текстового типа.
Здесь не будут рассматриваться специфические особенности каждого из классов-на-
следников TField: с этим можно разобраться и самостоятельно. Предпочтительней из-
учить свойства, методы и события, составляющие общую функциональность всех классов
полей.

Свойства, методы и события класса TField


Класс TField имеет множество свойств «на все случаи жизни». В этом разделе будет рас-
смотрена часть этих свойств, в основном те, с которыми приходится сталкиваться чаще всего.
С остальными свойствами можно ознакомиться в справочной системе C++ Builder.
Большая часть свойств класса TField связана с преобразованием значений поля в опр-
еделенный тип данных. Наименования этих свойств состоят из приставки As и следу-
ющего за ней наименования типа, в который будет производиться преобразование. Эти
свойства — самые простые, полезные и часто используемые. Ниже приведено несколько
примеров их применения.
Ansi String Price=Tablel->FieldByName("Price")->AsString;
int IntPrice=Tablel->FieldByName("Price")->AsInteger;
Currency CurPrice=Tablel->FieldByName("Price")->AsCurrency;
Еще одна группа свойств связана со значением поля. К ней можно отнести следующие
свойства.
CurValue, OldValue и NewValue (первые два предназначены только для чтения) — эти
свойства содержат, соответственно, текущее значение поля, старое значение и новое. Все
эти свойства используются только в случае, если доступ к данным осуществляется при
помощи компонента TClientDataSet или разрешены кэшированные изменения (cached
updates). Эти свойства применяются, если при внесении изменений в базу данных (при
вызове метода Post) произошла ошибка. Тогда в обработчиках событий OnUpdateError
или OnReconcileError можно воспользоваться свойствами CurValue, OldValue и NewValue,
Глава 2. Использование механизма ВОЕ 79

чтобы устранить источник ошибки.


Value, DisplayText и Text. Свойство Value используется для прямого чтения/записи зна-
чения поля (без преобразования) при выполнении программы. Тип этого свойства всегда
совпадает с типом самого поля. Например, если тип поля — TCurrencyField, то и свойст-
во Value имеет тип Currency. Свойства DisplayText и Text принадлежат к типу AnsiString
и содержат текстовое представление значения поля в том виде, в каком это значение ото-
бражается в элементах управления данными. Свойство Text содержит текстовое представ-
ление значения поля в процессе его редактирования в элементе управления. Свойства Text
и DisplayText используются в обработчике события OnGetText поля, в тело которого они
передаются в качестве аргументов.
Свойства DataType и FieldKind описывают тип данных поля и тип самого поля.
Свойство DataType служит для задания типа данных при создании объекта поля про-
граммным путем. Свойство DataType может принимать одно из значений перечислимого
типа TFieldType. Значения этого типа соответствуют перечисленным в таблице 2.1 клас-
сам-наследникам класса TField. Например, классу TIntegerField соответствует константа
ftlnteger, и т.д. Свойство FieldKind задает тип поля и может принимать одно из значений
перечислимого типа TFieldKind:
• fkData — поле содержит обычные данные, хранящиеся в наборе данных.
• jkCalculated— поле является вычисляемым. Значение поля вычисляется в обрабо-
тчике события OnCalcFields.
• fkLookup — поле является полем подстановки. О вычисляемых полях и полях под-
становки будет рассказано чуть далее.
• JklnternalCalc — вычисляемое поле, значение которого хранится в наборе данных,
как и у обычного поля типа/kData. К этому типу относятся поля, вычисляемые на
стороне серверов баз данных формата SQL, или поля, вычисляемые BDE для ото-
бражения результатов запроса, для которого свойство RequestLive имеет значение
true.
• jkAggregate — поле агрегатного типа.
Еще несколько свойств имеют отношение к типу поля. Это Calculated, Lookup,
LookupCache, LookupDataSet, KeyFields, LookupKeyFields, LookupList и LookupResultField.
Значение true свойства Calculated поля означает, что это поле вычисляемо, т. е. его значе-
ние вычисляется в обработчике события OnCalcFields набора данных. Остальные свой-
ства имеют отношению к полям подстановки. Все они будут рассмотрены дальше.
Особый интерес представляет свойство AutoGenerateValue. Дело в том, что не все
драйверы баз данных могут определить поля, значения для которых должны подставлять-
ся сервером автоматически. К таким относятся поля автоинкрементного типа и те, для
которых задано значение по умолчанию. В таком случае при попытке сохранить модифи-
цированную или новую запись в базе данных (при вызове метода Post) будет возбуждено
исключение. Для того чтобы избежать этого и предназначено свойство AutoGenerateValue.
С его помощью можно сообщить серверу БД, что данное поле или автоинкрементно, или
имеет значение по умолчанию. AutoGenerate Value может принимать одно из значений типа
TAutoRefreshFlag: arNone (сервер не должен подставлять значение в это поле), arAutoInc
(данное поле автоинкрементно), arDefault (поле имеет значение по умолчанию). Несмотря
на то, что не всегда требуется соответствующая установка свойства AutoGenerateValue,
рекомендуется всегда задавать необходимое значение для полей автоинкрементного типа
и полей, имеющих значение по умолчанию.
80 Borland C++ Builder 6. Разработка приложений баз данных

Значение по умолчанию для объекта поля можно задать при помощи свойств
DefaultExpression. В качестве значения этого свойства может быть любое выраже
ние, допустимое в операторах SQL, не содержащее ссылку на значение какого-либ|
поля. Если выражение не является числовым, его нужно заключить в одинарные ка
вычки, например, 'No Item' или '15:45'. Если для компонента поля задано свойств*
DefaultExpression, то оно переопределяет значение по умолчанию, заданное для пол;
набора данных, так как поля заполняются значениями в приложении до того как лю
бые изменения отсылаются серверу.
Класс TField имеет еще одну группу свойств, по своему назначению напоминаю
щих свойства набора данных (таблицы). Это свойства, связанные с ограничениями ш
вводимые в поле значения. К ним можно отнести CustomConstraint, ImportedConstraint
ConstraintErmrMessage и HasConstraint. В свойстве CustomConstraint можно задать стро-
ку условия, которое будет налагаться на вводимые в поле значения в дополнение к ограни-
чениям, заданным в самом наборе данных. Если введенное значение не будет удовлетво-
рять условию, указанному в свойстве CustomConstraint, то будет отображено сообщение
об ошибке, заданное в свойстве ConstraintErmrMessage.
Свойство ImportedConstraint может содержать строку условия на вводимые в поле зна-
чения, заданного для поля в самом наборе данных. Кроме того, свойство ImportedConstraint
может быть пустым. Других вариантов быть не должно. Если свойство ImportedConstraint
пусто, ограничения для поля, заданные в наборе данных, проверяются на стороне сер-
вера, и при их нарушении именно сервер возбуждает исключительную ситуацию. Если
ограничения, заданные в наборе данных, продублированы в свойстве ImportedConstraint,
то они проверяются в приложении, и именно приложение возбуждает исключение при их
нарушении. В этом случае необходимо задать в свойстве ConstraintErrorMessage строку
сообщения, которая будет отображена для пользователя.
Свойство HasConstraint имеет булевый тип и предназначено только для чтения. Если
это свойство имеет значение true, то для поля задано одно из возможных ограничений.
Другими словами, для поля задано одно из свойств: CustomConstraint, ImportedConstraint
или DefaultExpression.
Если свойство FieldKind объекта-поля имеет значение fkData, то свойство FieldName
этого объекта содержит имя поля набора данных, которое является для него источником
данных.
И в заключение короткого обзора свойств класса TField хотелось бы упомянуть о еще
трех. Свойство Islndex содержит значение true, если поле входит в текущий (активный)
индекс набора данных. Свойство IsNull содержит значение true, если в поле не было вве-
дено никакого значения. Свойство EditMask имеет тип TEditMask (синоним AnsiString)
и задает ограничения на вводимые в поле символы. За более подробной информацией
о допустимых масках ввода следует обратиться к описанию типа TEditMask в справочной
системе C++ Builder.
Класс TField имеет не очень много методов. Практически все они предназначены для
внутреннего использования BDE. Некоторый интерес представляют, пожалуй, только
методы Assign и Clear. Метод Assign копирует свойство Value из другого поля или при-
сваивает полю другой объект. Оба поля или поле и присваиваемый объект должны иметь
один тип. Например, при вызове метода TablelDateO->Assign(TablelDatel) полю DateO та-
блицы Table 1 будет присвоено значение поля Datel той же таблицы. Предполагается, что
оба поля имеют тип Date. В результате вызова метода TablelDescription->Assign(Memol
->Lines) полю Description таблицы Tablel, которое имеет тип Memo, будет присвоено со-
Глава 2. Использование механизма BDE

держимое элемента управления Memol, имеющего тот же тип. Метод Clear очищает поле,
присваивая его свойству Value значение NULL.
У класса TField всего четыре события, но в некоторых случаях они весьма полезны. Это
OnChange, OnGetText, OnSetText и OnValidate. Событие OnChcmge происходит сразу после
того, как данные поля помещаются в буфер текущей записи. Отредактированные данные
поля помещаются в буфер текущей записи при переходе набора данных из режима редакти-
рования в режим просмотра, в том числе при переходе на другое поле текущей записи или
на другую запись. Обработчик события OnChange вызывается сразу после вызова обработ-
чика события OnValidate, но только если данные поля были успешно приняты и помещены
в буфер текущей записи. В обработчике события OnChange можно запрограммировать от-
клик на введенные в поле новые данные, например, обновление не связанных с набором
данных элементов управления. В качестве аргумента в обработчик события OnChange пере-
дается указатель на объект поля, для которого было вызвано событие.
Событие OnValidate происходит непосредственно перед тем как данные поля будут по-
мещены в буфер текущей записи. Обработчику события в качестве аргумента передается
указатель на объект поля, для которого это событие и было вызвано. Событие OnValidate
можно использовать для дополнительной проверки введенного в поле значения на уровне
приложения. Если значение не удовлетворяет критериям приложения, необходимо возбу-
дить исключение (при помощи вызова throw). В этом случае значение не будет сохранено
в базе данных и событие OnChange не произойдет.
Обработчики событий OnGetText и OnSetText объявлены следующим образом:
typedef void fastcall ( closure *TFieldGetTextEvent)(TField* Sender,
AnsiString &Text, bool DisplayText);
typedef void fastcall ( closure *TFieldSetTextEvent)(TField* Sender,
const A n s i S t r i n g T e x t ) ;
Событие OnGetText происходит всякий раз при обращении к свойствам DisplayText или
Text объекта TField. Например, это событие происходит для каждого элемента управления
данными, в котором должно отображаться значение соответствующего поля. При заполне-
нии элемента управления TDBGrid данными событие OnGetText происходит столько раз,
сколько записей содержит сетка. Помимо указателя Sender на объект поля, для которого
вызывается обработчик события, ему также передается ссылка на пустую строку Text типа
AnsiString.
После того как код обработчика события выполнит все запрограммированные дей-
ствия, значение аргумента Text будет использовано для отображения в соответствующем
элементе управления данными. Например, если для отображения информации из табли-
цы используется элемент управления TDBGrid, и для одного из компонентов поля вы
создали пустой обработчик события OnGetText, то соответствующий столбец сетки при
выполнении приложения будет пуст. Другими словами, если вы создали обработчик со-
бытия OnGetText, то сами должны позаботиться о правильном заполнении аргумента Text.
Если обработчика события нет, вместо Text используется свойство AsString объекта поля.
Отсутствие обработчика события OnGetText равносильно наличию следующего обработ-,
чика:
void fastcall TForml::TablelItemGetText(TField *Sender,
AnsiString &Text, bool DisplayText)
{
Text=Sender->AsString;
82 Borland C++ Builder б. Разработка приложений баз данных

Третий аргумент обработчика события OnGetText— переменная логического тип


DisplayText. Как сказано в документации, этот аргумент указывает, будет ли строка Text ис
пользоваться только при отображении или при редактировании тоже. Однако я не замети
каких-то различий в поведении поля в зависимости от значения этого аргумента.
Событие OnSetText происходит после того, как свойству Text присваивается какое
либо значение. В тело обработчика этого события также передается аргумент Text, н
с модификатором const, так что изменить строку Text уже нельзя. Однако можно из
менить свойство Text компонента поля. Если вы внесли в это свойство в обработчик^
события OnGetText какие-либо изменения, то в обработчике OnSetText эти изменена
следует отменить.

Постоянные и динамические поля. Редактор полей


Объекты поля могут быть или динамическими (Dynamic fields), или ПОСТОЯННЫМР
(Persistent fields). Динамические объекты полей создаются автоматически при открытии
набора данных, если он не имеет постоянных объектов полей. В этом случае для каждого
поля набора данных создается объект поля. Тип объекта поля совпадает с типом соответ-
ствующего поля набора данных. Динамические поля располагаются в наборе в том же по-
рядке, что и поля таблицы. Таким образом, набор динамических полей позволяет работать
с ними так, как будто это поля таблицы.
Работать с динамическими полями очень просто. Для их создания не нужно ничего
делать — вы открываете набор данных и получаете в свое распоряжение набор объектов,
которые можно использовать для доступа к полям таблицы. Набор динамически создава-
емых полей всегда правильно отражает структуру набора данных. Если в таблицу были
добавлены новые поля, или какие-то поля были из таблицы удалены или переименованы,
это никак не скажется на вашем приложении. Это происходит потому, что при каждом от-
крытии набора данных считывается информация о его структуре и затем эта информация
используется для создания набора полей.
Для хранения определений полей набора данных предназначено свойство FieldDefs
компонента ТТаЫе. Оно имеет тип TFieldDefs и хранит список определений полей набора
данных, к которому подключен объект ТТаЫе. Заполняется свойство FieldDefs при каждом
открытии набора данных, а затем, если нет постоянных полей, на его основе создается
набор динамических полей. Свойство FieldDefs кроме определения всех полей табли-
цы может содержать и определения других полей, например вычисляемых (Calculated
Fields). Оно также может использоваться для программного создания таблицы (наряду
со свойством IndexDefs), для копирования структуры таблицы в другую таблицу и прочих
подобных задач.
Постоянные поля (Persistentfields) создаются при разработке приложения при помощи
специального инструмента, называемого редактором полей (Fields Editor). Создавая на-
бор постоянных полей, вы можете включить в него только те поля набора данных, которые
вам нужны, причем в том порядке, который вам нужен. Создание постоянных полей по-
зволяет быть уверенными в том, что при каждом запуске приложения вы получаете один
и тот же набор полей, упорядоченных именно так, как вы и ожидаете. Более существенное
преимущество постоянных полей в том, что с ними вы получаете возможность настро-
ить объекты полей так, как это не сделаешь при использовании динамических полей.
Некоторая часть важных свойств компонента TField (а также его наследников) доступна
в режиме конструктора только для постоянных полей.
Глава 2. Использование механизма ВОЕ 83
Нет единого рецепта, какой из типов полей (динамические или постоянные) нужно вы-
брать при создании собственного приложения. В большинстве случаев, конечно, удобнее
пользоваться постоянными полями. Один раз создав и настроив набор нужных полей, вы
можете без проблем пользоваться им всегда. Но есть случаи, когда без динамических по-
лей не обойтись. Один из недостатков постоянных полей проявляется при удалении полей
из таблицы, для которой созданы объекты постоянных полей. В этом случае при запуске
приложения возбуждается исключение, сообщающее о том, что соответствующего поля
в таблице нет. Единственный способ добиться, чтобы приложение нормально работало
и дальше,- открыть его в режиме конструктора и удалить объекты постоянных полей, ко-
торые вызывают ошибку.
Есть еще ряд случаев, когда без набора динамических полей не обойтись, в частно-
сти, в приложении, в котором заранее не известна структура таблицы. В качестве примера
можно привести приложение, отображающее содержимое таблицы, имя которой выбира-
ется из списка и структура которой заранее не известна.

tern
Category
temMo jfcKn !Co«fX>.*l
328 HUBCompexPS <ndex
329 HUBHPPraCun Price
330 ниВСотрвкО;
331 HUBEP-ai6DX-
332 Плота Mel EthE
333 Ппота Intel EthE
33-1 Плата Intel Ethe Express Pro/1 Ods Ptl Server Adapt
335 Плота Intel Pro/1DOS Dual Port Server Adapter
336 Плата imel Pro/ COOT
j 337 HUB Intel Stainda one Managed Switch

<JJ :•••' ±f

Рис. 2.13. При помощи редактора полей можно перемещаться по записям


в режиме конструктора
Итак, если вы решили воспользоваться набором постоянных полей, вам следует об-
ратиться к редактору полей. Его можно вызвать, или дважды щелкнув в форме на изо-
бражении компонента-наследника класса TDataSet (например, ТТаЫе или TQuery), или
выбрав из его контекстного меню пункт Fields Editor. В любом случае, на экране будет
отображено соответствующее диалоговое окно (см. рис. 2.13).
Практически вся функциональность редактора полей сосредоточена в его контекстном
меню. Чтобы добавить в список постоянных полей нужные поля из набора данных, следу-
ет выбрать из контекстного меню пункт Add fields или пункт Add all fields. В первом случае
на экране будет отображено диалоговое окно, содержащее список доступных полей набо-
ра данных. Во втором — все поля будут добавлены автоматически.
Еще один пункт контекстного меню — New field — служит для добавления поля
в список постоянных полей. При выборе этого пункта на экране отобразится одно-
именное диалоговое окно. Оно позволяет добавить определение поля одного из сле-
дующих типов: поле данных (Data), вычисляемое (Calculated) и поле подстановки
(Lookup). Если вы используете клиентский набор данных (например, ClientDataSet),
то можно добавить также определения полей агрегатного типа (Aggregate) и вычисля-
емых полей типа IntemalCalc. О создании вычисляемых полей и полей подстановки
будет рассказано далее. Что касается полей данных (Data), то они должны основы-
ваться на существующих в наборе данных полях. Как сказано в документации, поля
84 Borland C++ Builder 6. Разработка приложений баз данных

этого типа могут использоваться для замены полей набора данных, например, для и
менения типа данных поля.
Основную часть рабочей области окна редактора полей занимает список постоянш
полей. При выборе поля в этом списке инспектор объектов будет отображать все его сво
ства. Вы можете изменить значения всех свойств поля, включая свойства Name и Fiel
Name. Свойство FieldName содержит имя поля набора данных, которому соответству
объект поля, а свойство Name — имя самого объекта. По умолчанию в качестве HMCI
объекта поля используется составное имя, содержащее имя поля и имя компонента н
бора данных. Например, Tablelhem. Вы можете задать любое подходящее имя и зате
использовать его в программе для доступа к свойствам поля. Это гораздо удобнее, че
громоздкие конструкции вида Table!->FieldByName(«Item»).
Обратите внимание на то, что изменить тип значений поля при помощи инспектор
объектов вам не удастся. При создании объекта поля редактор полей получает информ;
цию о типе данных поля (или непосредственно из набора данных, или от пользователя пр
создании нового поля) и на основании этой информации создает объект подходящего та
па. Например, для поля Item таблицы Items редактор полей создаст постоянное поле тип
TStringField, а для поля Price той же таблицы — объект типа TCurrencyField. Так что есл]
вы уже создали постоянное поле, то единственный способ изменить его тип — удалить и
списка и создать заново, но уже с другим типом данных. Удалить постоянное поле можно
выделив его в списке редактора полей и нажав клавишу Delete.
Группа из пяти пунктов контекстного меню редактора полей предназначена для рабо
ты с наборами атрибутов (Attribute Sets). О наборах атрибутов полей уже рассказывалос!
в предыдущей главе, при описании работы с утилитой SQL Explorer. Наборы атрибутов
созданные при помощи SQL Explorer, можно использовать в редакторе полей для быстрой
настройки свойств полей. Для этого нужно выделить наименование поля в списке и выбр-
ать из контекстного меню пункт Associate attributes. На экране будет отображено диалого-
вое окно, содержащее список всех доступных на данный момент наборов атрибутов. Если
на вкладке Dictionary утилиты SQL Explorer вы раскроете узел Attribute Sets, вы увидите
точно такой же список.
Если вы выберете в диалоговом окне подходящий набор атрибутов и нажмете кнопку
ОК, объект поля будет связан с этим набором. Это значит, что при помощи нескольких
щелчков мышки вы сможете задать значения для многих свойств поля, включая ограни-
чения, сообщения об ошибке и т.д. Связь поля с набором атрибутов означает, что все из-
менения, сделанные в наборе атрибутов, будут отражаться и на свойствах поля. Чтобы от-
делить поле от набора атрибутов, следует выбрать пункт Unassociate attributes. Значения
свойств поля при этом не изменятся, но изменения в наборе атрибутов больше влиять на
них не будут.
Если поле ассоциировано с каким-либо набором атрибутов, можно воспользоваться
еще тремя пунктами контекстного меню редактора полей: Save attributes, Save attributes
as и Retrieve attributes. При помощи пункта Save attributes можно сохранить изменения,
сделанные в свойствах поля, в наборе атрибутов, с которым это поле связано. С помощью
пункта Save attributes as можно сохранить набор атрибутов в словаре под другим именем.
Пункт Retrieve attributes предназначен для обновления свойств поля на основе связанного
с ним набора атрибутов.
В заключение обзора возможностей редактора полей следует упомянуть о панели на-
вигации, расположенной в верхней части рабочей области его окна. С помощью этой па-
нели можно передвигаться по записям набора данных прямо в режиме конструктора.
Глава 2. Использование механизма ВОЕ

Замечание
Поля, перечисленные в списке редактора полей, можно перетаскивать с помощью
мышки на форму. При этом на форму будут добавляться элементы управления,
соответствующие перетаскиваемому полю. Например, для полей простого типа
(числового или текстового) будет создан объект типа TDBEdit, а при перетаскивании на
форму поля, содержащего рисунок, будет создан объект типа TDBImage. Кроме того, для
каждого из созданных элементов управления данными на форму будет добавлена метка
(объект типа Tlabel), в качестве подписи содержащая имя поля. Можно пометить группу
полей, которые предполагается разместить на форме, и перетащить их все на форму за
один раз. Такой нехитрый прием позволяет моментально воспроизвести необходимую
структуру формы.

Вычисляемые поля (Calculated Fields)


-
При помощи редактора полей можно также создавать и вычисляемые поля (Calculated
Fields), т.е. поля, получающие значение в результате операций, которые могут произво-
диться с участием значений других полей текущей записи. Например, при заполнении на-
кладной на отпуск товаров достаточно указать количество товара и его цену, а стоимость,
которая требуется в документе, может быть рассчитана на основании первых двух данных.
Вследствие этого в таблице нет смысла хранить сведения о стоимости товара, достаточно
данных о количестве и цене, а стоимость будет представлена вычисляемым полем.
Создание вычисляемого поля— двухшаговый процесс. На первом шаге нужно при
помощи редактора полей определить вычисляемое поле. На втором шаге в обработчике
события OnCalcFields набора данных нужно задать способ, которым поле будет получать
значение. Следует иметь в виду, что помимо определения вычисляемого поля нужно также
определить и все поля данных, значения которых должны будут отображаться в элементах
управления, а также поля набора данных, информация из которых будет использоваться
при определении значений вычисляемых полей. Для полей, значения которых будут ис-
пользоваться при расчетах, но не должны отображаться в форме, нужно для свойства
Visible задать значение false.
В обработчике события OnCalcFields для каждого вычисляемого поля должна при-
сутствовать конструкция внца.ИмяПоля->Уа1ие-значение. Кроме конструкций этого вида
в обработчике события OnCalcFields могут присутствовать также и другие достаточно
сложные вычисления. Однако следует помнить, что это событие может происходить ча-
сто, поэтому незачем чрезмерно раздувать его обработчик. Событие OnCalcFields проис-
ходит в таких случаях:
Q когда открывается набор данных, в котором определены вычисляемые поля;
Q когда набор данных переходит в состояние редактирования (состояние dsEdif);
G когда запись возвращается из базы данных (например, при обновлении набора данных).
Кроме того, если свойство AutoCalcFields имеет значение true, событие OnCalcFields
будет происходить также и при переходе фокуса с одного элемента управления на другой
в пределах одной записи или при переходе с одной колонки сетки (TDBGrid) на другую.
Если набор данных содержит много вычисляемых полей, и пользователю разрешено
редактировать записи таблицы, то свойству AutoCalcFields нужно задать значение false,
иначе приложение будет сильно «притормаживать».
Прежде чем привести пример использования вычисляемых полей, добавим в нашу ба-
86 Borland C++ Builder б. Разработка приложений баз данных

зу данных с псевдонимом MyData, пока содержащую две таблицы, еще одну таблицу. Он
понадобится нам и в дальнейшем. Откройте Database Desktop и создайте новую таблиц
типа Paradox 7. Добавьте в ее макет определения полей, как указано в таблице 2.2.
Таблица 2.2. Определение полей таблицы Employees

Имя поля (Field Name) Тип (Туре) Размер (Size) Ключ (Key)
EmployeeNo (Счетчик) Autoincrement (+) *
LName (Фамилия) Alpha (A) 50
FName (Имя) Alpha (A) 20
MName (Отчество) Alpha (A) 25
HireDate (Дата приема) Date (D)
FireDate (Дата увольнения) Date (D)
Description (Характеристика) Memo (M) 240
Photo (Фотография) Graphic (G)
Birthday (Дата рождения) Date (D)

Для полей LName и HireDate следует установить флажок Required field — они долж-
ны быть обязательно заполнены. Далее в поле со списком Table properties выберите
позицию Secondary Indexes и нажмите кнопку Define. Создайте вторичный индекс на
основе полей LName, FName и MName (именно в такой последовательности) и дайте
ему имя NameNDX. Сохраните таблицу в том же каталоге, что и остальные таблицы,
под именем Employees. Таким образом, мы получили таблицу для хранения инфор-
мации о сотрудниках. Если вы заметили, в таблице Employees нет данных о зарплате
сотрудников и о занимаемых ими должностях. Для этого лучше создать отдельную
таблицу, что-то вроде штатного расписания. В ней будут храниться те данные о сотр-
уднике, которые со временем могут меняться. По коду сотрудника (EmployeeNo) обе
таблицы должны быть связаны.
Теперь перейдем к примеру применения вычисляемых полей. Как вы уже, навер-
ное, догадались, в этом примере будет использоваться созданная нами новая таблица
Employees. Пока она пуста, поэтому вы можете заполнить ее данными по своему усмо-
трению или скопировать с диска, поставляемого вместе с книгой. Далее» создайте новое
приложение и разместите на пустой форме компоненты TDBGrid, TTable и TDataSource,
оставив их имена по умолчанию. Подключите, как всегда, DBGridl к DataSourcel, a Data-
Sourcel к Tablel. Объект Tablel подключите к таблице Employees базы данных MyData.
Задача состоит в следующем: необходимо создать список сотрудников, еще не уво-
ленных с предприятия, содержащий фамилии и инициалы, возраст и стаж работы. Список
должен быть отсортирован по возрастанию по фамилиям, именам и отчествам.
Нас интересуют еще не уволенные сотрудники, т.е. те, для которых поле FireDate (дата
увольнения) не заполнено. Поэтому прежде всего необходимо отфильтровать набор дан-
ных так, чтобы в итоговую выборку попали только те записи о клиентах, значение поля
FireDate которых равно NULL. Для этого в свойство Filter набора данных введите следую-
щую строку: FireDate=NULL. Чтобы итоговая выборка была отсортирована по фамилиям
и инициалам по возрастанию, укажите в качестве текущего индекса созданный ранее при
конструировании таблицы Employees индекс NameNDX (просто выберите его имя из спи-
ска свойства IndexName).
Глава 2. Использование механизма ВОЕ 87

В итоговой выборке должны присутствовать (отображаться) только три поля, причем


все три — вычисляемые: Name (фамилия и инициалы), Age (возраст), Standing (стаж).
Откройте редактор полей (например, дважды щелкнув мышкой на значке объекта ТТаЫе).
Далее, выбрав пункт New field контекстного меню, в одноименном диалоговом окне вве-
дите определение всех трех полей. Имя каждого из них необходимо ввести в поле ввода
Name, имя компонента поля можно не менять (имя компонента по умолчанию имеет вид
ТаЫеШмя_поля). Из комбинированного списка Туре нужно выбрать тип String для поля
Name и тип Smalllnt для полей Age и Standing. В поле ввода Size укажите размер 50 для
поля Name, для остальных полей размер подставляется автоматически и изменить его
нельзя. В заключение следует для каждого из полей в области Field type выбрать значение
переключателя Calculated. На рис. 2.14 изображен момент определения поля Standing.

Рис. 2.14. Определение вычисляемого поля Standing


Для вычисления значений полей Name, Age и Standing нам понадобятся также поля
LName, FName, MName, HireDate и Birthday. Кроме того, нужно добавить поле FireDate,
так как оно используется в выражении для фильтра (в противном случае при открытии на-
бора данных вы получите сообщение об ошибке). Откройте редактор полей и выберите из
контекстного меню пункт Add fie Ids. В списке доступных полей выберите все вышеназван-
ные и нажмите кнопку ОК. Теперь нужно задать для свойства Visible всех добавленных из
набора данных полей значение false, так как эти поля не нужны нам в итоговой выборке.
Сделать это можно, выделив все нужные поля в окне редактора полей и задав значение
false свойству Visible для всей группы целиком.
Далее, создайте обработчик события OnCalcFields и введите в него следующий код:
void _fastcall TForml: :TablelCalcFields(TDataSet *DataSet)

AnsiString FI.SI;
unsigned short yearl, yearO, month, day;

if (!TablelFName->IsNull)
FI=" " + (TablelFName->Value). SubString(0, 1) + "." ;
if((!Table!MName->IsNuU) && ( ! (FI . IsEmpty ()) ))
SI=" " + (TablelMName->Value) . SubString(0, 1) + "." ;
TablelName->Value=TablelLName->Value + FI + SI;
Date() .DecodeDate(&yearl, &month, &day) ;
(TablelBirthday->Value) .DecodeDate(&year0, &month, &day) ;
TablelAge->Value=yearl-year0;
(TablelHireDate->Value) .DecodeDate(&year0, &month, &day) ;
TablelStanding->Value=yearl-year0;
Borland C++ Builder 6. Разработка приложений баз данных

Первые два оператора обработчика предназначены для формирования инициалов. О


формируются в переменных FI и SI типа AnsiString. Первый инициал создается, если i
ле FName не пусто. Чтобы проверить это, используется метод IsNull. Если поле FNar
не пусто, переменной FI присваивается строка, содержащая пробел, первую букву име
и символ точки. Для отделения первой буквы строки используется метод Substring клас
AnsiString. Этот метод возвращает подстроку длиной, указываемой вторым аргументе
начиная с символа, номер которого указан первым аргументом. Манипуляции, продел
ваемые с переменной SI, аналогичны, за исключением того, что второй инициал форми]:
ется только в том случае, когда есть первый. Чтобы проверить это, используется еще од
полезный метод класса AnsiString — метод IsEmpty.

Бобров С Н
ЦрейбвнФ.А

Рис. 2.15. Набор данных, который содержит три вычисляемых поля


В заключение этого участка кода свойству Value вычисляемого поля Name присваив
ется строка, содержащая фамилию и инициалы сотрудника. Это ключевой оператор: i
будь его, вычисляемое поле Name осталось бы пустым.
В оставшейся части обработчика события OnCalcFields вычисляются значения для пол(
Age и Standing. Возраст сотрудника (поле Age) определяется как разность между текущн
годом и годом, соответствующим дате рождения. Стаж (поле Standing) равен разности межд
текущим годом и годом-приема на работу. Для получения текущей даты используется мете
Date, возвращающий значение типа TDateTime. Класс TDateTime имеет метод DecodeDat
с помощью которого можно извлечь из даты значения года, месяца и дня.
В заключение нужно создать обработчик события OnCreate формы и ввести в нег
следующий код:
void fastcall TForml::FormCreate(TObject *Sender)
{
Tablel->0pen();
Tablel->Filtered=true;
}
В этом обработчике открывается набор данных и включается фильтр. Откомпилируйт
приложение и запустите его на выполнение. На рис. 2.15 изображен примерный вид по
лучившегося приложения.
Замечание
Решать задачи наподобие описанной в этом пункте гораздо удобнее с помощьк
другого наследника класса TDataSet — компонента TQuery. Более подробно о компонент!
TQuery будет сказано далее-.
Глава 2. Использование механизма BDE

Поля подстановки (Lookup Fields)


Поля подстановки (Lookup Fields) предназначены для отображения в наборе данных
значений полей из другого набора данных. Подстановка информации из другого набора
данных производится на основании связи между ключевыми полями обоих наборов. Чаще
всего поля подстановки используются для замены внешнего ключа таблицы, содержащего
числовое значение, более описательным значением, соответствующим первичному ключу
другой таблицы. Например, в таблице Items есть поле Category, являющееся внешним
ключом по отношению к таблице Category. Это поле содержит числовые значения, ко-
торые не очень-то информативны. Если категорий в таблице более сотни, то запомнить
номер каждой категории не в состоянии даже очень опытный оператор. Используя поле
подстановки, можно вместо числовых значений категорий получить в наборе данных их
текстовые описания.
В качестве примера создадим поле подстановки для поля Category таблицы Items. Для
начала нужно создать новое приложение и, как обычно, разместить на его главной форме
компоненты TDBGrid, TTable и TDataSource, оставив их имена по умолчанию. Далее свя-
жите эти компоненты между собой обычным образом, подключив объект Tablel к таблице
Items базы данных MyData. Для свойства Active объекта Tablel задайте значение true,
открыв тем самым набор данных. Нам понадобится еще один объект типа ТТаЫе — раз-
местите его на форме и подключите к таблице Category. Имя объекта по умолчанию
(ТаЫе2) оставьте без изменения.
Теперь нужно воспользоваться редактором полей. Отобразите его на экране, дважды
щелкнув на объекте Tablel. Выберите из контекстного меню пункт Add all fields, чтобы
добавить в список постоянных полей все поля из набора данных. Далее, нам нужно до-
бавить новое поле — поле подстановки. Выберите из контекстного меню редактора полей
пункт New field. В окне определения нового поля, в области Field properties, укажите имя
поля (Name) (поле Category в списке уже есть, поэтому в качестве имени можно выбрать
CategoryName), его тип (Туре) и размер (Size) (мы предполагаем использовать наимено-
вание категории из таблицы Category, поэтому нужно выбрать тип String размером в 25
символов).
Затем в области Field type выберите значение переключателя Lookup, чтобы указать,
что создаваемое поле — это поле подстановки. В результате в области Lookup definition
станут доступны два комбинированных списка: Key Fields и Dataset. Элемент управления
Key Fields служит для того, чтобы задать одно или несколько полей, на основании которых
будет осуществляться подстановка. Чаще всего здесь указывается внешний ключ текуще-
го набора данных. Одно поле можно выбрать из списка элемента Key Fields, а несколько
полей можно указать самим, разделяя их наименования точкой с запятой. В нашем при-
мере нужно выбрать поле Category.
Список элемента Dataset содержит перечень всех доступных наследников класса
TDataSet (в нашем случае их два: Tablel и Table!). Нужно выбрать тот набор данных,
значения из которого будут использоваться для подстановки. Несмотря на присутствие
в списке объекта Tablel, задавать его в качестве подстановочного набора данных не сле-
дует, иначе при выполнении приложения будет возбуждена исключительная ситуация (ци-
клическая ссылка). В нашем примере следует выбрать объект ТаЫе2. После этого станут
доступны еще два комбинированных списка: Lookup Keys и Result Field.
90 Borland C++ Builder 6. Разработка приложений баз данных

: Field ptoperties
: Цата: |CrtegoiyJome Component |ToblelC3tegoryName2 ;.,;

Type:.. '.'Sting ^J Site: |re

; Field уре- '

''. gey field*: ' JGategoi

Lookup Keys: jCainqoiyNo _Tj Besuft Field; fCotegoi

Рис. 2.16. Определение поля подстановки CategoryName


Элемент управления Lookup Keys должен содержать одно или несколько полей по;
становочного набора данных (Lookup DataSei). Список полей, содержащийся в элемет
управления Lookup Keys, должен точно соответствовать списку полей, указанных в элем<
нте управления Key Fields. По совпадению значений полей из этих списков и будет ос>
1
ществляться подстановка. В простейшем случае здесь нужно указать первичный клю
соответствующий внешнему ключу текущего набора данных, указанному в элементе Kt
Fields. В нашем примере следует выбрать поле CategoryNo.
И последний элемент управления из группы Lookup definition— Result Field. И
списка полей подстановочного набора данных, прикрепленного к элементу Result Fieh
нужно выбрать поле, значения из которого и будут представлены в текущем набор
данных в качестве подстановки. Выберите из этого списка поле Category. На рис. 2.1
представлено диалоговое окно New Field, содержащее определение поля подстановк
CategoryName.
Теперь набор данных Tablel содержит два поля, описывающих категорию. Пол
Category, содержащее числовые значения внешнего ключа, в итоговой выборке нам н
нужно, поэтому необходимо задать для свойства Visible этого поля значение/я/уе. Пол
CategoryName будет смотреться лучше, если оно будет следовать непосредственно з
полем Item. Чтобы добиться этого, достаточно перетащить его при помощи мышк:
вверх по списку в редакторе полей. И последний штрих: задайте свойству DisplayLabt
поля CategoryName значение Category. Это значение и будет отображаться в качеств
заголовка столбца категорий в приложении. Откомпилируйте приложение и запустит
его на выполнение. Примерный вид приложения в режиме выполнения представлю
на рис. 2.17.

» 1 Samsung ML-4500 Принтеры А< GDI, ОЗУ 2 Мб, 600 dpi. .—


J

2 SamsungML-1210 Принтеры А4. GDI, ОЗУ В Мб. 600 dpi, . :


3 Epson EPL-59QQL Принтеры А4. GDI, ОЗУ 2(1 Э) МБ. 600 с ^ J
A Canon LBP-81Q Принтеры А4. GDI, 600 dpi. аи 8 pprn, i *j
5 Brother HL-1Z40 Mono Laser Принтеры A4. ОЗУ 2М6. 600x600 dpi, a \
e.EpsonAcuLossfCIOOO Принтеры A4, цвет. ОЗУ 1 6(256) Мб. Е ,;i.
7 EpsonAcuLaserCZQOO Принтеры A4, цвет, ОЗУ 64(51 2) Мб, Е ":|
8 HPLeserJetlCQQw Принтеры :А< ОЗУ! Мб, GDI, 600 dpi, .-*'v:|
• 9 HPLeserJel1200 Принтеры М ОЗУ 8(72) Мб. 1200 dpi, T" 'J
10 HP LaserJet 1220 Принтеры А4, сканер 600 dpi • 24 бит : ! i
11 HP LaserJet 3200 Принтеры А4. сканер 600 dpi- 24 бит ,J. '.
«1J JLJ

Рис. 2.17. Набор данных, содержащий поле подстановки Category


Глава 2. Использование механизма ВОЕ 91

Выделите в редакторе полей поле подстановки CategoryName и обратите внимание на


свойства, отображенные в инспекторе объектов. Можно заметить, что указанные в диало-
говом окне New field значения отобразились на соответствующие свойства поля. Свойству
FieldKind присвоено значение JkLookup, в полном соответствии с выбранным в диало-
говом окне значением переключателя Field type. Значения четырех комбинированных
списков из области Lookup definition отображены, соответственно, на свойства KeyFields,
LookupDataSet, LookupKeyFields и LookupResultField. В принципе, все эти свойства можно
настроить и без участия редактора полей, например, если вам нужно их отредактировать
после того, как поле подстановки уже было создано.
В заключение следует отметить еще одно свойство полей подстановки, которое может
значительно повлиять на производительность приложения, особенно при удаленном под-
ключении к источнику данных. Это свойство логического типа LookupCache. Если задать
для него значение true, то необходимые значения из подстановочного набора данных
при открытии текущего набора данных будут кэшироваться. В этом случае при переходе
к другой записи значение для поля подстановки будет выбираться из кэша, а не из табли-
цы. Если таблица расположена на удаленном сервере и качество связи оставляет желать
лучшего, то кэширование полей подстановки позволяет значительно увеличить произво-
дительность приложения.
С другой стороны, если значения поля подстановки для каждой записи отличны друг
от друга, значительные расходы на поиски необходимого значения в кэше могут вызвать
обратный эффект, снижая производительность приложения.

Организация связи между таблицами


Одна из задач, очень часто встречающихся при проектировании приложений управ-
ления базами данных,- организация между таблицами связи типа главная/подчиненная
(Master/Detail). На практике эта связь обычно выглядит так: выбор записи или отдельного
значения в одном из элементов управления, подключенных к одному набору данных, при-
водит к тому, что связанный набор данных будет содержать только те записи, которые со-
ответствуют сделанному выбору. Как и следует из названия, в такой связи участвуют две
таблицы — главная (Master) и подчиненная, или детальная (Detail). С точки зрения орга-
низации базы данных главная таблица — это таблица-справочник, содержащая описание
одной из главных сущностей, используемых в других, детальных таблицах. Эти понятия
относительны, как и все в этом мире. Таблица, детальная по отношению к одной таблице,
может быть главной по отношению к другой.
Создание связи главная/подчиненная между двумя таблицами легче всего объяснить
на примере. Именно так мы и поступим. Предположим, нам нужно создать приложение,
отражающее информацию о товарах, принадлежащих конкретной категории. Категорию
можно выбирать при выполнении приложения. В этом случае информацию о товарах
удобно отображать в элементе управления TDBGrid, а категорию — в элементе управле-
ния TDBLookupComboBox.
Создайте новое приложение и разместите в верхней части пустой формы компонент
TDBLookupComboBox, а под ним — компонент TDBGrid. Далее, разместите на форме по паре
объектов TTable (Tablel и ТаЫе2) и TDataSource (DataSourcel и DataSource2). Присоедините
объекты DataSourcel и DataSource2 к объектам Tablel и ТаЫе2 соответственно. Затем Tablel
подключите к таблице Category базы данных MyData, а ТаЫе2 — к таблице Items той же ба-
зы. Таким образом, объект Tablel будет представлять главную таблицу в формируемой связи
(Category), а ТаЫе2 — подчиненную, или детальную (Items).
92 Borland C++ Builder 6. Разработка приложений баз данных

Объект DBGridlсвяжите с DataSource2. Объект DBLookupComboBoxl настрою


следующим образом. Для свойства ListSource в качестве значения укажите набо
данных DataSourcel, для свойства ListField укажите поле Category, а для свойств
KeyField— поле CategoryNo. Таким образом вы устанавливаете, что источнике:
строк для списка элемента управления DBLookupComboBoxl будет DataSource
1
(таблица Category), отображаться в нем будут значения из поля Category исто
ника строк (текстовое представление категории), а значением элемента управлени
DBLookupComboBoxl будет значение из поля CategoryNo (числовое представление
Именно поле CategoryNo, указанное в качестве ключевого (свойство KeyField), и буде
т использоваться далее для формирования связи.
Итак, мы получили два независимых потока данных: один от таблицы Categor
посредством объектов Table] и DataSourcel к элементу управления данным:
DBLookupComboBoxl, а второй — от таблицы Items посредством объектов ТаЫе2 и Data
Source! к элементу управления DBGridl. Чтобы эти потоки данных работали во взаимо
действии, необходимо задать связь между Tablel и ТаЫе2.
Выделите на форме объект ТаЫе2, который будет играть роль подчиненного набор;
данных, и задайте в качестве значения свойства MasterSource объект DataSourcel, подклю
ченный к главному источнику данных. Затем щелкните мышкой в инспекторе объектов н<
кнопке построителя, расположенной в правой части поля ввода для свойства MasterField
На экране будет отображено диалоговое окно Field Link Designer (конструктор связей)
В верхней части окна расположен комбинированный список Available Indexes (доступные
индексы). В нем перечислены все индексы, определенные для подчиненной таблицы. Дл?
таблицы Items это Primary (первичный ключ), Category (внешний ключ) и ItemNDX (вто-
ричный индекс). Выберите в этом списке индекс Category.

Моду пи памяти __»]

Swat (tern ; (Category JDescnpliorv . *|


Descripsc * 1
DfMM 64 Mb PC1 33. Infineon 5 1 69 pin. 54 bit
DIMM 64 Mb РСШ NCP 5 68 pin. 64 bit
DIMM 1 28 Mb PC! 33, POI original 5 60pm, 64 bit В chip— '•-
DIMM 1 28 Mb PC1 33, NCR 5 68 pin. 64 bit
DIMM 1 26 Mb PCI 33. Samsung original 5 : 63 pin. 5-1 bit 8 chip ..,;.
DIMM 1 28 Mb PCI DO. Transcend 5 rS16MLE?2V6XN.w |
DIMM ?SG Mb PCI 33. Spectek 5 6 chips
DIMM 256 Mb PCI 33. M tec 5 68pm. 64 bit
.QIMM256MbPC133.PQI 5 68 pin. 54 bit
J DIMM 256 Mb PCI 33. Samsung original 5 1 68 pin. 64 bit
1
LLJ jr
Рис. 2.18. Окно редактора связей Рис. 2.19. Приложение, демонстрирующее
между таблицами (Field Link Editor) связь между таблицами. При выборе
категории в комбинированном списке
элемент TDBGrid будет отображать список
товаров, ей соответствующих

Чуть ниже комбинированного списка Available Indexes расположены еще два элемента
управления. Список Detail Fields содержит перечень полей, входящих в выбранный ин-
декс. В нашем примере здесь будет только одно поле — Category. В списке Master Fields
перечислены все поля главной таблицы. Чтобы создать нужную нам связь, выберите
в списке Detail Fields поле Category, а в списке Master Fields — поле CategoryNo, после
чего нажмите кнопку Add. В результате этих действий созданная между полями связь
будет добавлена в список Joined Fields (см. рис. 2.18). Связь между таблицами будет окон-
Глава 2. Использование механизма ВОЕ

чательно оформлена после нажатия кнопки ОК. При этом свойство MasterFields получит
значение CategoryNo.
И последний штрих, впрочем, не имеющий отношения к заданию связей. Создайте об-
работчик события OnCreate формы и введите в него следующий оператор:
void _fastcall TForml::FormCreate(TObject *Sender)
{
DBLookupComboBoxl->KeyValue=Tablel->
Field ByName("CategoryNo")->Value;
}
При помощи этого оператора устанавливается начальное значение комбинирован-
ного списка подстановки DBLookupComboBoxl. Если этого не сделать, то после за-
грузки приложения этот элемент управления будет пуст. В свойстве KeyValne элемента
DBLookupComboBoxl содержится значение ключевого поля, в нашем случае — поля
CategoryNo. Присвоив этому свойству значение поля CategoryNo первой записи открытого
набора данных Tablel, мы приведем в соответствие оба элемента визуализации данных.
Откройте оба набора данных, присвоив их свойствам Active значение true, и запу-
стите приложение на выполнение. Вы должны получить результат наподобие изобра-
женного на рис. 2.19. Теперь можно выбирать из комбинированного списка категорию,
а элемент TDBGrid будет автоматически обновляться, отображая список товаров вы-
бранной категории.

Режим кэширования изменений


Механизм BDE, как, впрочем, и другие механизмы доступа к данным, поддерживает
режим кэширования изменений. В режиме кэширования изменений (Cached Updates Mode)
все изменения, вносимые в набор данных, не записываются в базу данных сразу, а хранятся
в локальной памяти того компьютера, на котором все эти изменения и были сделаны. Все
операции по модификации содержимого полей записей набора данных, а также по удалению
и вставке записей, хранятся в текущем каталоге в виде файлов с расширением MB, например,
Dell.MB или Del2.MB. Иногда эти файлы автоматически не удаляются, засоряя диск ненужной
информацией. В этом случае их придется удалять самому.
Режим кэширования изменений поддерживается в BDE на нескольких уровнях: компо-
нента базы данных (TDatabase), отдельного набора данных (ТТаЫе или TQuery), а также
клиентского набора данных (компонент TBDEClientDataSef). Использование клиентского
набора данных — рекомендованный подход к работе в режиме кэширования изменений.
Если вы хотите получить все преимущества этого режима, то должны воспользоваться
услугами компонента TBDEClientDataSet. В простейших случаях ограничиваются воз-
можностями компонентов TDatabase и ТТаЫе (TQuery). Компоненты TDatabase и TBDE-
ClientDataSet будут рассматриваться в следующих разделах, а в этом остановимся на воз-
можностях компонента ТТаЫе.
Чтобы перевести набор данных в режим кэширования изменений, нужно присвоить
свойству CachedUpdates компонента ТТаЫе значение true. Начиная с этого момента все
сохраненные (при помощи метода Post) изменения в наборе данных, включая вставку или
удаление записей, а также редактирование информации, записываются в специальную
область локальной памяти (кэш, или cache). Информация обо всех изменениях набора
данных накапливается в локальном кэше до тех пор, пока не будет явно вызван или метод
ApplyUpdates, или метод CancelUpdates.
94 Borland C++ Builder 6. Разработка приложений баз данных

Метод ApplyUpdates переписывает все несохраненные изменения из кэша в базу да]


ных, однако, в отличие от одноименного метода компонента TDatabase, не очищает кэ!
Чтобы очистить кэш, нужно вслед за вызовом ApplyUpdates вызвать метод CommitUpdate
Метод CancelUpdates просто очищает кэш, тем самым отменяя все изменения, внесеннь
в набор данных со времени последнего вызова метода ApplyUpdates или CancelUpdati
(или с момента включения режима кэширования изменений).
Такой механизм взаимодействия приложения с набором данных (таблицей в 6asi
е данных) очень выгоден в распределенных приложениях клиент-серверного TI
па. В этом случае набор данных или его часть копируется на клиентскую машин
и дальнейшая работа с ним происходит без непосредственного взаимодействия с базо
данных. Когда необходимые изменения внесены, вызов метода ApplyUpdates отсылае
всю необходимую информацию о сделанных изменениях на сервер БД. Это позволяе
снизить нагрузку на сеть и сервер, а также не мешать другим клиентам, которые тож
работают с этой базой данных.
После вызова одного из методов — ApplyUpdates или CancelUpdates — набор данны
все еще будет находиться в режиме кэширования изменений. Чтобы его отключить, нужн
задать для свойства CachedUpdates компонента ТТаЫе значение true. Если перед выходи
из режима кэширования в кэше была информация о несохраненных изменениях, то поел
задания для свойства CachedUpdates значения false вся она будет безвозвратно утерян
(без предупреждения). Чтобы избежать этой неприятности, можно применить очен
полезное свойство UpdatesPending. Это свойство логического типа содержит значение
true, если в кэше набора данных есть несохраненные изменения. Таким образом, пере,
выходом из режима кэширования изменений (или перед выходом из приложения) можн<
проверить значение свойства UpdatesPending. Конечно, свойство CachedUpdates должж
при этом иметь значение true. Если свойство UpdatesPending имеет значение false, можн<
вызвать или метод ApplyUpdates, чобы сохранить накопившиеся в кэше изменения, шп
метод CancelUpdates — чтобы отменить их. Вполне естественно, вначале необходимо за
просить мнение оператора, работающего с приложением.
В классе TBDEDataSet объявлены два события, непосредственно связанные с механи-
змом кэширования изменений:
typedef void fastcall ( closure *TUpdateRecordEvent)
(Db::TDataSet* DataSet, Db::TUpdateKind UpdateKind,
TUpdateAction &UpdateAction);
typedef void fastcall ( closure *TUpdateErrorEvent)
(Db::TDataSet* DataSet, b::EDatabaseError * E,
Db::TUpdateKind UpdateKind, TUpdateAction &Upd'ateAction) ;
Каждое из этих событий может происходить при пересылке несохраненных изменений
в базу данных, т.е. в промежутке времени между вызовом метода ApplyUpdates и заве-
ршением сохранения кэшированных изменений. Обработчик события OnUpdateRecord
типа TUpdateRecordEvent вызывается для каждой записи — вставленной, удаленной или
модифицированной, — информация о которой хранится в кэше. Этим событием можно
воспользоваться для дополнительной обработки, включающей обновление связанных за-
писей при каскадном обновлении или удалении, а также для дополнительного контроля
над параметрами подстановки и в других подобных случаях.
Кроме традиционного для такого рода события указателя на объект набора данных
DataSet, обработчик события OnUpdateRecord принимает еще два важных аргумента.
Аргумент UpdateKind типа TUpdateKind, как и следует из его названия, указывает на тип
Глава 2. Использование механизма ВОЕ 95

обновления, который применяется для данной записи. Аргументы этого типа могут при-
нимать одно из следующих значений: ukModify (запись была модифицирована), uklnsert
(запись была вставлена) и ukDelete (запись была удалена). Обычно этим аргументом поль-
зуются в информационных целях.
Третий аргумент — - UpdateAction — имеет особое значение для обработчика со-
бытия OnUpdateRecord. Если вы создали обработчик, то обязаны задать для аргумента
UpdateAction подходящее значение типа TUpdateAction. К этому типу относятся следую-
щие значения: uaAbort (текущее действие прекращается без выдачи сообщения об ошиб-
ке), uaApplied (текущее действие продолжается, т. е. запись сохраняется в базе данных),
uaFail (текущее действие прекращается, и на экране отображается сообщение об ошибке),
uaSkip (сохранение записи прекращается, но информация о ней остается в кэше). К этому
типу относится также значение uaRetry (повтор попытки сохранить запись), но пользо-
ваться им можно только в обработчике события OnUpdateRecord. Если вы создали пустой
обработчик события, то аргумент UpdateAction автоматически получит значение uaFail.
Сообщение об ошибке при этом будет информировать, скорее всего, о том, что сохране-
ние невозможно, так как набор данных предназначен только для чтения. Для того чтобы
запись была действительно сохранена в наборе данных, в теле обработчика события
OnUpdateRecord нужно присвоить аргументу UpdateAction значение uaApplied.
Если обновление какой-либо записи привело к генерации исключения, то для этой за-
писи вызывается событие OnUpdateError. Например, исключение можно сгенерировать
при помощи оператора throw, если проверка допустимости введенных данных закончи-
лась неудачей. Обработчик этого события имеет четыре аргумента: три аргумента по типу
и смыслу совпадают с аргументами обработчика события OnUpdateRecord, а четвертый
(точнее, второй по счету) имеет тип указателя на объект EDatabaseError. Этот объект со-
держит информацию о происшедшей ошибке. При помощи события OnUpdateError вы
можете по-своему обработать возникшую исключительную ситуацию.
Чтобы проиллюстрировать принципы применения основных свойств, методов и событий,
связанных с режимом кэширования изменений, рассмотрим небольшой пример. Итак, создай-
те новое приложение и на пустой форме разместите компоненты TDBGrid, TTable и TDataS-
ource, связав их, как обьино, в одну информационную цепочку и оставив их имена по умол-
чанию. Объект Tablel подключите к таблице Items базы данных MyData. Откройте набор
данных и переведите его в режим кэширования изменений, задав для свойства CachedUpdates
значение true. В верхней части формы разместите следующие элементы управления.
Q Флажок (TCheckBox) и метку (TLabef). Для флажка задайте имя CBUpdatesPending
и пустую строку в качестве свойства Caption. Для метки задайте подпись:
«Незавершенные обновления». Этот флажок должен будет отображать состояние кэша.
Если флажок установлен, значит, в кэше имеются несохраненные изменения.
Q В верхней правой части формы разместите панель (ТРапеГ) и две кнопки на ней. Для
одной кнопки задайте имя ButtonApply и подпись Обновить, а для второй — имя
ButtonCancel и подпись Отменить. При помощи этих кнопок можно будет или сохра-
нить кэшированные изменения, или отменить их.
Q В центре верхней части формы разместите еще одну панель и на ней четыре мет-
ки (TLabel). Для меток задайте следующие имена и подписи, соответственно: LIns
и «Вставленных записей: 0», LDel и «Удаленных записей: 0», LMod и «Модифициро-
ванных записей: 0», LErr и «Ошибок при обновлении: 0». Эти метки будут отображать
количество сохраненных записей в зависимости от типа их обновления, а если при
обновлении произошли ошибки, то и количество ошибок.
96 Borland C++ Builder 6. Разработка приложений баз данных

Упорядочьте все элементы управления в соответствии с рис. 2.20. Далее необхо


димо создать код, который заставит работать все элементы управления совместно
В первую очередь следует позаботиться об установке правильного значения для флажк;
CBUpdatesPending. Так как все изменения кэшируются после явного или неявного вы
зова метода Post, то для этого нужно воспользоваться событием AfterPost объекта Table!
Создайте обработчик этого события и введите в его тело следующий код:
CBUpdatesPending->Checked=TaЫel->UpdatesPending;
После любого вызова метода Post состояние флажка CBUpdatesPending будет в точно-
сти соответствовать значению свойства UpdatesPending.

Не завершенные

* 1 ; Упаленнычзеписей: В Сшиби»; ni«'! а&нквоении:!}-" Отменить

r.t. . * •о jftem - - - " |C«»gp - ""- - - 1 -_ij


1 Samsung МЫ530 A4. GDI. ОЗУ 2 Мб. 600 dpi, до В ppm, поток 1 50 г-1
2 Samsung ML-1^10 АЧ GDI. ОЗУ 8 Мб. 600 dpi, до П ppm, лоток 1 %'
3 Epson EPL-5900L А4, GDI, ОЗУ 2(13) Мб, 600 dpi, до 12 ppm. лото-: :;:
4 Canon LBP-810 А4. GDL 600 dpi, до 8 ppm, лот ок 1 00 листов. LF :.:.
5 Brother HL-1 240 Mono Lase '-. А4, ОЗУ 2М&, 600x600 dpi, до 1 2 ppm, лоток 250 ;. - '..
\ 6 Epson AcuLaser C1 OOOw
1
А4, цвет, ОЗУ 1 6(256) МБ. 600 dpi/2400 dpi RIT. а \
? EpsonAcul_oserC20GQ А4, цвет, ОЗУ 84(512) Мб, 600 dpi/2400 dpi RIT. «, .Д
8 HP LaserJet 1000 А4. ОЗУ 1 Мб, GDI. 600 dpi до 1 D ppm, лоток 2БО |
_ 9 HP LaserJet ?200 А4. ОЗУ 0(72) Мб. 1?00 dpi, до 1 4 ppm, лоток 250 Щ

iLd ±Г

Рис. 2.20. Приложение — пример использования режима кэширования изменений


Основную функциональную нагрузку в этом приложении, конечно, несут обра-
ботчики событий нажатия кнопок ButtonApply и ButtonCancel. При нажатии первой
из них должен вызываться метод ApplyUpdates и вслед за ним метод CommitUpdates.
Основное действие, которое должно быть произведено при нажатии второй кнопки,
- вызов метода CancelUpdates. В каждом из обработчиков событий нажатия кнопки
нужно позаботиться и о флажке CBUpdatesPending, так как и метод CommitUpdates,
и метод CancelUpdates очищают кэш несохраненных изменений. Таким образом,
в каждый из обработчиков этих событий нужно вставить в точности такой же опера-
тор, как и в обработчике события AfterPost.
После нажатия кнопок ButtonApply к ButtonCancel следует обновить информацию
о количестве сохраненных изменений в зависимости от типа, и о количестве происшед-
ших в процессе сохранения ошибок. Эта информация будет храниться в переменных
целого типа NDel, NIns, NMod и NErr. Чтобы они были доступны везде в модуле Unitl.cpp,
вслед за объявлением в верхней части модуля:
TForml *Forml;
введите следующее объявление:
int NDel, NIns, NMod, NErr;
В конструкторе формы для каждой из этих переменных задайте значение 0 (следует
придерживаться правил явной инициализации). Заботу о правильном значении каждой из
переменных NDel, NIns, NMod и NErr разумно будет возложить на обработчики событий
OnUpdateRecord и OnUpdateError. Создайте обработчики этих событий. Далее, в обра-
ботчике события OnUpdateRecord следует проверить значение аргумента UpdateKind и,
в зависимости от него, увеличить соответствующую переменную на 1. Кроме того, нужно
Глава 2. Использование механизма BDE 97
не забыть задать для аргумента UpdateAction значение uaApplied. В обработчике собы-
тия OnUpdateError нужно увеличить на 1 значение переменной NErr, а для аргумента
UpdateAction задать значение uaAbort.
Функцию обновления содержимого меток bins, LDel, LMod и LErr берет на себя приват-
ный метод UpdateLabel. В нем на основании значений переменных NIns, NDel, NMod и NErr
обновляется свойство Caption каждой из четырех меток. Кроме того, здесь же все соответ-
ствующие переменные обнуляются. Следует позаботиться о том, чтобы метод UpdateLabel
вызывался из обработчиков событий нажатия кнопок ButtonApply и ButtonCancel.
Приложение-пример полностью приведено в листингах 2.7 и 2.8, а его вид в режиме
выполнения показан на рис. 2.20.
Листинг 2.5. Файл заголовка приложения CachedUpdates
11-
#ifndef UnitlH
«define UnitlH
//-.
«include <Classes.hpp>
«include <Controls.hpp>
«i nclude <5tdCtrls.hpp>
«include <Forms .hpp>
«include <DB.hpp>
«include <DBGrids.hpp>
«include <DBTables.hpp>
«include <Grids . hpp>
«i nclude <ExtCtrls.hpp>
«include <DBClient.hpp>
«include <DBLocal .hpp>
«i nclude <DBLocalB.hpp>
«include <Provider .hpp>
//..
class TForml : public TForm
published: // IDE-managed Components
TDBGrid «DBGridl;
TTable *Tablel;
TDataSource *Data5ourcel;
TButton *ButtonUpdate;
TButton *ButtonCancel;
TCheckBox *CBUpdatesPending;
TPanel *Panell;
TPanel *Panel2;
TLabel *LIns;
TLabel *Label2;
TLabel *LDel;
TLabel *LMod;
TLabel *LErr;
void fastcall ButtonUpdateClick(TObject «Sender)
void fastcall ButtonCancelClick(TObject «Sender)
void fastcall TablelAfterPost(TDataSet *DataSet)
void fastcall TablelUpdateRecord(TDataSet «DataSet,
TUpdateKind UpdateKind, TUpdateAction &UpdateAction);
void fastcall TablelUpdateError(TDataSet *DataSet,
EDatabaseError *E, TUpdateKind UpdateKind,
4 Зак. 319
98 Borland C++ Builder 6. Разработка приложений баз данных

TUpdateAction &UpdateAction) ;
void _ fastcall FormCloseQuery(TObject *Sender, bool &CanClose)
private: // User declarations
void _ fastcall UpdateLabel(void) ;
public: // User declarations
_ fastcall TForml (TComponent* Owner);

extern PACKAGE TForml *Forml;


//---
#endif

Листинг 2.6. Главный модуль приложения CachedUpdates

#include <vcl.h>
#pragma hdrstop
#include "Unitl.h"

#pragma package(smart_ini t)
#pragma resource "*.dfm"
TForml *Forml;
int NDel, NIns, NMod, N E r r ;
_fastcall TForml::TForml(TComponent* Owner)
: TForm(Owner)
NDel=0; NMod=0;
NIns=0; NErr=0;

void _ fastcall TForml: :ButtonUpdateClick(TObject «Sender)


{
Tablel->ApplyUpdates() ;
Tablel->CommitUpdates() ;
CBUpdatesPending->Checked=Tablel->UpdatesPending;
UpdateLabeK) ;

void _ fastcall TForml : :ButtonCancelClick(TObject *5ender)


{
Tablel->CancelUpdates() ;
CBUpdatesPending->Checked=Tablel->UpdatesPending;
UpdateLabeK) ;

void _ fastcall TForml : :TablelAfterPost(TDataSet *DataSet)


{
CBUpda te s Pending->Checked=Tabl el ->Updates Pending;

void _ fastcall TForml : :TablelUpdateRecord(TDataSet *DataSet,


TUpdateKind UpdateKind, TUpdateAction &UpdateAction)
Глава 2. Использование механизма BDE 99

UpdateAction=uaApplied;
switch(UpdateKind)
case ukModify:
{ NMod++; break; }
case uklnsert:
{ NIns++; break;}
case ukDelete:
{ NDel++; break; }

void fastcall TForml::TablelUpdateError(TDataSet *DataSet,


EDatabaseError *E, TUpdateKind UpdateKind,
TUpdateAction &UpdateAction)
С
NErr++;
UpdateAction=uaAbort;

void fastcall TForml::UpdateLabel(void)


1Лп5->Сариоп="Вставленных записей: " + IntToStr(NIns);
NIns=0;
Юе1->Сар11оп="Удаленных записей: " + IntToStr(NDel);
NDel=0;
1Мой->СарНоп="Модифицированных записей: " + IntToStr(NMod);
NMod=0;
LErr->Caption="Oujn6oK при обновлении: " + IntToStr(NErr);
NErr=0;

void fastcall TForml::FormCloseQuery(TObject *Sender, bool &CanClose)


if((Tablel->CachedUpdates) && (Tablel->UpdatesPending))
swi tch(Application->MessageBox(
"Остались несохраненные изменения! Сохранить?",
"Внимание!", MB_YESNOCANCEL + MB_ICONQUESTION))
case IDYES:
{ Tablel->ApplyUpdates();
Tablel->CommitUpdatesO ;
break;}
case IDCANCEL:
{ CanClose=false;
break; }
case IDNO:
{ Tablel->CancelUpdates();
break; }
100 Borland C++ Builder 6. Разработка приложений баз данных

Замечание
В этом разделе рассмотрены не все возможности режима кэширования изменений
присущие компоненту ТТаЫе. Например, при помощи метода RevertRecord можнс
отменить изменение только последней модифицированной записи, при помощ!
свойства UpdateObject - указать объект типа TUpdateSQL, который будет обновляв
соответствующий набор данных, и т. д. Вы можете сами ознакомиться со всем!
возможностями режима кэширования изменений, обратившись к разделу Using the BD
to cache updates справочной системы C++ Builder.

Создание таблиц «на лету»


Новую таблицу можно создать не только в процессе конструирования при помощи одно
го из визуальных инструментов (Database Desktop и тому подобных), но и «на лету», при
бегнув к различным средствам программирования. К этим средствам относятся языки про
граммирования, различного рода языки сценариев, язык запросов SQL. В механизме BDI
возможности по программному созданию новых таблиц собраны в компоненте ТТаЫе.
Начинать программное создание таблицы нужно с создания объекта ТТаЫе и, при необхо
димости, нового псевдонима. Новый псевдоним можно создать при помощи метода AddAlia
компонента TSession. Объект Session типа TSession создавать не нужно, это происходит ав
тематически, если в программе используется механизм BDE. Более подробно о компонент!
TSession будет рассказано далее. Для создания псевдонима базы данных типа STANDARL
(Paradox, dBASE, текстовые таблицы) проще пользоваться методом AddStandardAlias ком
понента TSession, а не методом AddAHas. Например, для создания псевдонима MyDatal базь
данных Paradox можно воспользоваться следующим кодом:
if ( ! S e s s i o n - > I s A l i a s ( " M y D a t a l " ) ) S e s s i o n - > A d d S t a n d a r d A l i a s (
"MyDatal", " D : \ \ M y D a t a l " , "PARADOX");
Первый аргумент метода AddStandardAlias задает имя псевдонима, второй аргумент со
держит путь и имя каталога, в котором будут храниться таблицы базы данных. Третий ар
гумент также имеет тип AnsiString и может содержать одно из трех значений: "PARADOX"
"DBASE", "ASCUDRV" (последнее предназначено для текстовых таблиц). Прежде чел
создать новый псевдоним, нужно проверить, нет ли уже псевдонима с указанным именем
Для этого следует воспользоваться методом IsAlias компонента TSession. В качестве един
ственного аргумента этот метод принимает строку, содержащую имя псевдонима. Есл!
такой псевдоним уже есть, метод возвращает значение true.
Для создания объекта типа ТТаЫе нужно воспользоваться оператором new. Как толью
объект станет не нужен, его следует удалить при помощи оператора delete:
ТТаЫе* Tablel= new T T a b l e ( t h i s ) ;

delete(Tablel);
Как только объект ТТаЫе будет создан, нужно задать параметры подключения и тиг
таблицы:
Tablel->DatabaseName="MyDatal";
Tablel->TableName="Itemsl.db";
Tablel->TableType=ttParadox;
На следующем этапе нужно создать определения полей и индексов. Для этого ис-
Глава 2. Использование механизма ВОЕ 101

пользуются свойства FieldDefs и IndexDefs компонента ТТаЫе. Прежде чем воспользо-


ваться этими свойствами, нужно для каждого из них вызвать метод Clear. Чтобы добавить
определение нового поля в коллекцию FieldDefs, нужно вызвать метод AddFieldDef, воз-
вращающий указатель на новый объект типа TFieldDef. Потом можно воспользоваться
свойствами этого объекта, чтобы задать для него имя, тип значений и другие атрибуты
поля. Ниже приводится определение поля Item:
NewFieldDef->Name="Item";
NewFieldDef->DataType=ftString;
NewFieldDef->Size=50;
NewFieldDef->Requi red=true;
Аналогично создается и определение нового индекса таблицы. Вначале вызывается
MQToaAddlndexDef, возвращающий указатель на новый объект типа TIndexDef. Далее этот
объект используется для задания свойств индекса. Например, следующий код определяет
первичный индекс на основе поля ItemNo:
NewIndexDef->Name="<Primary>";
NewlndexDef->FieIds="ItemNo";
NewIndexDef->Options=TIndexOptions() « ixPrimary « i x U n i q u e
Собственно создание таблицы в соответствии со всеми заданными свойствами объекта
ТТаЫе происходит после вызова его метода CreateTable. В листинге 2.7 приведен код соз-
дания таблицы Item, которая ранее использовалась в качестве примера.
Листинг 2.7. Код создания таблицы Item
Table* Tablel= new TTable(this);
if (!Session->IsAlias("MyDatal"»
Session->AddStandardAlias("MyDatal", "D:\\MyDatal", "PARADOX");
Tablel->DatabaseName="MyDatal";
Tablel->TableName="Itemsl.db";
Tablel->TableType=ttParadox;
Tablel->FieldDefs->Clear();
Tablel->IndexDefs->Clear();
TFieldDef* NewFieldDef=Tablel->FieldDefs->AddFieldDef();
NewFieldDef->Name="ItemNo";
NewFieldDef->DataType=ftAutoInc;
NewFieldDef=Tablel->FieldDefs->AddFieldDef();
NewFieldDef->Name="Item";
NewFieldDef->DataType=ftStг ing;
NewFieldDef->Size=50;
NewFieldDef->Requi red=true;
NewFieldDef=Tablel->FieldDefs->AddFieldDef();
NewFieldDef->Name="Category";
NewFieldDef->DataType=ftlnteger;
NewFieldDef->Requi red=true;
NewFieldDef=Tablel->FieldDefs->AddFieldDef();
NewFieldDef->Name="Description";
102 Borland C++ Builder 6. Разработка приложений баз данных

NewFieldDef->DataType=ftString;
NewFieldDef->Size=100;
NewFieldDef=Tablel->FieldDefs->AddFieldDef();
NewFieldDef->Name="Index";
NewFieldDef->DataType=ftString;
NewFieldDef->Size=5;
NewFieldDef->Requi red=true;
NewFieldDef=Tablel->FieldDefs->AddFieldDef();
NewFieldDef->Name="Price";
NewFieldDef->DataType=ftCuгrency;
NewFi eldDef->Required=true;
TIndexDef* NewIndexDef=Tablel->IndexDefs->Add!ndexDef();
NewIndexDef->Name="<Primary>";
NewIndexDef->Fields="ItemNo";
NewIndexDef->Options=TIndexOptions() ixllnique;
NewIndexDef=Tablel->IndexDefs->AddIndexDef();
NewIndexDef->Name="ItemNDX";
NewIndexDef->Fields="Item";
NewIndexDef->Options=TIndexOptions() ixUnique;
Tablel->CreateTable();
delete(Tablel);
В следующей главе будет продолжено рассмотрение возможностей механизма BDE
В частности, будут описаны объекты TSession, TDatabase, TQuery, основные компоненть
визуализации и управления данными (Data Controls), пользовательские наборы данныз
и другие вопросы.

Резюме
В этой главе мы рассмотрели основы использования механизма BDE для созда
ния приложений баз данных. Внимание прежде всего было уделено наборам данных
в частности, компоненту ТТаЫе. Были приведены различные примеры манипуляций
с наборами данных: открытие/закрытие набора данных, методы навигации, фильтра-
ции, поиска по заданному образцу, использование закладок. Кроме того, были описань
тесно связанные с компонентом ТТаЫе объекты TDataSource и TField. Объяснялось
как при помощи свойств, методов и событий объекта TDataSource можно определит!
состояние набора данных — находится ли он в режиме вставки записи, редактирова-
ния, просмотра и т.д.
Набор данных может содержать коллекцию динамических (Dynamic) или постоянных
(Persistent) полей. В главе была объяснена разница между этими коллекциями и рассказа-
но, как при помощи редактора полей можно создать набор постоянных полей. Описаны
основные свойства, методы и события объектов полей и приведены примеры их исполь-
зования. Объяснено, как можно создать и использовать вычисляемые (Calculated) поля
и поля подстановки (Lookup Fields).
Заключительная часть главы посвящена созданию связей между таблицами типа
главная — подчиненная (Master/Detail), использованию режима кэширования изменений,
а также созданию таблиц «налету», т.е. при помощи программного кода.
Использование механизма BDE
(продолжение)

Модули данных
•ф Компоненты TDataba.se и TSession
ф Элементы визуализации и управления данными
•ф Введение в локальный SQL
ф Компонент TQuery
Ф Компоненты TUpdateSQL и TBatchMove
Ф Клиентские наборы данных
ф Резюме

В
предыдущей главе рассматривались основы применения Borland Database Engine
(BDE) при разработке приложений баз данных. Внимание уделялось прежде всего ра-
боте с объектом ТТаЫе и главным особенностям использования наборов данных. Были
также кратко рассмотрены компоненты TDataSmirce и TField. В настоящей главе будет про-
должено изучение основных принципов построения приложений баз данных на основе ме-
ханизма BDE. В частности, будет рассматриваться другой наследник класса TDataSet — ком-
понент TQuery. Компонент TQuery имеет большое значение, так как именно он применяется
в большинстве случаев — в силу своей гибкости и удобства. Кроме исследования возможно-
стей компонента TQuery, в предлагаемой вашему вниманию главе будут кратко описаны осно-
вы языка локальный SQL, а также компоненты TSession, TDatabase, TUpdateSQL, TBatchMove,
модули данных (TDataModule) и клиентские наборы данных (TBDECHentDataSet).
Начнем, пожалуй, с модулей данных.

Модули данных
Модуль данных (компонент типа TDataModule) представляет собой не что иное, как
специального вида форму. «Специальность» модуля данных заключается в том, что его
можно видеть только при конструировании приложения, а в процессе выполнения модуль
данных на экране не отображается. В то время как форма служит контейнером для любых
компонентов, модуль данных может содержать только невизуальные компоненты (что,
впрочем, вполне естественно).
Обычно в модуль данных помещают компоненты для связи с базами данных. К этим
компонентам относятся уже рассматривавшиеся в предыдущей главе ТТаЫе, TDataSource,
а также и другие невизуальные компоненты — TQuery, TDatabase, TSession, TStoredProc
и т.д. Все эти невизуальные компоненты располагаются на вкладке Data Access, а также
104 Borland C++ Builder 6. Разработка приложений баз данных

на вкладках, связанных с определенным механизмом доступа (BDE, dbExpress, ADC


InterBase). Кроме того, в модулях данных можно размещать и другие невизуальные ком
поненты, не связанные с базами данных, например, TTimer, TOpenDialog, TSaveDialo^
и все остальные компоненты с вкладки Dialogs.
Очевидное преимущество такого подхода не только в том, что из формы удаляютс
загромождающие ее в режиме конструктора невизуальные компоненты, а из модуля фор
мы — десяток-другой связанных с ними методов. Главный смысл использования модуле]
данных заключаются в том, что с их помощью можно разделить логику управления ба
зами данных («бизнес-правила») и пользовательский интерфейс, размещаемый в форме
В простейшем случае это происходит следующим образом. В модуле данных размеща
ются компоненты ТТаЫе, TDataSource и тому подобные, и создаются методы, выполняю
щие различные действия непосредственно с наборами данных: манипуляции с записям!^
фильтрацию, поиск, обслуживание различных режимов работы и тому подобные опера
ции. Все элементы визуализации и управления данными размещаются в форме, подклю
чаясь к компоненту TDataSource, размещенному в модуле данных. Таким образом, модул
формы будет содержать только код, обслуживающий интерфейс пользователя, а при не
обходимости выполнять ориентированные на базу данных задачи, будут вызываться ее
ответствующие методы модуля данных. Такое разделение позволит менять бизнес-логик
приложения, модифицируя только модуль данных и не затрагивая интерфейс.
Для того чтобы создать новый модуль данных, нужно выбрать пункт Data Module ме
ню File \ New. Новый модуль данных в C++ Builder 6 снова выглядит так же, как и в С+
Builder 4 (см. рис. 3.1). В пятой версии модуль данных имел достаточно сложный ви/
включая панель иерархического представления компонентов, вкладку для размещени
объектов, а также вкладку для отображения схемы данных (Diagram). В шестой вереи
C++ Builder вкладка Diagram перекочевала в редактор кода — этой вкладкой теперь снаб
жен каждый модуль проекта, а иерархия объектов доступна через окно Object TreeViev
В связи с этим модуль данных и приобрел свой первоначальный простой вид.

Рис. 3.1. Модуль данных в режиме конструктора

Umtl cpp UniG.cpp j

lUnliltedl 3 J ii

li J
:
\Umt2 '-РРЛ 1 П|й
1 Modified 'Insert h ADiagretri./

Рис. З.2. Схема данных (Diagram), построенная для модуля данных приложения, де-
монстрирующего связь типа Master/Detail
Глава 3. Использование механизма BDE (продолжение) 105

Для того чтобы объекты и методы модуля данных были доступны в форме, во-
площающей пользовательский интерфейс, нужно в эту форму добавить заголовок
модуля данных. Проще всего это сделать при помощи команды Include Unit Hdr
меню File. Тогда обращаться к объектам и методам модуля данных из формы можно
при помощи конструкций вида ИмяМодуля->ИмяОбъекта_или_Метода, например,
DModl->Tablel. После этого можно размещать в модуле данных и настраивать соот-
ветствующим образом необходимые невизуальные компоненты. Если предполагается,
что модуль данных будет содержать большое количество объектов, представляющих
собой отдельные взаимосвязанные цепочки, нужно по мере создания таких цепочек
на вкладке Diagram редактора кода создавать соответствующую схему данных. На
рис. 3.2 изображена схема, описывающая связь типа Master/Detail между таблицами
Category и Items.
В отличие от формы, модуль данных имеет всего два события: OnCreate и OnDe-
stroy. Оба события имеют стандартный тип TNotifyEvent. Смысл применения этих
событий очевиден. Событие OnCreate используется для начальной настройки соеди-
нения, открытия наборов данных, включения фильтрации и тому подобных действий.
OnDestroy используется для противоположных целей: закрытия наборов данных, от-
ключения от базы данных и т.д.
В дальнейшем практически во всех более-менее сложных примерах приложений будут
использоваться модули данных.
'

Управление подключением к базе данных


при помощи объекта TDatabase
При использовании механизма BDE в информационных приложениях все свойства
подключения к конкретной базе данных и возможности манипулирования подключением
инкапсулированы в компоненте TDatabase. Объект типа TDatabase подключается к базе
данных и контролирует работу всех наборов данных, у которых в качестве свойства
DatabaseName указано его имя.
Чтобы воспользоваться интерфейсом, предоставляемым компонентом TDatabase,
необязательно явно создавать в приложении объект этого типа. Объект типа TDatabase
создается неявно при первом подключении какого-либо набора данных к базе данных.
Например, при подключении двух объектов типа ТТаЫе к базе данных MyData будет соз-
дан только один объект типа TDatabase, а при подключении в том же приложении тре-
тьего объекта типа ТТаЫе к другой базе данных, например BCDEMOS, будет создан еще
один объект типа TDatabase. Получить доступ к неявно созданному объекту TDatabase
можно при помощи свойства Database, которое объявлено в классе TDBDataSet.
Например, в результате выполнения следующей строки кода в элементе редактирования
Edit] будет содержаться количество подключенных к неявно созданному объекту типа
TDatabase наборов данных:
Editl->Text=Tablel->Database->DataSetCount;
В частности, если к базе данных MyData кроме объекта Tablel подключен еще
и объект ТаЫе2, то после выполнения предыдущей строки кода в Editl будет содер-
жаться значение 2.
106 Borland C++ Builder 6. Разработка приложений баз данных

Если вы хотите более полно контролировать свойства подключения к базе данных, BI


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

Основные свойства компонента TDatabase


Имеется несколько возможностей подключения компонента TDatabase к базе данны>
Самый простой вариант — подключение при помощи псевдонима (alias). Если для баз!
данных ранее был создан псевдоним (при помощи SQL Explorer или BDE Administrator), т
достаточно выбрать его имя из списка, прикрепленного к свойству AliasName. После этоп
нужно указать подходящий идентификатор в качестве значения свойства DatabaseName
Именно этот идентификатор будет появляться в списке свойства DatabaseName наборе
данных наряду с именами зарегистрированных псевдонимов.
При подключении к базе данных можно обойтись и без псевдонима. В этом случа^
нужно выбрать из списка, прикрепленного к свойству DriverName, наименование нужно
го драйвера. Например, STANDARD (для баз данных Paradox или dBASE), INTRBASE (дл:
баз данных InterBase) и т. д. После этого, как и в первом случае, нужно указать в свойств!
DatabaseName подходящее имя, при помощи которого можно будет подключать наборь
данных (ТТаЫе или TQuery) к нужной базе данных. Если свойство AliasName имело дс
этого какое-либо значение, то оно будет очищено, так как свойства AliasName и Driver
Name — взаимоисключающие. Это следует иметь в виду при программном измененш
значений этих свойств.
Если для задания подключения было использовано свойство DriverName, то нужно за
дать еще и базу данных, с которой должен быть связан объект типа TDatabase. Для этогс
служит свойство Params, представляющее собой список строк (тип TStrings). Свойстве
Params компонента базы данных может содержать список параметров и их значений
в виде ИмяПараметра=Значение. Состав параметров, а также набор допустимых для ни>
значений зависят от формата используемой базы данных (т. е. от указанного драйвера)
В любом случае, свойство Params может содержать те же самые параметры, что и псевдо-
ним соответствующего типа.
Способ задания базы данных при помощи параметров также зависит от указанно-
го драйвера. Ниже приведены примеры для форматов STANDARD (Paradox и dBASE)
INTRBASE и MSACCESS соответственно:
PATH=d:\MyData //STANDARD
SERVER NAME=d:\IBase\MyIBase.gdb //INTRBASE
DATABASE N A M E = d : \ D a t a \ N o r t h w i n d . m d b //MSACCESS
Обратите внимание на то, что слева и справа от знака равенства нет пробелов.
Кроме задания базы данных для подключения, свойство Params часто используется
и для указания других параметров подключения, например, имени пользователя и пароля
для доступа к данным, языкового драйвера и т. д. Особенно важно правильно настроить
специфические параметры подключения в случае использования драйверов ODBC или
SQL Links, являющихся частью BDE. О том, как правильно настроить параметры баз
данных типа SQL, можно узнать, обратившись к соответствующей документации, постав-
ляемой вместе с конкретным сервером базы данных.
Глава 3. Использование механизма ВОЕ (продолжение) 107

Рис. 3.3. Для заполнения свойства Params компонента TDatabase


можно использовать редактор списка значений (Value List editor)
Свойство Params, как и любое другое свойство типа TString, имеет прикрепленный
редактор списка значений (Value List editor). Получить доступ к этому редактору можно
при помощи кнопки построителя свойства Params. Центральную часть редактора зани-
мает стандартный список значений, состоящий из двух столбцов. В столбец Key нужно
вводить наименование параметров, а в столбец Value — соответствующие значения (см.
рис. 3.3). Воспользовавшись кнопкой Code Editor..., которая расположена в левой нижней
части окна редактора списка значений, можно перейти в окно редактора кода, где и ввести
необходимые параметры (в формате ИмяПараметра=Значение, по одному параметру
в строке, без разделителей).
Компонент TDatabase имеет свой собственный редактор, значительно облегчающий
настройку подключения к базе данных. Получить доступ к нему можно, дважды щел-
кнув мышкой на изображении объекта в форме. На рис. 3.4 изображен редактор компо-
нента TDatabase с настройками для подключения к базе данных photodatabase.gdb типа
INTRBASE, расположенной в каталоге Temp диска D:. В верхней части окна редактора
компонента TDatabase — элементы управления Name, Alias Name и Driver Name. Они
соответствуют свойствам компонента TDatabase DatabaseName, AliasName и DriverName.
Вы должны обязательно задать значение для свойства DatabaseName в элементе редакти-
рования Name (это значение будет использоваться объектами наборов данных в качестве
имени псевдонима базы данных). После этого вы должны задать или имя псевдонима
(элемент Alias Name), или наименование драйвера базы данных (элемент Driver Name),
памятуя о том, что эти свойства взаимоисключающие.

j-jMyOOB

Eammeler overrides
SERVER NAME'd\Temp\photodatabase gdb
USER NAME-SYSDBA
P*SSWORD=rrmsterkey

Г Login prampt
• 17 beep inactive connection

Рис. З.4. Редактор компонента TDatabase содержит настройки для подключения к базе
данных photodatabase.gdb типа INTRBASE
108 Borland C++ Builder 6. Разработка приложений баз данных

В центральной части редактора компонента TDatabase размещен элемент MEMO, c<


держащий список параметров подключения. Этот список, недвусмысленно называемы
переопределением параметров (Parameter overrides), соответствует свойству Params 061
екта типа TDatabase. Здесь вы можете задать любые параметры подключения (конечн
же, воспринимаемые соответствующим сервером базы данных). Хорошо, если вы у»
давно работаете с конкретным типом баз данных или у вас есть подробная документ:
ция о возможных параметрах подключения. Если это не так, то некоторую информ;
цию вы можете получить при помощи редактора компонента базы данных (компонет
TDatabase). Если вы уже указали драйвер базы данных в элементе Driver Name, то можн
воспользоваться кнопкой Defaults. После нажатия этой кнопки элемент Parameter oven
ides будет отображать список всех параметров подключения к базе данных выбранног
типа, а также принятые для них значения по умолчанию. Часть этих параметров можн
переопределить по своему усмотрению (например, задать имя базы данных и пароль
а остальные можно или оставить неизменными, или удалить их из списка, чтобы не засс
рять элемент лишней информацией.
Окно редактора компонента базы данных содержит еще два элемента. Это флажк
Login prompt и Keep inactive connection. Значения этих флажков отображаются на значе
ния свойств логического типа LoginPrompt и KeepConnection соответственно. Свойств
LoginPrompt указывает, будет ли отображаться диалоговое окно для ввода имени пользе
вателя и пароля непосредственно перед подключением к базе данных. Значение по умол
чанию для этого свойства — true, однако на период разработки и отладки приложени
лучше задать значение false, чтобы избавить себя от лишних хлопот. Если для свойств
LoginPrompt задано значение false, а подключение к базе данных требует обязательно:
авторизации, то вы должны указать имя пользователя и пароль в свойстве Params объект;
TDatabase (иначе вы не сможете установить связь с базой данных).
Выше уже говорилось о том, что в контексте одного объекта типа TDatabase може'
быть открыто несколько наборов данных. Открыть любой набор данных в контексте объ
екта TDatabase можно лишь в том случае, если этот объект связан с базой данных (т. е
открыт сеанс связи с базой данных). За состояние сеанса связи объекта TDatabase с базо!
данных отвечает свойство Connected. Если свойство Connected имеет значение true — се
анс связи с БД открыт, а если задать для этого свойства значение false, то связь с БД об
рывается. Кроме того, открыть и закрыть сеанс связи можно при помощи методов Opei
и Close соответственно.
Как только вы тем или иным способом открыли сеанс связи объекта TDatabast
с базой данных, вы можете в контексте этого объекта открыть любое количество на
боров данных (связав с объектом БД свойство DatabaseName набора данных). Бел}
вы закроете все наборы данных, связанные с конкретным объектом БД, то возникав!
вполне естественный вопрос: а что же делать с сеансом связи? Оставить его откры-
тым или закрыть? На этот вопрос отвечает свойство логического типа KeepConnection
Если оно имеет значение true, сеанс связи остается открытым, даже если нет ни одногс
открытого (активного) набора данных. В противном случае при закрытии всех связан-
ных с объектом TDatabase наборов данных сеанс связи также будет закрыт. Это може!
несколько сэкономить ресурсы, однако расходы при повторном открытии сеанса связи
могут намного превысить такую экономию.
Компонент TDatabase имеет еще несколько полезных свойств, о которых хотелось бы
упомянуть. Свойства DataSets и DataSetCount можно использовать для получения досту-
па к активным (т. е. открытым в данный момент) наборам данных, связанным с объектом
Глава 3. Использование механизма ВОЕ (продолжение) 109

TDatabase. Оба свойства предназначены только для чтения. Свойство DataSetCount содер-
жит количество активных наборов данных, a DataSets представляет собой индексирован-
ный массив активных наборов данных (объектов типа TDBDataSei). Ниже приведен не-
большой отрывок кода, иллюстрирующий применение свойств DataSetCount и DataSets:
Memol->Clear();
for (int i = 0; i < Databasel->DataSetCount; i++)
{
TTabTe* MyTable;
MyTable=dynamic_cast<TTable *>(Databasel->DataSets[i]);
if(MyTable) Memol->Lines->Add(MyTable->TableName);
}
В этом отрывке кода предполагается, что в приложении задействован объект ТМето
(Memo!), объект TDatabase (Database!) и несколько подключенных к нему активных объ-
ектов ТТаЫе. Сеанс связи с базой данных предполагается открытым. Отрывок начинается
с оператора, очищающего содержимое элемента Memol. Затем осуществляется перебор
всех открытых наборов данных, подключенных к объекту Database!. В теле цикла каж-
дый из активных наборов данных приводится к типу ТТаЫе* (т. е. проверяется, является
ли набор данных таблицей). В случае успеха, к набору строк элемента Memol добавляется
наименование таблицы, к которой подключен набор данных.
Свойство только для чтения Locale позволяет определить языковой драйвер, используемый
для объекта TDatabase. Следующие два свойства также предназначены только для чтения.
При помощи свойства логического типа IsSQLBased можно определить, подключен ли объект
TDatabase к базе данных при помощи драйверов ODBC или SQL Links. Свойство InTransaction
также имеет логический тип. Значение true этого свойства означает, что в данный момент
в контексте объекта базы данных выполняется транзакция. Это свойство необходимо обяза-
тельно проверить, перед тем как запустить очередную транзакцию.
Есть еще одно свойство, которое может пригодиться при разработке собственных
компонентов с использованием объекта TDatabase или если вы применяете в своих при-
ложениях модуль данных, содержащий объект TDatabase и предварительно сохраненный
в депозитарии объектов. Это свойство логического типа HandleShared. Чтобы избежать
конфликта имен объектов TDatabase во всех упомянутых выше случаях, следует задать
для этого свойства значение true.

Транзакции и ВОЕ
Транзакция {Transaction) — это целостный набор действий по модификации данных
одной или нескольких таблиц базы данных (или нескольких БД). Под целостным подраз-
умевается такой набор действий, который или в целом успешно завершается, или целиком
отменяется. Другими словами, если при выполнении действий, входящих в состав тран-
закции, произошла ошибка, то набор таблиц, которые должны были подвергнуться воз-
действию, остается в том состоянии, в котором они были до начала транзакции. Таблицы
подвергаются модификации только в том случае, если все действия, входящие в транза-
кцию, завершаются успешно. Стандартный и понятный пример использования транзак-
ций — перевод денег с одного счета на другой. Если при переводе денег произошел какой-
нибудь сбой, то может случиться, что деньги будут сняты с одного счета, а на другой счет
переведены не будут (исчезновение денег). В результате применения транзакций деньги
или будут успешно переведены на другой счет (удачная транзакция), или останутся на
110 Borland C++ Builder 6. Разработка приложений баз данных

первом счете (неудачная транзакция). В любом случае информация базы данных останет-
ся целостной (непротиворечивой).
Транзакции должны удовлетворять четырем основным свойствам. Эти свойства на-
зываются ЛС/£>-свойствами (на жаргоне— кислотными), по первым буквам понятий
характеризующих эти свойства. Ниже приведено определение для этих свойств.
Q Целостность (Atomicity). Или все модификации таблиц в рамках отдельной транзак-
ции завершаются, или база данных остается в том состоянии, в котором она находи-
лась до транзакции.
Q Устойчивость (Consistency). Все модификации таблиц базы данных корректны и ост-
авляют базу данных в устойчивом состоянии.
Q Изоляция (Isolation). Результаты выполнения любой незаконченной (неподтвержден-
ной) транзакции не видны другим транзакциям.
LI Прочность (Durability). Подтверждение обновления записей не должно зависеть от каких-
либо сбоев, в том числе сбоев оборудования, сбоев при передаче данных в сети и т. д.
Механизм BDE обеспечивает автоматическое управление транзакциями на уровне
отдельной записи. Это означает, что обновление модифицированной записи таблицы про-
изойдет только в случае успеха, иначе запись останется неизменной. Это предотвращает
изменение значений одних полей записи, если другие поля этой же записи в силу тех или
иных причин модифицированы не будут. Если такой уровень использования транзакций
вас не устраивает (например, вам обязательно нужно одновременно обновить несколько
записей, принадлежащих нескольким таблицам), вы должны воспользоваться возможно-
стями управления явными транзакциями, имеющимися у компонента TDatabase.
Для управления явными транзакциями (далее— просто транзакциями) компонент
TDatabase имеет три метода и одно свойство. Последовательность действий, входящих в одну
транзакцию, начинается с вызова метода StartTransaction. Заканчивается транзакция или вы-
зовом метода Commit для подтверждения всех изменений при транзакции, или вызовом мето-
да Rollback— для отмены произведенных транзакцией действий в случае неудачи. Обычно
механизм управления транзакциями применяется совместно с блоком try...catch. Происходит
это следующим образом. Вначале вызывается метод StartTransaction объекта TDatabase.
Далее в блоке оператора try предпринимается попытка модифицировать информацию в базе
данных. В случае успеха вызывается метод Commit для подтверждения сделанных изменений.
Если что-то пошло не так, в блоке оператора catch вызывается метод Rollback для отмены всех
сделанных изменений. Ниже приведен отрывок кода, в котором транзакция используется для
сохранения сделанных изменений в режиме кэширования изменений (cached updates):
Databasel->StartTransaction() ;
try
{
Tablel->ApplyUpdates() ;
Databasel->Commit() ;
}
catch (...)
{
Databasel->Rollback() ;
throw;
}
Tablel->Commi tUpdatesQ ;
Глава 3. Использование механизма ВОЕ (продолжение)

Обратите внимание на то, что после вызова метода Rollback при помощи метода th-
row возбуждается исключение. Это нужно для того, чтобы предотвратить вызов метода
CommitUpdates, очищающего буфер кэшированных изменений.
На выполнение транзакций влияет значение свойства Tramlsolation объекта TDatabase.
Это свойство может принимать одно из трех значений типа TTranslsolation. Каждое из
этих значений определяет уровень изоляции текущей транзакции:
О tiDirtyRead— наименьший уровень изоляции. Разрешается чтение неподтвержденных
изменений, сделанных текущей транзакцией.
Q tiReadCommitted — • значение по умолчанию для баз данных в формате SQL.
Разрешается чтение только подтвержденных (committed) изменений.
Q tiRepeatableRead'— наиболее изолированный уровень выполнения транзакций.
Транзакции доступна информация, известная на момент начала выполнения транзакции.
Если вы применяете транзакции для модификации информации в базе данных типа
STANDARD (Paradox, dBASE), то допустим только уровень изоляции tiDirtyRead. Для баз
данных текстового типа (ASCIIDRV) недопустимо использовать транзакции вообще.

Получение имен таблиц и полей базы данных


Получить информацию об именах таблиц базы данных и о полях каждой из таблиц
можно при помощи методов GetTableNames и GetFieldNames компонента TDatabase. Эти
методы объявлены следующим образом:
void fastcall GetTableNames(Classes::TStrings* List,
bool SystemTables = false);
void fastcall GetFieldNames(const AnsiString TableName,
Classes::TStrings* List);
Метод GetTableNames принимает в качестве аргумента указатель на список строк
(TStrings*). В результате работы метода объект TStrings будет содержать список имен
таблиц, содержащихся в базе данных, к которой подключен объект TDatabase. При этом
необязательно открывать сеанс связи (т. е. свойство Connected может иметь значение
false). Второй аргумент логического типа SystemTables указывать необязательно. Если за-
дать для этого аргумента значение true, то в список таблиц базы данных будут включаться
системные таблицы. В некоторых базах данных (например, в Microsoft Access, Interbase
и др.) автоматически создаются такие таблицы для хранения внутренней (системной)
информации. Если аргумент SystemTables не указывать, то системные таблицы в список
включаться не будут.
Метод GetFieldNames принимает два аргумента. Первый аргумент (типа AnsiString)
должен содержать имя таблицы. Второй аргумент имеет тип TString* (указатель на список
строк). Этот список строк будет после выполнения метода содержать перечень всех полей
указанной таблицы.
Чтобы проверить, как работают методы GetTableNames и GetFieldNames, можно
создать небольшое тестовое приложение. Откройте новый проект и на пустую форму
приложения добавьте два списка (объекта типа TListView). Далее создайте новый модуль
данных (пункт меню File->New->Data Module) и поместите на него один объект типа
TDatabase. Оставьте имена всех объектов без изменения, а для модуля данных задайте
имя DModl (свойство Name). Объект Database! подключите к псевдониму MyData (или
любым другим образом задайте подключение к какой-нибудь базе данных). Чтобы объект
112 Borland C++ Builder 6. Разработка приложений баз данных

Database! был доступен в форме Forml, нужно добавить вес модуль файл заголов]
модуля данных. Для этого активизируйте форму и выберите пункт Include Unit Hdr MI
ню File. В открывшемся окне выберите имя модуля, соответствующего модулю даннь
(Unit2) и нажмите кнопку ОК. В результате в модуль формы (Unitl) будет добавлена erpi
ка ^include "Uni2.h". Впрочем, то же самое можно было сделать и вручную.
Список ListViewl, расположенный в левой части формы, будет включать все таблиц
базы данных. Заполнить этот список значениями можно при открытии формы, в обрабо
чике ее события OnCreate:
void fastcall TForml::FormCreate(TObject *Sender)
{
DModl->Databasel->GetTableNames(ListBoxl->Iterns);
ListBoxl->ItemIndex=0;
DModl->Databasel->GetFieldNames(
Li stBoxl->Iterns->Strings[ListBoxl->ItemIndex],
ListBox2->Items);
}
В первой строке обработчика события вызывается метод GetTableNames, заполняй
щий свойство Items списка ListBoxl именами таблиц базы данных, к которой подключе
объект Database!'. Далее активизируется первый элемент списка (с индексом 0) и вызывг
ется метод GetFieldNames, заполняющий свойство Items списка ListView2 именами поле
таблицы, имя которой указано в первом аргументе (т. е. таблицы, чье имя является первы
в списке). Обратите внимание на то, что при обращении к объекту Database! нужно чере
модификатор -> указывать имя модуля данных DModl.

Category EmployeeNo
LNome
ЯЕЕКЗЭИИИИИИИИИЯИИ FName
Suppliers MNome
HireDtfe
FireDeta •
Description
Photo
Birthday Т

Рис. З.5. Приложение для иллюстрации применения методов


GetTableNames и GetFieldNames
На следующем шаге необходимо обеспечить возможность обновлять список ListView.
в ответ на выбор таблицы в списке ListViewl. Для этого можно воспользоваться событи
ем OnClick компонента TListView. Для списка это событие происходит не только в отве1
на щелчок мышкой на одном из его элементов, но и при переходе по элементам списк;
с помощью клавиш управления курсором. Создайте обработчик события OnClick да
объекта ListViewl и введите в его тело следующий код:
void fastcall TForml::ListBoxlClick(TObject *Sender)
{
DModl->Databasel->GetFieldNames(
ListBoxl->Items->Strings[ListBoxl->ItemIndex] ,
ListBox2->Items) ;
Глава 3. Использование механизма BDE (продолжение) 113

Откомпилируйте приложение, запустите его на выполнение и посмотрите, как это все


работает (см. рис. 3.5).
Замечание
На самом деле приложение сразу работать не будет, а при его запуске будет возбуж-
дена исключительная ситуация с сообщением самого любимого программистами типа:
«Access violation at address 00407C59 in module 'PROJECT! F.XE'». Все дело в том, что при
создании формы в обработчике события OnCreate используется обращение к модулю
данных DModl. Однако на момент создания формы объект модуля данных еще не создан
и поэтому недоступен. Чтобы избежать этой ошибки, нужно поменять очередность соз-
дания объектов формы и модуля данных. Для этого выберите пункт Options меню Project.
В окне Project Options перейдите на вкладку Forms. В левой части вкладки расположен
список автоматически создаваемых форм (Auto-create forms). Вверху списка располо-
жена Form? - это значит, что первой будет создаваться именно форма FormJ. Чтобы по-
менять очередность создания, можно перетащить при помощи мышки наименование
модуля данных DModl в верхнюю часть списка.
Того же результата можно добиться при помощи исходного кода проекта. Выберите
в меню Project пункт View Source. В окне редактора кода появится вкладка модуля^проекта
Project).срр. В этом модуле расположены вызовы методов CreateForm объекта Application.
Чтобы решить проблему, нужно просто поменять местами вызовы методов:
Application->CreateForm( classid(TDModl), &DModl);
Appl1cation->CreateForm( classld(TForml), &Forml);

События компонента TDatabase


Компонент TDatabase имеет всего пять событий, и все они имеют отношение
к открытию или закрытию сеанса связи с базой данных. Непосредственно в классе-
е TDatabase объявлено одно событие — OnLogin. События BeforeConnect, AfterConnect,
BeforeDisconnect и AfterDisconnect объявлены в классе TCustomConnection, который яв-
ляется непосредственным предком класса TDatabase. Последние четыре события имеют
стандартный тип TNotifyEvent, а событие OnLogin объявлено следующим образом:
typedef void fastcall ( closure *TDatabaseLoginEvent)
(TDatabase* Database, Classes::TStrings* LoginParams);
Событие OnLogin происходит при подключении приложения к базе данных, но только
в том случае, если свойство LoginPrompt имеет значение true и подключение к базе данных
требует авторизации (указания имени пользователя и пароля). Это событие обычно приме-
няется для задания специфических действий при подключении к базе данных, чаще всего
для задания, имени пользователя и пароля, которые переопределяют соответствующие зна-
чения, указанные в свойстве Params объекта TDatabase. Для этого используется аргумент
LoginParams, имеющий тип TStrings. Ниже приведен код, который может использоваться
в обработчике события OnLogin для подключения к базе данных типа INTRBASE:
void fastcall TForml::DatabaselLogin(TDatabase *Database,
TStrings *LoginParams)
{
LoginParams->Add("USER NAME=SYSDBA");
LoginParams->Add("PASSWORD=masterkey");
114 Borland C++ Builder 6. Разработка приложений баз данных

Если обработчик события OnLogin не задан, то сведения об имени пользователя


и пароле берутся из свойства Params или из диалогового окна, выдаваемого на экран
перед подключением к базе данных.
События BeforeConnect и AfterConnect происходят непосредственно перед открытием
сеанса связи и соответственно сразу после его открытия. Этими событиями можно восполь-
зоваться для каких-либо действий, специфичных для данного приложения. Например, можно
организовать в приложении ввод информации о подключении к конкретному типу БД, а затем
в обработчике события BeforeConnect соответствующим образом настроить свойство Params
объекта TDatabase. В следующем отрывке кода непосредственно перед подключением к базе
данных задается ее имя и путь, а также имя пользователя и пароль. Эта информация берется
из соответствующих переменных, куда она вводится пользователем.
void fastcall TForml::DatabaselBeforeConnect(TObject *Sender)
{
Databasel->Params->Clear();
Databasel->Params->Add("SERVER NAME=" + DBName);
Databasel->Params->Add("USER NAME=" + UName);
Databasel->Params->Add("PASSWORD=" + PWord);
}
Аналогичным образом используются и события BeforeDisconnect и After Disconnect,
с той лишь разницей, что первое из них происходит непосредственно перед разрывом
сеанса связи, а второе — сразу после того, как сеанс связи будет закрыт.

Управление сеансами связи при помощи


компонента TSession
Точно так же, как компонент TDatabase определяет общий фон для всех активных в его
контексте наборов данных, компонент TSession определяет набор свойств, задающих об-
щий фон для всех сеансов связи (объектов TDatabase), открытых в его контексте. Объект
типа TSession, или сессию можно использовать не только для задания свойств, определя-
ющих поведение создаваемых неявно объектов базы данных (TDatabase). С его помощью
можно также:
Q получать информацию об объектах, используемых в контексте текущей сессии (псев-
донимах, базах данных, наборах данных, полях таблиц и т. д.);
Q открывать или закрывать на уровне сессии все фигурирующие в ее контексте объекты
баз данных или наборов данных;
G обрабатывать события OnPassword и OnStartUp компонента TSession;
Q устанавливать для таблиц Paradox сетевые (NetFileDir) и локальные (PrivateDir) ди-
ректории;
Q задавать пароль для доступа к таблицам Paradox, закрытым паролем.
Обычно нет необходимости явно создавать объект TSession. Объект этого типа соз-
дается неявно при использовании в приложении любого из объектов, требующих под-
ключения к базе данных (объектов TDatabase или одного из наборов данных). Вы можете
сами в этом убедиться, разместив на форме или модуле данных, например, объект ТТаЫе
или TDatabase. В окне Object TreeView появится узел Default, соответствующий неявно
Глава 3. Использование механизма BDE (продолжение) 115

созданному объекту TSession. Обратите внимание, что если вы добавили на форму или
модуль данных один из объектов набора данных (например, ТТаЫе), то неявно создается
и связывается с Session и объект типа TDatabase. Доступ к неявно созданному объекту
TSession можно получить через глобальную переменную Session.
Если есть необходимость в настройке свойств объекта TSession в режиме конструкто-
ра, то нужно создать объект явно, поместив его с вкладки BDE или на форму, или в модуль
данных. В большинстве случаев в приложении достаточно одного объекта TSession, соз-
данного явно или неявно. Однако иногда бывает необходимо использовать несколько та-
ких объектов, например, если вы применяете несколько наборов объектов баз данных, для
которых предусмотрены разные глобальные настройки поведения. Кроме того, если вы
используете в своем приложении несколько потоков (Threads), то необходимо для каждого
из потоков использовать свой объект TSession.
Далее будет рассмотрено несколько задач, решаемых при помощи компонента
TSession.

Свойства и события компонента TSession


Если вы явно создаете в приложении объект типа TSession, то нужно задать для него
имя (свойство Name) и имя сессии (свойство SessionName). Имя объекта используется для
ссылки на него в программном коде, а имя сессии — для ассоциации с сессией явно или
неявно создаваемых объектов баз данных. Явно создаваемые объекты баз данных связы-
ваются с конкретной сессией при помощи свойства SessionName. Список этого свойства
кроме имен явно созданных объектов сессий содержит и имя сессии, создаваемой по
умолчанию (Default).
Неявно создаваемые объекты баз данных автоматически связываются с сессией по
умолчанию. Например, если разместить на форме объект ТТаЫе, автоматически будет
создан объект Session, представляющий сессию Default. Этот объект будет создан в любом
случае, даже если форма уже содержит явно созданные объекты типа TSession. Также авто-
матически будет создан неявный объект базы данных, связанный с сессией по умолчанию.
И, наконец, объект типа ТТаЫе связывается с неявно созданным объектом базы данных.
Все эти связи между объектами можно наблюдать в окне Object TreeView. Если вы хотите
перевести объект набора данных под управление явно созданной сессии, то следует задать
имя этой сессии в качестве значения свойства SessionName набора данных.
Компонент TSession имеет одно интересное свойство, связанное с именованием соз-
даваемой сессии. Это свойство логического T:vm& AutoSessionName. Если задать для этого
свойства значение true, то BDE будет автоматически генерировать для объекта уникальное
имя сессии (значение свойства SessionName). Имя сессии генерируется на основе имени
объекта, после которого ставится символ подчеркивания «_» и цифра. Например, для объ-
екта Session! будет сгенерировано имя сессии наподобие Session!_1. Основное назначе-
ние автоматически генерируемых имен сессий в том, чтобы гарантировать разработчику
многопоточных приложений, что создаваемые в разных потоках объекты TSession не вы-
зовут конфликт имен.
После того как для свойства AutoSessionName устанавливается значение true, свой-
ство SessionName всех объектов наборов данных (наследников TDataSet) или баз данных
(TDatabase) в качестве значения получает автоматически сгенерированное имя этой сес-
сии. Другими словами, все наборы данных переходят под управление той сессии, свойство
которой AutoSessionName было установлено в значение true. Отсюда вытекает три вполне
естественных ограничения на использование этого свойства. Нельзя разрешить автомати-
116 Borland C++ Builder 6. Разработка приложений баз данных

ческую генерацию имен, если форма или модуль данных содержит более одного объект
TSession (неявно создаваемая сессия Default при необходимости автоматически удаляет
ся). С другой стороны, нельзя добавить на форму или в модуль данных объект TSession
если там уже есть объект этого типа и для него свойство AutoSessionName имеет значени
true. Последнее ограничение состоит в том, что в процессе выполнения программы вы н
сможете задать значение для свойства SessionName, если для сессии разрешена автомати
ческая генерация имен.
Свойство KeepConnections логического типа задает значение для свойств;
KeepConnection всех неявно создаваемых в контексте текущей сессии объектов баз дан
ных. Свойство KeepConnection компонента TDatabase уже подробно рассматривалос]
ранее, и здесь мы останавливаться на этом не будем. SQLHourGlass — еще одно свойств(
логического типа, задающее поведение выполняющихся в контексте сессии объектов ба:
данных. Смысл свойства, я думаю, понятен из его названия. Если для него задано значе
ние true, курсор мышки примет вид песочных часов SQL, пока BDE ожидает завершеню
выполнения SQL-сервером запрошенных у него действий. Свойство можно устанавливай
только до открытия сессии.
Свойство NetFileDir специфично для таблиц Paradox и предназначено для храненш
пути к файлу PDOXUSRS.NET, который BDE использует для управления сетевым до-
ступом к таблицам Paradox. Если путь к этому файлу не задан, то он берется из настроек
драйвера Paradox. Свойство PrivatDir задает каталог, в котором будут храниться создава-
емые BDE при обработке таблиц временные файлы. По умолчанию используется каталог
BIN той папки, в которую был установлен C++ Builder.
После того как все необходимые свойства для сессии заданы, ее можно открыть, за-
дав для свойства Active значение true. Как всегда в таких случаях, для тех же целей мож-
но воспользоваться методом Open. При открытии сессии инициализируются свойства
PrivateDir, NetFileDir, если они не были настроены вручную в режиме конструктора
и ConfigMode. Для закрытия сессии можно воспользоваться свойством Active, задав для
него значение false, или вызвав метод Close. После закрытия сессии деактивируются так-
же и все имеющие отношение к ней сеансы связи с базами данных и, как следствие, все
выполняющиеся в ее контексте наборы данных. После повторного открытия сессии вам
придется самостоятельно активировать все нужные наборы данных. Кстати, самим от-
крывать сессию не обязательно. Достаточно активизировать один из наборов данных или
объектов баз данных, связанных с сессией, и она автоматически будет открыта.
И в заключение обзора свойств компонента TSession следует упомянуть о свойствах
только для чтения Databases и DatabaseCount. Эта парочка свойств, как и следует из их
названия, позволяет осуществлять итерацию по всем открытым в контексте текущей сес-
сии объектам баз данных. Ниже приведен пример кода, позволяющий активизировать все
объекты базы данных, связанных с сессией Sessionl при повторном ее открытии:

Sessionl->Active=true;
for (int i = 0; i < Sessionl->DatabaseCount; i++)
Sessionl->Databases[i]->Active = true;

Конечно же, вам придется позаботиться еще и о том, чтобы открыть все связанные с сесси-
ей (или, что то же самое, со всеми объектами баз данных текущей сессии) наборы данных.
Компонент TSession имеет всего два события. Событие OnStartup происходит при
переключении свойства Active из значения false в значение true. Обработчиком этого
Глава 3. Использование механизма ВОЕ (продолжение) 117

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


ния, например, с информационными целями. Событие OnPassword используется при
подключении к таблицам Paradox, закрытым паролем. Это событие происходит, когда
BDE пытается в первый раз подключиться к закрытым паролем таблицам Paradox,
если пароль не был предоставлен другими средствами. В этом случае в обработчике
события OnPassword необходимо задать подходящий пароль (например, при помощи
вызова метода AddPaSsword объекта TSessiori). Кроме стандартного аргумента Sender
типа TObject, обработчик события OnPassword получает еще и аргумент логического
типа Continue. Можно задать для этого аргумента значение true, в результате чего добав-
ленный пароль будет доступен для BDE, иначе пароль будет отвергнут. Если обработчик
события OnPassword отсутствует, BDE выдает стандартное диалоговое окно для ввода
пароля. Более подробно о работе с таблицами Paradox и dBASE, которые закрыты паро-
лями, будет рассказано в следующем разделе.
Замечание
В тело обработчика события OnPassword передается аргумент Sender, указывающий
на объект типа TObject. К сожалению, этот аргумент указывает на объект TSess/on, а не
на объект набора данных, при помощи которого производится попытка подключиться
к таблице базы данных.

Использование паролей для доступа


к таблицам Paradox и dBASE
Таблицы Paradox и dBASE можно закрыть паролями. Для этого можно воспользо-
ваться SQL Explorer или Database Desktop. Например, чтобы для таблицы Paradox за-
дать пароль при помощи утилиты Database Desktop, следует открыть нужную таблицу
в режиме изменения структуры (пункт Restructure меню Table) и выбрать позицию
Password Security из комбинированного списка Table properties. Если пароль для табли-
цы еще не был установлен, то нужно нажать кнопку Define, иначе — кнопку Modify.
В первом случае в открывшемся диалоговом окне вам нужно указать пароль в поле вво-
да Master pass word и затем повторить его в поле ввода Verify master password. Во втором
случае вы можете воспользоваться или кнопкой Delete для удаления пароля, или кноп-
кой Change для его изменения. При помощи кнопки Auxiliary Passwords можно задать
права доступа более изощренно — например, права доступа к отдельным полям, права
на удаление, вставку записей, и т.д.

Рис. 3.6. Стандартное диалоговое окно для ввода пароля


После этого при использовании таблицы нужно указывать один или несколько паролей,
на основании которых и будет определяться набор прав, предоставляемых пользователю.
Компонент TSession содержит набор методов, позволяющих управлять наборами паролей
для доступа к таблицам Paradox и dBASE. Механизм управления паролями основан на
118 Borland C++ Builder 6. Разработка приложений баз данных

том, что объекты типа TSession могут хранить в своем внутреннем буфере набор паролей
При попытке подключения к таблице, закрытой паролем, BDE может получить сообще
ние о недостаточных правах доступа. В этом случае для подключения к таблице може'
использоваться один из паролей, хранящихся в сессии, в контексте которой работает от
крываемый набор данных. Если пароля для таблицы в буфере объекта TSession нет, вы
дается стандартное диалоговое окно для ввода пароля (см. рис. 4.6). Используя это окно
можно добавить новый пароль (кнопка Add), удалить указанный пароль (кнопка Remove)
а также удалить все пароли, хранящиеся в текущем объекте TSession (Remove all). Для до-
бавления пароля и задания дальнейшей реакции BDE можно воспользоваться событием
OnPassword, которое упоминалось в предыдущем разделе.
Простейший способ добавить пароль к текущей сессии - - использовать метод
AddPassword. В качестве аргумента этот метод принимает строку AnsiString, содержащую
пароль. Метод AddPassword за один вызов добавляет только один пароль, но этот метод
можно вызывать сколько угодно раз. Допустим, что в контексте сессии Session! выпол-
няется четыре набора данных. Все эти наборы (объекты ТТаЫе) подключены к таблицам,
для доступа к которым требуется указать пароли Passl, Pass2, Pass3 и Pass4 соответствен-
но. Тогда, чтобы успешно подключиться к таблицам, а также отменить выдачу стандарт-
ного диалогового окна для ввода пароля, в тело обработчика события OnPassword можно
вставить следующий код:
void fastcall TForml::SessionlPassword(TObject *Sender, bool &Continue)
{
Sessionl->AddPassword("Passl")
Sessionl->AddPassword("Pass2")
Sessionl->AddPassword("Pass3")
Sessionl->AddPassword("Pass4")
Continue=true;
}
Если в предыдущем отрывке кода опустить последний оператор, присваивающий зна-
чение true аргументу Continue, то соответствующая таблица открыта не будет, и на экран
будет выдано сообщение о недостатке прав для подключения.
Метод A ddPassword можно использовать не только в обработчике события OnPassword.
Например, можно собственными средствами организовать ввод имени и пароля пользова-
теля при запуске приложения, а затем, в зависимости от введенных значений, один или
несколько раз вызвать метод AddPassword для добавления набора паролей, задающих
уровень доступа пользователя к таблицам базы данных.
По вполне понятным причинам можно не только добавлять пароли во внутренний
буфер объекта типа TSession, но и удалять их. Делать это можно при помощи методов
RemovePassword и RemoveAllPasswords. Метод Remove? assword принимает в качестве
аргумента строку AnsiString, содержащую пароль из буфера объекта TSession. Пароль
должен был быть добавлен до этого при помощи метода AddPassword. При помощи
метода RemoveAllPasswords можно удалить все добавленные ранее пароли, очистив тем
самым внутренний буфер объекта TSession. Оба метода могут использоваться при пере-
ключении приложения с одного пользователя на другого без перезагрузки приложения.
В этом случае, удалив одну группу паролей, соответствующих первому пользователю,
можно добавить другую группу паролей, определяющих права на доступ к информации
второго пользователя.
Глава 3. Использование механизма BDE (продолжение) 119

Есть еще один полезный метод, который можно с успехом использовать в своих при-
ложениях. Это метод GetPassword, не имеющий аргументов и возвращающий значение
логического типа. Вызов его приводит к возбуждению события OnPassword сессии. Если
для события определен обработчик, то выполняется заложенная в него логика, иначе на
экране отображается стандартное диалоговое окно для ввода пароля. В последнем слу-
чае метод GetPassword возвращает значение true, если пользователь нажал кнопку ОК,
и значение false, если пользователь нажал Cancel. Услугами метода GetPassword можно
воспользоваться для того, чтобы добавить группу паролей в буфер объекта TSession сразу
после определения пользователя, не дожидаясь реального подключения к таблицам.

Управление сеансами связи


Компонент TSession содержит достаточно большую группу методов, позволяющих ма-
нипулировать сеансами связи с базами данных. Часть этих методов предназначена для ра-
боты с псевдонимами и драйверами, часть — управляет сеансами связи с базами данных.
При помощи методов AddAlias viAddStandardAlias к списку псевдонимов, зарегистри-
рованных (например, при помощи BDE Administrator) и доступных для любой сессии
BDE, можно добавить новый псевдоним, доступный только в рамках текущей сессии.
Метод AddA lias объявлен следующим образом:
void fastcall AddAlias(const AnsiString Name,
const AnsiString Driver, Classes::TStrings* List);
Аргументы Name и Driver задают, соответственно, имя псевдонима и использующийся
драйвер базы данных. Третий аргумент метода AddAlias имеет тип указателя на абстракт-
ный класс TStrings. В приложении вместо класса TStrings следует использовать один из
его наследников — например, класс TStringList. При помощи аргумента List можно за-
дать список параметров для нового псевдонима, например, имя и путь к базе данных, имя
пользователя и т.д.
Ниже приведен отрывок кода, используемый для создания нового псевдонима для
базы данных employee.gdb типа INTRBASE. Вначале создается экземпляр ParList класса
TStringList. Затем в теле оператора try этот объект заполняется информацией о парамет-
рах. Имя и путь к базе данных задается в параметре SER VER NAME, имя пользователя
и пароль — соответственно, в параметрах USER NAME и PASSWORD. В заключение де-
лается попытка создания нового псевдонима. Операторы заключительной части отрывка
кода связывают сетку DBGridl с объектом DataSourcel, который в свою очередь связы-
вается с таблицей Tablel. В заключение объект Tablel связывается с сессией MySession
(свойство SessionName), с созданным псевдонимом (свойство DatabaseName) и таблицей
Sales (свойство TableName). После вызова метода Open набор данных будет открыт
и сетка DBGridl заполнится данными.
void fastcall TForml::ButtonlClick(TObject *Sender)
{
TStringList *ParList = new TStringList();
try
{
ParList->Add("SERVER NAME=d:\\Temp\\employee.gdb");
ParList->Add("USER NAME=SYSDBA");
ParList->Add("PASSWORD=masterkey");
Sessionl->AddAlias("MyIntrbase", "INTRBASE", ParList);
120 Borland C++ Builder 6. Разработка приложений баз данных

}
fj n a 11 у
{
delete(ParList);
}
DBGridl->DataSource=DataSourcel;
DataSourcel->DataSet=Tablel;
Tablel->SessionName="MySession";
Tablel->DatabaseName="MyIntrbase";
Tablel->TableName="Sales";
Tablel->0pen();

Замечание
При повторном выполнении приведенного выше отрывка кода будет возбуждена ис-
ключительная ситуация с сообщением о том, что имя базы данных не является уникаль-
ным в данном контексте. Действительно, псевдоним с тем же именем уже был создан
при первом выполнении и остается доступен для ВОЕ в контексте текущей сессии. Чтобы
избежать этой ситуации, прежде чем создавать новый псевдоним, необходимо прове-
рить, нет ли уже псевдонима с таким именем. Для этого можно воспользоваться методом
IsAlias объекта TSess/on. Этот метод получает строку с именем псевдонима, и если такой
псевдоним данной сессии доступен, то возвращает значение true.

Замечание
Состав доступных в контексте данной сессии псевдонимов зависит от значения
свойства ConfigMode компонента TSess/on. Это свойство может принимать следующие
значения: cfmVirtual (доступны все псевдонимы из конфигурационного файла ВОЕ, все
псевдонимы данной сессии, как постоянные, так и временные); cfmPersistent (доступны
только псевдонимы из конфигурационного файла ВОЕ и постоянные псевдонимы данной
сессии); cfmSession (доступны только псевдонимы, созданные в контексте данной сес-
сии). По умолчанию для сессии доступны все возможные псевдонимы.

Метод AddAHas в основном используется для создания псевдонимов для баз данных
SQL. Для баз данных в формате STANDARD (Paradox, dBASE или текстовый формат) про-
ще использовать другой метод — AddStandardAlias. Он также принимает три аргумента,
и все эти аргументы имеют тип AnsiString. Первый аргумент должен задавать имя псев-
донима, второй — путь к таблицам базы данных. Третий аргумент указывает тип базы
данных и может принимать одно из трех значений: «Paradox», «DBASE» и «ASCIIDRV».
При помощи метода DeleteAlias можно удалить указанный псевдоним из сессии.
В качестве аргумента этому методу нужно передать имя доступного для сессии псев-
донима. Метод ModifyAlias позволяет модифицировать псевдоним, изменив его пара-
метры. Первый аргумент типа AnsiString должен задавать имя существующего псевдо-
нима. Второй аргумент типа TStringList* должен содержать список новых параметров
псевдонима. Приложение должно позаботиться о создании, заполнении и удалении
объекта типа TStringList.
Созданный в контексте сессии псевдоним сохраняется только до тех пор, пока BDE
остается в памяти компьютера. Чтобы созданный псевдоним был доступен и при по-
следующих сеансах BDE, следует вызвать метод SaveConfigFile. Дело в том, что при
инициализации BDE вся информация из его файла конфигурации загружается в память,
и все изменения, сделанные в конфигурации BDE, автоматически на диске не сохраня-
Глава 3. Использование механизма BDE (продолжение) 121

ются. Следует заметить, что если свойство ConfigMode объекта TSession имеет значе-
ние cfmSession, то все изменения конфигурации, сделанные в контексте этого объекта,
сохранены не будут.
Так же как и псевдонимы, в сессию можно добавлять драйверы баз данных SQL.
Для манипуляции драйверами баз данных объект TSession имеет три метода: AddDriver,
DeleteDriver и ModifyDriver. По смыслу и способу использования эти методы мало отли-
чаются от методов манипуляции псевдонимами. Более подробную информацию можно
найти в справочной системе C++ Builder.
И последняя группа методов, о которых следует упомянуть в этом разделе, непо-
средственно связана с управлением явно и неявно созданными объектами TDatabase.
К этой группе относятся методы FindDatabase, OpenDatabase, CloseDatabase и Drop-
Connections. Метод FindDatabase принимает в качестве аргумента строку AnsiString,
содержащую имя объекта TDatabase, ассоциированного с данным объектом TSession.
Метод проверяет свойство DatabaseName всех сеансов связи, выполняющихся в конте-
ксте сессии, и в случае совпадения с переданным аргументом возвращает указатель на
найденный объект TDatabase. Если указанный объект базы данных не найден, то воз-
вращается значение NULL. Метод FindDatabase можно использовать в приложении
перед созданием нового объекта TDatabase, чтобы предотвратить попытки создать
объект с уже существующим именем.
Метод OpenDatabase предназначен для того, чтобы открыть сеанс связи, основан-
ный на уже существующем объекте TDatabase. Имя объекта базы данных передается
методу в качестве аргумента. В процессе работы метод OpenDatabase вызывает метод
FindDatabase, чтобы попытаться найти указанный объект TDatabase среди существую-
щих. Если объект не найден, создается временный объект TDatabase. В любом случае для
объекта базы данных вызывается метод Open и, в конце концов, метод OpenDatabase воз-
вращает на него указатель.
Закрыть объект TDatabase можно при помощи вызова метода DeleteDatabase. Объект
базы данных, свойство которого DatabaseName совпадает с переданным методу аргумен-
том, закрывается. Если этот объект был временным, то он удаляется из памяти, освобож-
дая ресурсы. И, наконец, метод DropConnections удаляет из памяти все временные объек-
ты базы данных, если они неактивны. Этот метод обычно используется для освобождения
ресурсов, если свойство KeepConnections сессии установлено в значение true.
В следующем пункте, завершающем обзор компонента TSession, приводится информа-
ция о том, как использовать сессию для получения исчерпывающей информации обо всех
соединениях и наборах данных, выполняющихся в ее контексте.

Получение информации о соединениях


Компонент TSession имеет большую группу методов, наименование которых начинает-
ся с префикса Get. При помощи этих методов можно получать список псевдонимов, драй-
веров, их параметров, а также баз данных, таблиц и полей таблиц, содержащихся в конте-
ксте данной сессии. Каждый из этих методов принимает аргумент типа TStringList*, ко-
торый и заполняется запрошенной информацией при работе метода. Приложение должно
само поз'аботиться о том, чтобы объект типа TStringList был предварительно создан и,
когда необходимости в его использовании не будет, уничтожен.
Ниже приведено объявление трех однотипных методов, принимающих только аргу-
мент типа TStringList* (TString*).
122 Borland C++ Builder б. Разработка приложений баз данных

void fastcall GetAliasNames(Classes::TStrings* List);


void fastcall GetDatabaseNames(Classes::TStrings* List);
void fastcall GetDriverNames(Classes::TStrings* List);
После завершения выполнения этих методов аргумент будет содержать список псев-
донимов, баз данных или драйверов соответственно. Вот как, например, можно получить
список доступных сессии драйверов:
TStringList *MyList=new TStringList();
try
{
Sessionl->GetAliasNames(MyLi st);
Li stBoxl->Items=MyList;
Li stBoxl->ItemIndex=0;
}
fi n a 11 у
{
delete MyLfst;
}
В первой строке отрывка кода, приведенного выше, создается объект типа TStringList,
а затем предпринимается попытка заполнить его наименованиями всех псевдонимов,
доступных сессии Session 1. Если это удается, то этот список присваивается свойству
Items объекта ListBoxl, который и будет теперь отображать полученный список псевдо-
нимов. И в заключение активным делается первый псевдоним в списке объекта ListBoxl.
В случае неудачи объект типа TStringList удаляется, освобождая ресурсы.
Остальные методы с префиксом Get имеют сходную структуру, но кроме аргумента
типа TStringList* (TString*), получают еще и другие аргументы:
void fastcall GetAliasParams(const AnsiString AliasName,
Classes::TStrings* List);
void fastcall GetDriverParams(const AnsiString DriverName,
Classes::TStrings* List);
void fastcall GetStoredProcNames(const AnsiString DatabaseName,
Classes::TStrings* List);
void fastcall GetTableNames(const AnsiString DatabaseName,
const AnsiString Pattern, bool Extensions, bool SystemTables,
Classes::TStrings* List);
void fastcall GetFieldNames(const AnsiString DatabaseName,
const AnsiString TableName, Classes::TStrings* List);
Первые три метода кроме списка получают еще один аргумент — имя объекта, для ко-
торого список и должен быть заполнен соответствующей информацией. Для первых двух
методов список List заполняется параметрами, соответственно, псевдонима и драйвера,
чье имя было передано в первом аргументе, а третий метод заполняет список именами
хранимых процедур указанной базы данных.
Структура метода GetTableNames немного сложнее. Этот метод возвращает в списке
List перечень всех таблиц базы данных DatabaseName. На возвращаемый перечень влия-
ют значения еще трех аргументов, передаваемых в метод GetTableNames. Строка Pattern
может содержать шаблон возвращаемых таблиц и стандартные символы замены. Если
аргумент Pattern — пустая строка, возвращаются все таблицы. Если аргумент Extensions
Глава 3. Использование механизма ВОЕ (продолжение) 123

имеет значение true, то имена таблиц Paradox и dBASE будут содержать и расширения.
Значение true аргумента SystemTables предполагает, что кроме имен регулярных таблиц
базы данных будут возвращены также имена системных таблиц (если таковые имеются).
И последний метод, GetFieldNames, в качестве аргументов принимает имя базы дан-
ных и имя таблицы, возвращая в аргументе List список полей.
Схему работы со всеми этими методами проще понять на конкретном примере.
Создайте новое приложение и в верхней правой части пустой формы разместите элемент
TComboBox. Под ним, расположите три объекта типа TListBox, расположив их один за
другим по горизонтали. Над каждым из списков разместите метки (компонент TLabef),
последовательно задав для них подписи: «Псевдонимы:», «Параметры:» и «Поля:».
В заключение расположите на форме объект TSession. Оставьте для всех элементов имена
по умолчанию. Для списка ListBoxS и метки Labels задайте для свойства Visible значе-
ние false, так как сразу после открытия формы эти элементы не должны быть видимы.
В завершение настройки макета будущего приложения щелкните на кнопке построителя
для свойства Items объекта TComboBox и в диалоговом окне String List Editor введите три
строки: «Псевдонимы», «Драйверы» и «Базы данных».
Логика работы приложения должна быть следующая. При выборе объекта из комбини-
рованного списка ComboBoxl должны обновляться соответствующим образом и списки
ListBoxl viListBox2 (при необходимости viListBox3). Далее, при выборе определенного
элемента в списке ListBoxl соответственно должны обновляться и списки ListBoxl и, при
необходимости, ListBoxS. Список ListBox3 будет использоваться, только если в комбини-
рованном списке ComboBoxl был выбран элемент Базы данных. В остальных случаях сам
список и соответствующая ему метка отображаться на форме не будут, а ширина формы
должна быть настроена соответствующим образом.
В листингах 3.1 и 3.2 приведен полный код файла заголовка и файла модуля приложе-
ния GetSessionlnfo, а после листингов помещено краткое описание.
Листинг 3.1. Файл заголовка приложения GetSessionlnfo
//
#ifndef UnitlH
#defme UnitlH
//
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <DBTables.hpp>
//
class TForml : public TForm
{
published: // IDE-managed Components
TListBox *ListBoxl;
TSession *Sessionl;
TComboBox *ComboBoxl;
TLabel *Labell;
TListBox *ListBox2;
TLabel *Label2;
TListBox *ListBox3;
TLabel *Label3;
124 Borland C++ Builder 6. Разработка приложений баз данных

void _ fastcall FormCreate(TObject *Sender) ;


void _ fastcall ComboBoxlChange(TObject *Sender)
void _ fa'stcall ListBoxlClick(TObject *Sender) ;
void _ fastcall ListBox2Click(TObject *Sender) ;
private: // User declarations
public: // User declarations
_ fastcall TForml(TComponent* Owner);

extern PACKAGE TForml *Forml;


//
#endif

Листинг З.2. Главный модуль приложения GetSessionlnfo

#include <vcl.h>
#pragma hdrstop
#include "Unitl.h

#pragma package(smart_ini^t)
#pragma resource "*.dfm"
TForml *Forml;
_fastcall TForml : :TForml (TComponent* Owner)
: TForm(Owner)

void _ fastcall TForml : :FormCreate(TObject *Sender)


{
ComboBoxl->ItemIndex=0;
Width=336;
ComboBoxlChange(NULL) ;
ListBoxlClick(NULL) ;

void _ fastcall TForml : :ComboBoxlChange(TObject *Sender)


{
TStringList *MyList=new TStri ngLi st () ;
try
{
switch (ComboBoxl->ItemIndex)
{
case 0: //Псевдонимы
{
Sessionl->GetAliasNames(MyList) ;
ListBox3->Visible=false;
Label3->Visible=false;
Width=330;
break;
Глава 3. Использование механизма ВОЕ (продолжение) 125

case 1: //Драйверы
{
Sessionl->GetDri ve r Names (My Li st) ;
Width=330;
ListBox3->Visible=false;
Label 3->Visible=false;
break;
}
case 2: //Базы данных
{
Sessionl->GetDatabaseNames(MyList) ;
Width=495;
ListBox3->Visible=true ;
Label3->Visible=true;
break;

Li stBoxl->Items=MyLi st ;
ListBoxl->ItemIndex=0;
Label!- >Caption=ComboBoxl->I terns- >St rings
[ComboBoxl->ItemIndex]+" : " ;
}
_ finally
{
delete MyList;
}
ListBoxlClick(NULL) ;
>
// ------------------------------------------------------------------
void _ fastcall TForml : : ListBoxlClick(TObject *Sender)
I
TStringList *MyList=new TStringListO ;
try
{
swi tch(ComboBoxl->ItemIndex)
{
case 0: //Псевдонимы
{
Sessionl->GetAliasParams (Li stBoxl->I terns- >
5trings[ListBoxl->ItemIndex] , MyList) ;
Label2->Caption="napaMeTpbi: " ;
break;
}
case 1: //Драйверы
{
Sessionl->GetDriverParams(Li s tBoxl->I terns- >
Strings[ListBoxl->ItemIndex] .MyList) ;
Label2->Caption = "riapaMeTpbi: " ;
break ;
126 Borland C++ Builder 6. Разработка приложений баз данных

case 2: //Базы данных


{
Sessionl->GetTableNames(Li stBoxl->I terns ->
Strings[ListBoxl->ItemIndex] ,
"" , false, false, MyList) ;
Label2- >Сар11оп="Таблицы: " ;
break;

ListBox2->Items=MyList ;
ListBox2->ItemIndex=0;
}
_ fl n a 1 1 у
{
delete MyList;
}
if (ComboBoxl->ItemIndex == 2) ListBox2Click(NULL) ;
}
// ----------- .
void __ fastcall TForml: :ListBox2Click(TObject *Sender)
{
i f (ComboBoxl->ItemIndex != 2) return;
TStringList *MyList=new TStringLi st () ;
try
{
Sessionl->GetFieldNames (Li stBoxl->I terns- >
Strings [ListBoxl->ItemIndex] ,
ListBox2->Items->Strings[ListBox2->ItemIndex] ,
MyList) ;
ListBox3->Items=MyList ;
ListBox3->ItemIndex=0;
}
_ fi n a 1 1 у
{
delete MyList;

Работа приложения GetSessionlnfo начинается с выполнения кода обработчика события


OnCreate главной формы Forml. Первая строка кода активизирует первый элемент списка
элемента ComboBoxl. Первый элемент комбинированного списка, как уже упоминалось
выше, — это «Псевдонимы». В этом случае задействованы только два списка: в крайнем
слева списке ListBoxl будут отображаться все псевдонимы, а в списке ListBox2 — список
соответствующих параметров. Третий список, ListBoxS, и метка Labels сразу отображать-
ся не должны, поэтому нужно соответствующим образом настроить ширину формы. Для
этого ее свойству Width задается значение 330.
Чтобы обновить содержимое списков, вызываются обработчики события OnChange ком-
бинированного списка ComboBoxl и OnClick объекта ListBoxl. Первый из этих обработчиков
предназначен для обновления списка ListBoxl , второй — для обновления списка ListBox2.
Глава 3. Использование механизма ВОЕ (продолжение) 127
Как видно из листинга 3.2, обработчики событий, возбуждаемых при изменении актив-
ного элемента в комбинированном списке и списках TListBox, объединены в иерархичес-
кую цепочку. При выборе элемента в комбинированном списке ComboBoxl возбуждается
его событие OnChange. После выполнения необходимых действий в коде обработчика
этого события вызывается обработчик события OnClick списка ListBoxl. Таким обра-
зом, обработчик события OnClick списка ListBoxl может быть вызван или вручную (из
обработчика ComboBoxl Change), или при выборе нового элемента списка (при помощи
мышки или клавиатуры). В коде обработчика этого события при необходимости (если
в комбинированном списке ComboBoxl был выбран элемент «Базы данных») вызывается
обработчик события OnClick списка ListBox2. Таким образом происходит цикл обновле-
ния содержимого всех элементов главной формы приложения.
Обработчики событий OnClick и OnChange, участвующие в обновлении элементов
управления формы, имеют сходную структуру. В начале кода обработчика создается объект
MyList типа TStringList. Далее в теле оператора try проверяется содержимое комбиниро-
ванного списка ComboBoxl. В зависимости от сделанного выбора вызывается нужный
метод, заполняющий объект MyList, должным образом настраивается ширина формы,
а также видимость элементов ListBoxS и LabelS, и задаются подписи для меток Label 1
и Label2. В заключение список MyList присваивается свойству Items следующего в иерар-
хии списка TListBox, и его первый элемент активизируется.
Откомпилируйте приложение, запустите его на выполнение и поэкспериментируй-
те с ним, наблюдая действия приложения в ответ на выбор тех или иных элементов.
Примерный вид главной формы приложения приведен на рис. 3.7.

DefauUDO
IBLocel
MyOata
MvOotol
База ценных MS Access
:unl FoxPro
Та&лииы Visual FoxPro
Файлы dSASE

Рис. З.7. Приложение GetSessionlnfo


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

Замечание
К упомянутой группе методов можно отнести также и методы GetAliasDriVerName
и GetConf/'gParams. Первый из них получает в качестве аргумента имя псевдонима
и возвращает имя используемого псевдонимом драйвера. Второй метод предназначен
для возвращения параметров конфигурации ВОЕ. Более подробную информацию об
этом методе можно получить в справочной системе C++ Builder и ВОЕ.
128 Borland C++ Builder 6. Разработка приложений баз данных

Элементы визуализации
и управления данными
Сталкиваясь с элементами управления этого типа, сразу убеждаешься в том, насколькс
английский язык приспособлен к тому, чтобы быть языком терминов. То, что по-русскр
называется элементами визуализации и управления данными, по-английски звучит так
Data-Aware Controls (или просто Data Controls). Все элементы этого типа собраны НЕ
вкладке Data Controls палитры компонентов. Основное их назначение, как и следует и;
названия, — отображать информацию из источника данных и предоставлять возможность
манипулировать данными. Таким образом, интерфейс любого приложения баз данных со-
стоит из набора элементов визуализации и управления данными.
Часть компонентов с вкладки Data Controls по своим функциям соответствуют привыч-
ным компонентам, расположенным на вкладке Standard. Например, элементу редактирования
TEdit соответствует элемент редактирования TDBEdit, метке TLabel соответствует компо-
нент TDBText, и т.д. Основное отличие элементов визуализации и управления данными от
их стандартных собратьев в том, что все элементы с вкладки Data Controls имеют свойство
DataSource. При помощи этого свойства компонент может подключаться к источнику данных,
предоставляя пользователю возможность просматривать или модифицировать информацию
из одной или нескольких таблиц, соответствующих этому источнику.
Какой из элементов визуализации и управления данными использовать в своем при-
ложении, определяется конкретной ситуацией и назначением формы. Большая часть
элементов управления с вкладки Data Controls предоставляет доступ к значению поля
текущей записи набора данных. Такие элементы управления, как TDBGrid и TDBCtrlGrid,
предоставляют доступ сразу к нескольким записям набора данных. В большинстве случа-
ев удобнее всего использовать сетку (компонент TDBGrid). Этот компонент предоставля-
ет пользователю огромное количество возможностей оформления, просмотра, навигации,
модификации информации из набора данных. Однако достаточно часто возникают ситуа-
ции, когда в форме нужно отображать только значения полей текущей записи. Например,
в форме, отображающей список операций (приход, расход, списание товара и т. д.), удоб-
нее использовать сетку (TDBGrid). Из этой формы можно вызывать другую форму, со-
держащую детальную информацию о текущей операции. Эта форма, по вполне понятным
причинам, должна отображать информацию только о текущей записи.
В последнем случае тип элемента управления определяется типом того поля набо-
ра данных, значения из которого элемент управления должен отображать. Например,
для отображения значений полей логического типа целесообразно использовать флаж-
ки (TDBCheckBox), для полей графического типа— компонент TDBImage, для полей
Memo — компонент TDBMemo и т. д.
В предлагаемом вашему вниманию разделе будут кратко освещены общие и некоторые спе-
цифические вопросы использования элементов визуализации и управления данными. Основное
внимание, однако, будет уделено описанию возможностей элемента сетка (компонент TDBGrid).

Простейшие средства управления данными


К простейшим элементам визуализации и управления данными можно отнести все те,
которые предназначены для предоставления доступа к значению поля одной (текущей)
записи. На вкладке Data Controls к этой категории относятся следующие компоненты:
TDBText, TDBEdit, TDBMemo, TDBImage, TDBCheckBox и TDBRichEdit. Каждый из них
Глава 3. Использование механизма ВОЕ (продолжение) 129
имеет два свойства, связывающие его с конкретным полем выбранного источника данных.
Свойство DataSource должно содержать имя источника данных (объекта TDataSource),
подключенного к одному из наборов данных, доступных в приложении. Связать эле-
мент управления с конкретным полем источника данных можно при помощи свойства
DataField, выбрав имя поля из выпадающего списка свойства.
Если набор данных, с которым связан элемент управления, открыт, то вы немедленно
увидите значение поля первой записи набора. При переходе к другой записи набора все
связанные с набором данных элементы управления обновятся, отображая соответствую-
щие значения полей этой записи. Перемещаться по записям набора данных в режиме кон-
структора можно при помощи панели навигации редактора полей (Field Editor), который
можно отобразить на экране, дважды щелкнув мышкой на иконке объекта набора данных.
В режиме выполнения приложения нужно предоставить пользователю какой-либо способ
навигации по набору данных. Для этого можно использовать компонент TDBNavigator,
также расположенный на вкладке Data Controls, или обеспечить какую-либо другую воз-
можность вызывать методы навигации (First, Last, Next и т. д.).
Остальные свойства, а также методы и события простейших элементов визуализации
и управления данными мало отличаются от свойств, методов и событий соответствующих стан-
дартных элементов управления. Смысл использования элементов обеих групп также совпадает.
Так, например, объекты типа TDBEdit предназначены для отображения и редактирования одно-
строчного текста, являющегося значением поля набора данных; объекты типа TDBText предна-
значены только для отображения однострочного текста; объекты типа TDBRichEdit позволяют
отображать и редактировать многострочный форматированный текст, и т. д.
Несколько особняком от остальных элементов управления с вкладки Data Controls
стоит компонент TDBNavigator (панель навигации). На этой вкладке это единственный
элемент управления, не предназначенный для отображения информации. С его помощью
можно перемещаться по записям набора данных, а также удалять, добавлять, сохранять
записи или отменять сделанные изменения. Для подключения к набору данных нужно
указать имя имеющегося объекта TDataSource. Управлять наличием кнопок панели на-
вигации можно при помощи свойства VisibleButtons. Это свойство типа множество (Set)
может содержать набор констант, каждая из которых соответствует конкретной кнопке
панели навигации (например, nbFirst, nbPrior и т. д.). Чтобы удалить кнопку с панели на-
вигатора в режиме конструктора, достаточно задать в инспекторе объектов для соответ-
ствующей константы значение false. Кнопка будет удалена с панели, и соответствующая
функция в режиме выполнения приложения доступна не будет.

Рис. 3.8. Форма для отображения и редактирования подробной информации о сотруднике.


Объект типа TDBNavigator служит для перемещения по записям таблицы Employee
5 Зак.319
130 Borland C++ Builder 6. Разработка приложений баз данных

На рис. 3.8 изображена форма для отображения подробной информации о сотруднике


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

Списки, комбинированные списки и группы


На вкладке Data Controls находится группа компонентов, представляющих в прило
жении значение поля одной записи, но предоставляющих возможность для выбора это
го значения из набора элементов. К этим компонентам относятся списки (TDBListBo)
и TDBLookupListBox), комбинированные списки (TDBComboBox и TDBLookupComboBox
и группы переключателей (TDBRadioGroup).
Компонент TDBListBox, как и обычный список, отображает элементы, перечисленньк
в его свойстве Items. При помощи свойств DataSource и DataField объект типа TDBListBo)
подключается к полю набора данных (таблицы). При переходе от одной записи наборе
данных к другой меняется и активный элемент списка, отображая значение поля текуще!
записи. При выборе элемента списка при помощи мышки или клавиатуры его значение
присваивается присоединенному полю набора данных.
Несколько большие возможности предоставляет компонент TDBLookupListBox. Благодар5
этому TDBLookupListBox и используется гораздо чаще, чем компонент TDBListBox. В первук
очередь следует отметить, что компонент TDBLoohipListBox не имеет свойства Items. Вместе
этого задать источник строк для отображения в списке можно при помощи двух других свойств
ListSource и ListField. Свойство ListSource должно содержать имя объекта TDataSource, под
ключенного к тому набору данных, значения одного или нескольких полей которого должнь
отображаться в списке. Этот набор данных называется таблицей подстановки (Lookup Table)
Имена отображаемых полей должны указываться в свойстве ListField. Имя одного поля можнс
выбрать из списка свойства, а имена нескольких полей следует вводить вручную, разделяя го
символом точки с запятой.
Еще одно важное свойство компонента TDBLookupListBox, связанное с таблице?
подстановки, — KeyField. Это свойство должно содержать имя поля таблицы подста
новки, которое будет представлять отображаемые в списке объекта значения. Чаще всегс
в качестве значения Свойства KeyField выступает первичный ключ таблицы (что и следу
ет из его названия). Например, можно в качестве таблицы подстановки для объекта тип;
TDBLookupListBox использовать таблицу Category, содержащую перечень всех катего
рий товаров. В этом случае в качестве значения свойства ListField удобно задать пол(
Category, а для свойства KeyField — поле CategoryNo, являющееся первичным ключо\
таблицы. Таким образом, отображаться будут текстовые описания категорий, а использо
ваться — соответствующие первичные ключи.
Как и почти все остальные элементы визуализации и управления данными, компонеш
TDBLookupListBox имеет свойства DataSource и DataField. Эти свойства служат для под
ключения к полю набора данных, которое объект TDBLookupListBox должен представ
лять. Поле, указываемое в свойстве DataField, должно по типу соответствовать полю, ука
занному в свойстве KeyField, так как по этим полям должна осуществляться связь межд)
основным набором данных и таблицей подстановки. Чаще всего свойство KeyField содер
жиг имя первичного ключа таблицы подстановки, а свойство DataField— имя внешнегс
ключа основной таблицы.
Глава 3. Использование механизма ВОЕ (продолжение) 131

Продолжая начатый ранее пример (в котором как таблица подстановки используется


Category), в качестве основной таблицы для объекта TDBLookupListBox можно взять
Items. Тогда свойство DataField можно установить в значение Category, так как именно это
поле является внешним ключом по отношению к таблице Category. Таким образом, при
выборе в списке объекта TDBLookupListBox наименования категории, внешнему ключу
Category таблицы Items будет присвоено соответствующее значение первичного ключа та-
блицы Category. Используя такую схему редактирования таблицы Items, можно добиться
максимальной наглядности и избежать ошибок из-за нарушения целостности данных.
Замечание
Объект типа TDBLookupListBox можно использовать только после того, как для него
заданы значения всех упомянутых ранее свойств: ListSource, ListField, KeyField, DataSource
и DataField. Если вам не нужны функциональные возможности компонента, связанные
сего направленностью на связь с базой данных, следует воспользоваться компонентом
TListBox, а если нет необходимости в таблице подстановки, лучше использовать компо-
нент TDBListBox.

Почти то же самое можно сказать и относительно компонентов TDBComboBox


и TDBLookupComboBox. Объект типа TDBComboBox может отображать в своем-
м выпадающем списке элементы, перечисленные в его свойстве Items, а объект типа
TDBLookupComboBox для отображения элементов выпадающего списка использует меха-
низм таблиц подстановки (свойства ListSource, ListField и KeyField). В остальном работа
с комбинированными списками обоих типов практически ничем не отличается от работы
с обычным компонентом ТСотЬоВох.
Еще один элемент управления, предоставляющий возможность отображать или
выбирать значения для поля из набора фиксированных элементов — - компонент
TDBRadioGroup. Для подключения к полю набора данных используются свойства
DataSource и DataField, а набор отображаемых в группе элементов хранится в свойстве
Items. Чтобы разместить элементы в несколько столбцов внутри группы, можно восполь-
зоваться свойством Columns. По умолчанию это свойство имеет значение 1, т. е. элементы
отображаются в одном столбце. Свойство Values типа TString должно содержать столько
же элементов, сколько и свойство Items. При выборе одного из отображаемых в группе
элементов соответствующий элемент из свойства Values присваивается полю текущей за-
писи, имя которого указано в свойстве DataField.

Диаграмма (компонент TDBChart)


Компонент TDBChart предназначен только для отображения информации, модифици-
ровать данные с его помощью нельзя. Диаграмма TDBChart — наиболее сложно органи-
зованный компонент из всех, расположенных на вкладке Data Controls. Чтобы убедиться
в этом, достаточно взглянуть на диалоговое окно редактирования ее свойств (см. рис.
3.9), которое отображается на экране после двойного щелчка мышкой на изображении
объекта. Окно редактирования свойств диаграммы содержит несколько десятков вкладок
различного уровня вложения, вмещающих огромное количество элементов управления.
Весь этот гигантский набор предназначен для тонкой настройки объекта диаграммы: от
задания способа получения набора данных до тонких аспектов отображения различных
элементов диаграммы (сетки, осей, области данных и т.д.).
Однако, несмотря на обилие, большинство свойств диаграммы автоматически по-
лучает значения по умолчанию. Поэтому на самом деле пользоваться диаграммой не так
132 Borland C++ Builder 6. Разработка приложений баз данных

уж сложно. В этом разделе будет рассмотрен пример, описание которого поможет понят
логику работы с компонентом TDBChart.
Прежде чем приступить к примеру, следует добавить в базу данных с псевдониме»
MyData еще одну таблицу. Эта таблица будет называться Storage, и в ней будут хранить
ся данные об остатках товара на складе. Описание структуры таблицы Storage приведен!
в таблице 3.1.
Таблица 3.1. Описание структуры таблицы Storage

Поле Tun Размер Ключ Обязательное


(Field Name) (Type) (Size) (Key) (Required)
StoreltemNo Autoincrement (+) - * Yes
Item Long Integer (I) - Yes
Qty Short (S) - No
Description Memo (M) 240 No

Как видите, таблица Storage невелика— всего четыре поля. Поле StoreltemNo яв
ляется первичным ключом таблицы, а поле Item — внешним ключом, ссылающимся н<
первичный ключ таблицы Items. Поле Qty предназначено для хранения количества товара
имеющегося на складе. Вводить значение в это поле не обязательно, так как отсутстви*
значения просто будет расцениваться как отсутствие товара на складе (т. е. отсутствующее
значение будет считаться нулем). В поле Description можно будет хранить дополнитель
ную информацию, например, о необходимости заказать товар и т.д. В заключение таблиц)
Storage необходимо связать с таблицей Items по полям Item и ItemNo. Как это можнс
сделать при помощи утилиты Database Desktop, уже рассказывалось в предыдущих гла
вах. Сохраните таблицу под именем Storage в том же каталоге, где хранятся и остальные
таблицы базы данных (псевдонима) MyData.

Format. | General | Mtrts Do» Source |

labels: (C

Рис. З.9. Диалоговое окно редактирования свойств диаграммы (компонент TDBChart)


Обязательным для заполнения в таблице Storage является поле Item; желательно так-
же заполнить некоторой информацией поле Qty, иначе склад будет считаться пустым. Поле
Item должно хранить значения первичных ключей таблицы Items. Один из самых простых
вариантов заполнения поля Item значениями — использование компонента TBatchMove,
который как раз и предназначен для подобных групповых операций. Более подробно ком-
понент TBatchMove будет рассматриваться чуть позже в этой главе. Пока же проделайте
Глава 3. Использование механизма ВОЕ (продолжение) 133

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


ТТаЫе и один компонент TBatchMove. Объект Tablel подключите к таблице Storage базы
данных MyData, а объект ТаЫе2 — к таблице Items той же базы. Для свойства Active обо-
их объектов задайте значение true.
Далее, для свойства Source объекта BatchMovel задайте значение ТаЫе2, а свойство
Destination установите в значение Tablel. Таким образом, информация будет копироваться
из таблицы Items в таблицу Storage. Затем щелкните на кнопке построителя для свойства
Mappings и в окне редактора строк введите следующий текст:
Item=IternNo
При помощи свойства Mappings можно указать поля таблицы-источника, значения
из которых будут копироваться. Имена полей таблицы-источника указываются справа от
знака равенства, а соответствующие имена полей таблицы-приемника — слева от знака
равенства.
Добавьте на форму кнопку (TButton), а в тело обработчика ее события OnClick введите
всего одну строку:
BatchMovel->Execute();
Эта строка кода запускает копирование информации, заданное в настройках объекта
BatchMovel, в результате чего будет заполнено значениями поле Item таблицы Storage.
Поле Qty можно, конечно, заполнить и вручную, однако это слишком длительный процесс.
Лучше поступить следующим образом. Добавьте на форму еще одну кнопку и в тело ее
обработчика события OnClick введите следующий код:
randomizeO ;
Tablel->First() ;
while (!Tablel->Eof)
{
Tablel->Edit() ;
Tablel->FieldByName("Qty")->Value=random(100);
Tablel->Post();
Tablel->Next();
}
В результате выполнения этого отрывка кода поле Qty таблицы Storage будет заполне-
но случайными значениями в диапазоне от 0 до 100. Впрочем, если вам неохота возиться,
выполняя все вышеописанные действия, просто воспользуйтесь уже готовой таблицей
Storage, которая находится на поставляемом с книгой диске в каталоге Data.
Теперь можно вернуться к примеру использования компонента TDBChart. Создайте
новое приложение и на его главной форме расположите компонент TDBChart, задав его
размеры так, чтобы заполнить всю рабочую область формы. После этого добавьте на фор-
му компонент TQuery с вкладки BDE. Подробно этот компонент будет рассмотрен позже,
а пока просто следуйте инструкциям. В качестве значения свойства DatabaseName объек-
та Query 1 выберите псевдоним MyData. Затем щелкните на кнопке построителя свойства
SQL и в окне редактора строк введите следующий текст:
SELECT Category.Category, Sum(Storage.Qty*Items.Price) AS Cost
FROM (Items INNER JOIN Category ON Items.Category =
134 Borland C++ Builder 6. Разработка приложений баз данных

Category.CategoryNo) INNER JOIN Storage ON Items.ItemNo = Storage.Item


GROUP BY Category.Category;
Приведенный выше текст — это оператор, записанный на языке запросов SQL. В этом
запросе участвуют три таблицы (Category, Items и Storage), связанные между собой по
ключевым полям (т.е. так, как это было запланировано при проектировании). Из таблицы
Category выбираются все наименования категорий, а из таблицы Storage — стоимость
всех товаров каждой категории, хранящихся на складе. Таблица Items служит для того,
чтобы связать между собой таблицы Category и Storage. Более подробно о языке запро-
сов SQL будет рассказано далее, при изучении компонента TQnery. В заключение устано-
вите свойство Active объекта Query! в значение true.
Теперь пора настроить объект DBChartl. После двойного щелчка мышкой на диа-
грамме на экране появится диалоговое окно редактирования ее свойств. Откройте окно на
вкладке Chart (вкладка самого верхнего уровня) и затем внутри этой вкладки, на вкладке
Series. В центральной части этой вкладки располагается список серий данных, которые
должны будут отображаться на диаграмме. Пока этот список пуст. Нажмите кнопку Add,
в появившемся диалоговом окне TeeChart Gallery выберите вид диаграммы Pie и нажмите
кнопку ОК. В результате в списке серий появится Seriesl.

Рис. 3.10. Диаграмма, отражающая соотношение


стоимости товаров каждой категории на складе в процентах
Далее, выберите эту серию и перейдите на вкладку верхнего уровня Series. Внутри
этой вкладки перейдите на вкладку Data Source и из расположенного там комбинирован-
ного списка выберите элемент Dataset. В результате на вкладке Data Source появятся еще
три комбинированных списка (см. рис. 3.9). В комбинированном списке Dataset выберите
Query 1. Из комбинированного списка Labels выберите поле Category, а из комбиниро-
ванного списка Pie— поле Cost. Таким образом, круговая диаграмма будет поделена
на секторы в зависимости от значений поля Cost (стоимость товаров каждой категории),
а помечен каждый сектор будет при помощи значений поля Category.
Удобно было бы, чтобы кроме наименований категорий, метки на диаграмме также со-
держали долю каждой категории в общей стоимости всех товаров. Перейдите на вкладку
Marks и в группе переключателей Style выберите переключатель Label and Percent. Теперь
вновь перейдите на вкладку верхнего уровня Chart и внутри нее — на вкладку Titles. На
этой вкладке вместо стандартного заголовка TDBChart введите что-нибудь более подходя-
щее. Например, Остатки на складе по категориям. Можно также отключить отображение
легенды, так как она занимает слишком много места. Для этого перейдите на соседнюю
вкладку Legend и снимите флажок Visible. Etce, диаграмма готова (см. рис. 3.10).
Глава 3. Использование механизма BDE (продолжение) 135
Компонент TDBCtrlGrid
Компонент TDBCtrlGrid, как и компонент TDBGrid, предназначен для одновременного
отображения информации сразу из нескольких записей. Однако в этих элементах отобра-
жение организовано по-разному. В то время как компонент TDBGrid отображает записи
набора данных в табличной форме, TDBCtrlGrid предоставляет возможность отображать
каждую запись на отдельной панели. При этом используются обычные элементы визуали-
зации и управления данными (TDBEdit, TDBImage и другие).

Рис. 3.11. Компонент TDBCtrlGrid в режиме конструктора


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

Рис. 3.12. Объект типа TDBCtrlGrid в режиме выполнения


136 Borland C++ Builder 6. Разработка приложений баз данных

В заключение краткого обсуждения компонента TDBCtrlGrid следует упомяну


о нескольких важных его свойствах.
G ColCount и RowCoimt. Эти свойства задают способ размещения и количество панеле
Значение по умолчанию для свойства ColCount — 1, а для свойства RowCount — 3. Э'
означает, что всего панелей 3 и расположены они в один столбец и в три строки. Таки
образом можно задать любое количество панелей и разместить их в несколько стрс
и столбцов.
Q Orientation. Свойство задает ориентацию панелей (вертикальную или горизонтальную).
Q AllowDelete и Allowlnsert. Эти свойства логического типа задают возможность удал!
ния или вставки записей.

Использование сетки (компонент TDBGrid)


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

Свойство Columns. Объекты TColumn


Свойство Columns имеет тип TDBGridColumns и является индексированной коллек
цией объектов TColumn. Объекты типа TColumn представляют отдельный столбец сетки
включая его заголовок. Свойство Columns можно использовать в режиме выполнения про-
граммы для управления коллекцией объектов TColumn, а также для доступа к отдельным
колонкам сетки.
В режиме конструктора столбцы сетки (набор объектов TColumn) можно настро-
ить при помощи редактора столбцов (Column Editor). Отобразить редактор столбцов
на экране можно, щелкнув мышкой на кнопке построителя свойства Columns (см. рис.
3.13). Почти всю рабочую область окна редактора столбцов занимает список объектов
TColumn, однако при первом вызове редактора этот список будет пуст. Следует заметить,
что между редактором столбцов и редактором полей существует определенная аналогия.
В частности, если список объектов TColumn пуст, BDE создаст динамический набор объ-
ектов-столбцов на основании всех полей набора данных, и именно этот набор столбцов
и будет отображаться в сетке.
При помощи редактора столбцов можно создать подходящий набор объектов TColumn,
которые будут отображать в сетке информацию только из выбранных полей набора дан-
ных. Чтобы создать новый объект TColumn, можно или нажать клавишу Ins на клавиатуре,
или кнопку Add New (крайняя слева на панели инструментов редактора столбцов), или
выбрать пункт Add контекстного меню. В результате в рабочей области окна редактора
столбцов появится строка наподобие следующей: 0 — TColumn. В этой строке 0 (или 1, 2,
Глава 3. Использование механизма ВОЕ (продолжение) 137
3 и т.д.) — это индекс данного объекта TColumn в коллекции TDBGridColumns. Для даль-
нейшей настройки объекта-столбца нужно воспользоваться инспектором объектов.

0 - Cetegoiy
1 -Item
"

Рис. 3.13. Редактор столбцов содержит определение трех объектов TColumn


В первую очередь следует указать наименование поля, которое будет источником дан-
ных для объекта-столбца. Это можно сделать, выбрав имя поля из списка доступных по-
лей набора данных, прикрепленного к свойству FieldName. Тогда в рабочей области окна
редактора столбцов вместо надписи TColumn появится наименование выбранного поля.
Имя поля будет использоваться и в качестве заголовка столбца в сетке. В принципе, на
этом можно было бы закончить настройку объекта-столбца. Добавив в коллекцию нужное
количество объектов TColumn и настроив каждый из них на определенное поле набора
данных, можно добиться того, что сетка будет отображать только нужные поля и в нужной
последовательности.
Дальнейшая настройка обычно заключается в задании различных свойств отображе-
ния в сетке самого столбца и его заголовка. Цвет фона столбца можно задать при помощи
свойства Color, а параметры шрифта данных — при помощи свойства Font. При помощи
свойства Alignment можно задать выравнивание данных в пределах ячейки (влево, вправо
или по центру).
Для настройки свойств заголовка столбца служит свойство Title объекта TColumn.
Свойство-объект Title имеет тип TColumnTitle. Развернув это свойство в инспекторе объектов,
можно получить доступ к основным свойствам заголовка. Текст заголовка по умолчанию
совпадает с именем поля, данные из которого представляет столбец в сетке. Изменить текст
подписи можно при помощи свойства Caption. Остальные свойства объекта Title позволяют
задать выравнивание подписи, цвет фона и параметры текста. На рис. 3.13 изображен редак-
тор столбцов, содержащий определения трех объектов TColumn. Для заголовков объектов-
столбцов заданы подходящие тексты подписей, а также цвет и выравнивание по центру. Как
это выглядит в сетке в режиме выполнения приложения, изображено на рис. 3.14.

Кит» гори | Товар l.[«nn j *j


Samsung ML-ЧШ 18900 — ^
Samsung ML-121Q 20300 .
Epson EPL-5900L 239 00
^ MH •Ш | Салоп LBP-81 0 215QO
Brother HL-1240 Mono Loser 26БОО
Epson AcuLaser C1 000 1.34900
Epson AcuLaser C2QOO 2,15500
HP LaserJet 100Qw 22-1 OOt =
HP LaserJet! 200 332.00*
HP Laser Jen 220 44000* :

1 = HP LaserJet 3200 637 00| • •

Рис. 3.14. Заголовки столбцов сетки проще всего настроить в режиме конструктора
Сетка TDBGrid, как уже говорилось, предназначена не только для отображения
информации; если это допускает набор данных, она дает возможность редактировать
138 Borland C++ Builder 6. Разработка приложений баз данных

данные. Однако если установить свойство Readonly объекта TColumn в значение tru
редактировать значение соответствующего поля в ячейке сетки будет запрещено. Г
умолчанию объект TColumn предоставляет тот же стиль редактирования, что и компоне!
TDBEdit. Изменить стиль редактирования в ячейке столбца можно при помощи свойст!
ButtonStyle. Оно может принимать одно из трех значений: cbsAuto, cbsEllipsis и cbsNon
Значение по умолчанию — cbsAuto. При нем ячейки столбца ведут себя или как обычнь;
элементы редактирования (компонент TDBEdit), или как комбинированные списки (KOIV
понент TDBComboBox). Все зависит от свойства PickList объекта TColumn. Это свойств
имеет тип TStrings* и может содержать список значений для подстановки. В этом случа
в режиме выполнения приложения при переходе в режим редактирования, ячейка столбц
приобретет вид комбинированного списка (компонент TDBComboBox). Прикреплении]
к ячейке список будет содержать все значения, указанные в свойстве PickList, и одн<
из них можно выбрать в качестве значения поля, соответствующего столбцу (см. рис
3.15). Высоту выпадающего списка в строчках можно указать при помощи свойств;
DropDownRows. По умолчанию это свойство имеет значение 7, т. е. выпадающий СПИСОР
будет содержать 7 строк.

Категорий .Товар I ц*на.,Ы


»> 1 V Samsung ML-450G 10900$ — 'I
ИИИИ1 * Samsung ML-1 21 0 203.001
Epson EPL-590QL 239.001
ft Canon LBM10 215001 ,1

Рис. 3.15. Если свойство ButtonStyle имеет значение cbsAuto


и свойство PickList содержит список значений подстановки,
то ячейка столбца работает как комбинированный список

I Кнп« прим 1 tump I .№».« |£|


[принтеры Samsung ML-450Q
[(Принтеры Samsung ML,-1210 203 DOt -
^Принтеры •» Epson EPL-5900L 239 JO
ЙЯЕЯЯЯЯИС* Салоп |_8Р-в10 215.00
Brother НЫ240 Mono Laser 266 00
_ Процессоры Epson Aculeser Cl 000 i 1.3-19 00 :

Модули пвм«" Epson AcuLaser C2000 2.156.00 I-


Виаеокарты
221 00
- Мониторы ^_
\ [рнитвры '' — * HP LaserJet 1200 332.00 ;.

Рис. 3.16. Если в качестве источника значений


для столбца используется поле подстановки (lookup field),
для ячейки автоматически применяется стиль комбинированного списка
Если в качестве источника значений для столбца используется поле подстановки (look-
up field), определенное в наборе данных (постоянное поле), то ячейки столбца также будут
работать как комбинированный список, даже если список значений свойства PickList пуст.
В приложении, изображенном на рис. 3.14, источником значений для столбца Категория
служит поле Category, имеющее тип длинное целое (long integer). В предыдущей главе
приводился пример создания на основе поля Category поля подстановки CategoryName.
Если для первого столбца вместо поля Category в качестве значения свойства FieldName
указать поле подстановки CategoryName и очистить список свойства PickList, то из спи-
ска подстановки в ячейке можно будет выбирать не абстрактные числа, а наименования
категорий (см. рис. 3.16).
Глава 3. Использование механизма ВОЕ (продолжение) 139

Значение cbsNone свойства ButtonStyle предполагает, что ячейка в любом случае будет
вести себя как обычный элемент редактирования.
Если для свойства ButtonStyle задать значение cbsEllipsis, то при переходе в режим ре-
дактирования, ячейки соответствующего столбца будут снабжаться кнопкой построителя
(кнопка с троеточием —Ellipsis). При нажатии этой кнопки (см. рис. 3.17) будет генериро-
ваться событие OnEditButtonClick компонента TDBGrid. Кнопкой построителя и событи-
ем OnEditButtonClick удобно пользоваться, чтобы вызвать на экран диалоговое окно, при
помощи которого, например, можно не только выбрать нужное значение, но и добавить
к списку новое (предварительно добавив новую запись в соответствующую таблицу).

Категория Товар j Ценп |*j


>1 Samsung ML-45M las.ous1^
1 Samsung Mi_-1 21 0 "03.00$ .
1 Epson EPL-59GQL 239 00*
1 Canon LBP-810 21500$ ,
----- --•• :.. -^

Рис. 3.17. Если для свойства ButtonStyle задать значение cbsEllipsis,


ячейки столбца будут снабжаться кнопкой построителя (Ellipsis)
Поясним сказанное на примере. Обратившись к форме, изображенной на рис. 3,16, мож-
но сделать следующие выводы. Форма предназначена для отображения значений трех полей
таблицы Items, причем вместо значений поля Category отображаются значения из сформи-
рованного на его основе поля подстановки (поля Category таблицы Category). Кроме того,
форма предоставляет возможность модифицировать значение поля Category, выбрав его из
списка, прикрепленного к ячейке. При выборе из списка имени категории соответствующее
значение первичного ключа присваивается полю Category текущей записи.
Все это работает хорошо до тех пор, пока не понадобится ввести новую категорию,
информация о которой отсутствует в таблице. В этом случае удобнее пользоваться кноп-
кой построителя; при нажатии на нее на экране отображается форма, предоставляющая
интерфейс для управления содержимым таблицы Category.
Для иллюстрации сказанного создадим приложение-пример. Откройте новое приложе-
ние и создайте модуль данных (File \ New \ Data Module). Для свойства Name модуля дан-
ных задайте значение DModl, а в сам модуль поместите два объекта ТТаЫе и два объекта
TDataSource, оставив их имена по умолчанию. Объект Tablel подключите к таблице Items
базы данных MyData, а объект Tablel — к таблице Category той же базы. В качестве зна-
чения свойства DataSet объекта DataSourcel укажите Tablel, а для свойства DataSet объек-
та DataSource2 задайте значение ТаЫе2.
Далее, дважды щелкнув на изображении объекта ТаЫе2, добавьте в список постоянных
полей все поля из таблицы Category. Точно так же добавьте в список постоянных полей
объекта Tablel все поля таблицы Items. В заключение нужно создать поле подстановки
(lookupfield), основанное на поле Category. Для этого, дважды щелкнув на значке объекта
Tablel, отобразите на экране редактор полей и выберите из контекстного меню пункт New
Field. В диалоговом окне определения нового поля задайте следующие значения:
Q Name — CategoryName.
U Type — String.
Q Size —25.
Q Field Type — Lookup.
Q Key Fields — Category.
140 Borland C++ Builder 6. Разработка приложений баз данных

Q Dataset—Table2.
G Lookup Keys — CategoiyNo.
G Result Field—Category.
В результате этих действий в наборе данных Tablel будет определено поле подстанов
ки с именем CategoryName.
Теперь перейдем к созданию формы Forml. Прежде всего, задайте для ее заголовю
(свойство Caption) какое-нибудь подходящее значение, например Items. После этого по
местите на форму компонент TDBGrid, оставив для него имя по умолчанию (DBGridl)
Для свойства DataSource сетки укажите значение: DMod l-> DataSource 1. Щелкнув нг
кнопке построителя свойства Columns сетки DBGridl, введите определения трех колонок
Последовательно задайте для их свойств FieldName значения CategoryName, Item и Price.
Для первой колонки (CategoryName) в качестве значения свойства ButtonStyle укажите
cbsEllipsis. Тогда ячейки этой колонки в режиме редактирования будут снабжаться кноп-
кой построителя.
Далее следует настроить заголовки всех трех колонок. Для доступа к свойствам заго-
ловка нужно развернуть свойство Title каждого из объектов TColwnn. Для колонок сетки,
изображенной на рис. 3.16, заданы следующие значения свойств (объекта-свойства Title):
Q свойство Caption — Категория, Товар и Цена соответственно;
G свойство Alignment — taCenter;
Q свойство Font, подсвойство Color — clMaroon',
1_J свойство Font, подсвойство-объект Style, подсвойство fsBold— true.
В ответ на'нажатие кнопки построителя, которая будет появляться в ячейках первой
колонки сетки при переходе в режим редактирования, на экране должна отображаться
форма, предоставляющая интерфейс для манипуляций записями таблицы Category.
Создайте новую форму (меню File \ New \ Form) и добавьте ее к текущему проекту, со-
хранив в том же каталоге, что и остальные файлы проекта. Для заголовка новой формы за-
дайте значение Categories. В верхней части формы разместите компонент TDBNavigator,
чуть ниже — компонент TDBGrid, еще ниже — две кнопки (компонент TButton). Для
имен всех объектов оставьте значения по умолчанию. Для свойств DataSource объектов
DBNavigatorl и DBGridl задайте значение DModl->DataSource2.
На следующем шаге нужно настроить сетку DBGridl формы Form2. Откройте редак-
тор столбцов (щелкнув на кнопке построителя свойства Columns) и добавьте определения
двух столбцов. Первый столбец свяжите с полем Category, второй — с полем Description
таблицы Category. Для заголовков задайте подписи Категория и Описание соответствен-
но. Остальные свойства заголовков столбцов настройте так же, как и для столбцов сетки
DBGridl формы Forml.
Форма Form2 должна отображаться на экране при щелчке мышкой на кнопке построи-
теля в ячейке первого столбца сетки. Чтобы обеспечить это, нужно соответствующим об-
разом обработать событие OnEditButtonClick объекта DBGridl. В первую очередь в обра-
ботчике этого события нужно проверить, в ячейке какого столбца была нажата кнопка
построителя. Это важно, так как стиль cbsEllipsis мог быть задан для нескольких столбцов
сетки. В таком случае удобно пользоваться свойством SelectedField компонента TDBGrid.
В следующей строке приведено определение свойства SelectedField:
property Db::TField* SelectedField =
{read=GetSelectedField, write=SetSelectedField};
Глава 3. Использование механизма ВОЕ (продолжение) 141

Свойство SelectedField содержит указатель на объект TField, соответствующий выде-


ленной в сетке ячейке. Ниже приведен код обработчика события OnEditButtonClick объ-
екта DBGridl формы Form/:
void _ fastcall TForml : : DBGridlEdi tButtonClick(TObject *Sender)
{
TField* SelF;
SelF=DBGridl->SelectedField;
if (SelF)
if (SelF->FieldName=="CategoryName")
{
TForm2* Form2 = new TForm2(Application) ; ,
Form2->ShowModal() ;
Form2->Free() ;

В первой строке кода объявляется переменная SelF типа ТТаЫе*, а затем этой пере-
менной присваивается значение свойства SelectedField. Далее проверяется, выделена ли
какая-нибудь ячейка сетки (в этом случае значение переменной SelF не должно равняться
NULL). Если переменная SelF содержит допустимую ссылку на поле набора данных, про-
веряется его имя. Если имя поля — CategoryName, создается объект формы Form2, затем
эта форма открывается в модальном режиме. Когда форма будет закрыта, соответствую-
щая объектная переменная удаляется.
Чтобы приведенный выше код обработчика события OnEditButtonClick работал пра-
вильно, нужно предотвратить автоматическое создание формы Form!. Для этого нужно
отобразить на экране диалоговое окно параметров проекта (меню Project \ Options) и перей-
ти на вкладку Forms. Затем в списке автоматически создаваемых форм (Auto-create forms)
следует выбрать Form2 и нажать кнопку со стрелкой вправо (>). В результате Form2 будет
переведена в список доступных форм (Available forms).
В заключение нужно правильно настроить форму Form2. В частности, желательно,
чтобы после загрузки формы активной записью сетки была запись о категории, для ко-
торой и была вызвана форма. Например, если кнопка построителя нажата в ячейке за-
писи о товаре из категории Процессоры, то сразу после загрузки формы Form2 активной
должна быть запись о категории Процессоры. Для этого нужно воспользоваться событием
OnCreate формы Form2. Ниже приведен код обработчика этого события:
void _ fastcall TForm2 : : FormCreate(TObject *Sender)
{
AnsiString ValString;
ValString=Forml->DBGridl->SelectedFi eld- >AsSt ring;
if (! (ValString.IsEmptyO))
{
TLocateOptions SearchOptions ;
DModl->Table2->Locate("Category", ValString, SearchOptions);

В этом обработчике события OnCreate снова используется свойство SelectedField.


С его помощью переменной ValString присваивается значение ячейки сетки DBGridl,
142 Borland C++ Builder 6. Разработка приложений баз данных

выделенной в форме Forml. Если это значение не пусто, то при помощи мето/
Locate указатель активной записи набора данных Table! (таблицы Category) nepi
водится на запись, значение поля Category которой совпадает со значением пер<
менной ValString.
В нижней части формы Form2 размещены две кнопки (компонент TButtori). При н<
жатии одной из них выбранное значение категории (значение первичного ключа) должн
быть присвоено полю Category текущей записи таблицы Items, после чего форма Form
должна быть закрыта. При нажатии второй кнопки форма Form2 должна быть просто за
крыта. Для первой кнопки задайте подпись ОК, для второй — Cancel. В тело обработчик
события OnClick кнопки ОК введите следующий текст:
void fastcall TForm2::BOKClick(TObject *Sender)
{
TDataSet* GridDataSetl;
TDataSet* GridDataSet2;
GridDataSetl=Forml->DBGridl->DataSource->DataSet;
GridDataSet2=DBGridl->DataSource->DataSet;
if(GridDataSetl && GridDataSet2)
{
GridDataSetl->Edit();
GridDataSetl->FieldByName("Category")->Value=
GridDataSet2->FieldByName("CategoryNo")->Value;
GridDataSetl->Post() ;
}
Close 0;
}
В первых двух строках кода обработчика события OnCreate объявлены две пере-
менные типа TDataSet*. В следующих двух строках кода переменной GridDataSetl
присваивается указатель на набор данных, к которому подключена сетка из формы
Forml (объект Table!), а переменной GridDataSetl — указатель на набор данных для
сетки из формы Form2 (объект ТаЫе2). В принципе, без этих громоздких действий
можно обойтись, но только если вы точно уверены, что оба объекта TDBGrid подклю-
чены к источникам данных. Именно это проверяется в условном операторе // Неплохо
было бы еще проверить имена источников данных и при несовпадении предусмотреть
соответствующую реакцию.
Если обе переменные типа TDataSet* ссылаются на нужные наборы данных, то на-
бор, связанный с таблицей Items, переводится в режим редактирования, полю Category
присваивается значение первичного ключа текущей записи набора данных, связанного
с таблицей Category, и вызывается метод Post. В заключение форма Form2 закрывается
при помощи вызова метода Close. Обработчик события кнопки Cancel должен содержать
только один оператор — вызов метода Close.
Откомпилируйте приложение и запустите его на выполнение. Щелкните на кнопке по-
строителя одной из ячеек столбца CategoryName (Категория). В результате на экране бу-
дет отображена форма Form2 (см. рис. 3.18). Используя элементы управления этой формы,
вы можете добавить в таблицу Category новый элемент, выбрать любой элемент из сетки
DBGridl, а затем, нажав кнопку ОК, присвоить соответствующее значение категории по-
лю Category таблицы Items.
Глава 3. Использование механизма ВОЕ (продолжение) 143

К. „.гари.)
Принтеры St. ± - Ч* -I-! I I ?
К.Г»1(Ц-(1|1И« >;:fMlMf! '• _*]
Принтеры -lEi
> Принтеры Принтеры струйные, матричные. пвэер1
Принтеры О
Сканеры Сканеры ппаншетные
. Принтеры Вг,
Принтеры Efu;
; Процессоры Процессоры Intel, Aind и VIA
, Принтеры Ef;-;
Модули памяти Моаули памяти DIMM. DIMM DDR и RlMM ' •.
'( Принтеры НС/
Видеокарты Виаеокарты от различных производите
,;:. Принтеры HI :-
Мониторы ЭПТ- и ЖКИ-мониторы i
^Принтеры HI
iLJ ±Г
OK " """| Cancel

Рис. 3.18. Используя кнопку построителя, можно организовать достаточно сложный


интерфейс для ввода значений в ячейку сетки (компонент TDBGrid)
Излишне напоминать, что всю настройку объектов TColumn, проводимую в режиме
конструктора, с тем же успехом можно производить и в режиме выполнения приложения.
Для доступа к столбцам сетки в режиме выполнения приложения предназначено индек-
сированное свойство-коллекция Columns типа TDBGridColumns. Как и любая коллекция,
класс TDBGridColumns обладает двумя свойствами, позволяющими осуществлять итера-
цию по всем элементам коллекции: Items (индексированный массив элементов) и Count
(количество элементов в коллекции). Ниже приведен отрывок кода, демонстрирующий
принципы использования этих свойств. В этом отрывке осуществляется итерация по всем
колонкам сетки DBGridl и для каждой колонки в список ListBoxl добавляется наименова-
ние поля, ассоциированного с ней.
for(int i=8; i <DBGridl->Columns->Count ;

Li stBoxl->I terns- >Add(DBGridl->Columns->I terns [i] ->FieldName) ;

Класс TDBGridColumns имеет стандартный для классов-коллекций набор методов, по-


зволяющих управлять составом коллекции. Метод Add создает новый объект TColumn, до-
бавляет его в коллекцию и возвращает указатель на него. Используя этот указатель, мож-
но настроить новый объект-столбец нужным образом. При помощи метода Insert можно
создать новый объект TColumn и вставить его в коллекцию столбцов в позицию, заданную
индексом. Как и в случае метода Add, метод Insert возвращает указатель на новый объект
TColumn. Метод Delete позволяет удалить элемент коллекции, заданный передаваемым
в качестве аргумента индексом, а при помощи метода Clear можно удалить все объекты
TColumn сразу, очистив тем самым коллекцию TDBGridColumns.
Определения всех объектов TColumn можно сохранить в файле. Это можно сделать
при помощи метода SaveToFile. Метод принимает единственный аргумент — имя файла,
в котором будет сохранена информация из коллекции Columns. Обратную задачу решает
метод LoadFromFile: с его помощью можно из файла загрузить в объект TDBGridColumns
данные, предварительно сохраненные при помощи метода SaveToFile. Используя эту пару
методов, можно один и тот же объект TDBGrid подключать к разным источникам данных,
по мере необходимости подгружая определения нужных столбцов. Соответствующая пара
методов чтения из потока и записи в поток — LoadFromStream и SaveToStream.
Класс TDBGridColumns имеет еще два интересных метода, позволяющих воздей-
ствовать на набор столбцов целиком. Вызов метода RestoreDefaults восстанавливает для
свойств всех столбцов, входящих в коллекцию, их значения по умолчанию. Тем самым
144 Borland C++ Builder 6. Разработка приложений баз данных

отменяются все изменения настройки набора столбцов. Метод RebuildColumns удаляе


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

Параметры сетки
Еще одно важное интегральное свойство компонента TDBGrid, управляющее различ
ными аспектами поведения и отображения сетки на экране, — свойство Options (параме-
тры]. Это свойство-множество (Set)', оно может содержать до 13 элементов перечислимо-
го типа TDBGridOption. Как обычно в таких случаях, чтобы включить элемент в СОСТЗЕ
параметров сетки в режиме конструктора, нужно в инспекторе объектов задать для неге
значение true. Ниже приведено краткое описание каждого из элементов.
Q dgEditing — разрешается редактировать данные в сетке. Этот элемент игнорируется,
если в состав параметров сетки входит элемент dgRowSelect.
LJ dgAlwaysShowEditor — сетка всегда будет находиться в режиме редактирования, так
что пользователю не нужно нажимать клавишу Enter или F2, чтобы перейти к редак-
тированию данных. Элемент dgAlwaysShowEditor игнорируется, если в набор параме-
тров сетки не входит элемент dgEditing или входит элемент dgRowSelect.
G dgTitles — если этот элемент присутствует в наборе параметров, то в верхней части
столбцов сетки будут отображаться заголовки.
Q dglndicator — наличие среди параметров этого элемента приводит к отображению в левой
части сетки столбца серых кнопок, на одной из которых — индикатор активной записи.
Q dgColumnResize — обеспечивает возможность перемещать столбцы и менять их раз-
меры (перетаскивая правую границу заголовка столбца).
О dgColLines — отображаются линии между столбцами в сетке.
О dgRowLines — отображаются линии между строками в сетке.
О dgTabs — можно перемещаться по ячейкам сетки при помощи клавиш Tab или Shift-
Tab. Если элемента dgTabs нет в наборе параметров сетки, при нажатии клавиши Tab
(Shift-Tab) фокус ввода покинет сетку, перейдя на следующий элемент управления (при
использовании комбинации клавиш Shift-Tab — на предыдущий).
Q dgRowSelect — щелчок мышкой в любой ячейке приводит к выделению целой строки,
содержащей эту ячейку. Если этот элемент присутствует в наборе параметров сетки,
редактировать данные невозможно. В этом случае элементы dgEditing к dgAlwaysShow-
Editor игнорируются.
О dgAlwaysShowSelection — выделенные ячейки будут отображаться с прямоугольником
выделения, даже если сетка теряет фокус ввода.
О dgConfirmDelete — если пользователь удаляет строку в сетке, нажимая комбинацию кла-
виш Ctrl + Delete, на экране отобразится диалоговое окно для подтверждения удаления.
Q dgCancelOnExit — если пользователь добавил новую запись и не ввел в нее никаких
данных, то при переходе фокуса ввода с сетки на другой элемент управления формы
запись не сохраняется в наборе данных. Это предотвращает случайный ввод в набор
данных пустой записи.
Q dgMultiSelect— разрешается выбирать в сетке сразу несколько записей (множественный
выбор). Для выбора нескольких записей, наряду с мышкой используется клавиша Ctrl.
Глава 3. Использование механизма ВОЕ (продолжение) 145

Понять смысл использования каждого из описанных выше элементов проще всего


на примере. Создайте новое приложение и расположите в верхней части пустой формы
панель (компонент ТРапеГ), а на ней — тринадцать флажков (TCheckBox) в три ряда.
Каждый из этих флажков будет представлять один из элементов параметров сетки, поэто-
му в качестве подписи флажков следует последовательно задать наименования элементов,
предварительно удалив префикс dg (имеется в виду свойство Options). Последовательно
задайте для свойства Tag флажков значения от 1 до 13. Далее следует очистить свойство
Caption объекта TPanel. Можно также задать для свойств Bevellnner и BevelOuter панели
значения bvLowered и bvRaised соответственно.
Ниже панели поместите компоненты TDBGrid, TTable и TDataSource. Объект DBGridl
подключите к DataSourcel, а объект DataSourcel — к объекту Table 1. Набор данных Table!
подключите к таблице Items базы данных MyData и активизируйте его. Подровняйте все
элементы управления так, чтобы форма выглядела более-менее прилично.
Чтобы было удобнее пользоваться набором флажков, нам понадобится массив
элементов типа TCheckBox*. Кроме того, в дальнейшем нам будет нужна переменная
IsFormCreate логического типа, принимающая значение true после того, как форма будет
создана. Перейдите на вкладку заголовочного файла модуля формы (Unit.h) в редакторе
кода и введите следующие объявления в разделе private класса TForml:
bool IsFormCreate;
TCheckBox* CBOpt[13] ;
Сразу после отображения главной формы приложения на экране группа флажков
должна в точности отражать состояние набора параметров сетки. Для этого в обрабо-
тчике события OnCreate формы свойству Checked каждого из флажков нужно присвоить
значение true, если соответствующий элемент присутствует в наборе параметров сетки,
и false — в противоположном случае. Однако прежде нужно инициализировать массив
CBOpt, объявленный в классе TForml. Для этого можно воспользоваться итерацией по
всем элементам управления формы. Каждый элемент управления проверяется на принад-
лежность к типу TCheckBox*, и в случае успеха соответствующий указатель присваива-
ется очередному элементу массива CBOpt. Ниже приведен отрывок кода, выполняющий
описанные действия:
i n t j=0;
for(int i=Q; IComponentCount ; i++)
{
TCheckBox* CBox=dynamic_cast<TCheckBox*> (Components [i ] ) ;
if (CBox)
{
CBOpt [j]=CBox;

Теперь несложно организовать инициализацию флажков значениями, отражающими


состояние набора параметров сетки по умолчанию. Ниже приведен соответствующий от-
рывок кода:
for(int i=8; i<13; -i++) CBOpt [i] ->Checked=
DBGridl->Options.Contains((TDBGridOption) (CBOpt [i] -
146 Borland C++ Builder 6. Разработка приложений баз данных

Сначала в этом отрывке кода в теле цикла при помощи свойства Tag очередного флажк
определяется его порядковый номер (считая от 0), а затем этот номер приводится к тип
TDBGridOption. Это можно сделать, так как в таком же порядке определены констант]
перечислимого типа TDBGridOption. Затем проверяется наличие соответствующего эле
мента в наборе параметров сетки, а результат присваивается свойству Checked очередног
флажка (метод Contains множества Set возвращает значение true, если указанный элемен
присутствует во множестве).
Чтобы предусмотреть отклик на изменение состояния одного из флажков, нужно вое
пользоваться его событием OnClick. Так как все флажки имеют различные значения свой
ства Tag, достаточно одного обработчика события. Создайте обработчик события OnClici
для одного из флажков (например, для первого) и свяжите с ним события OnClick все?
остальных флажков. В обработчике события прежде всего необходимо привести аргумет
Sender к типу TCheckBox*, чтобы удостовериться, что событие произошло именно дл?
флажка. Если это так, следует проверить значение свойства Checked флажка. Если фла-
жок установлен, соответствующий элемент (константа типа TDBGridOption) добавляется
в набор параметров сетки, иначе — удаляется. Константу получают, приведя уменьшенное
на единицу значение свойства Tag флажка (так как константы нумеруются с нуля), к типу
TDBGridOption. Ниже представлен отрывок кода, выполняющий описанные действия:
TCheckBox* TCB;
TDBGridOptions Opt=DBGridl->Options;
TCB=dynamic_cast<TCheckBox*>(Sender);
if(TCB)
{
if(TCB->Checked) DBGridl->Options = Opt
« ((TDBGridOption)(TCB->Tag-l));
else DBGridl->Options = Opt
» ((TDBGridOption)(TCB->Tag-l));
-}
Однако следует иметь в виду, что событие OnClick будет происходить не только тогда,
когда пользователь будет менять значения флажков при помощи мышки или клавиатуры,
но и при программном изменении значения свойства Checked. Таким образом, обработчик
события OnClick будет вызван тринадцать раз при обработке события OnCreate формы.
Чтобы избежать этого, используется переменная логического типа IsFormCreate, объявленная
в разделе private класса TForml. В первой строке обработчика события OnCreate формы этой
переменной нужно задать значение false, а в последней строке обработчика -— значение true.
Итак, на момент инициализации свойства Checked флажков переменная IsFormCreate будет
иметь значение false. Тогда, введя в первой строке обработчика события OnClick:
if (! IsFormCreate) return;
удастся избежать излишнего выполнения его операторов.
Полный текст описанного здесь тестового приложения приведен в листингах 3.3 и 3.4.
Примерный вид приложения в процессе выполнения приведен на рис. 3.19.
Листинг 3.3. Файл заголовка приложения GridOptionsDemo
//
#ifndef UnitlH
#defme UnitlH
Глава 3. Использование механизма ВОЕ (продолжение) 147

#include <Classes .hpp>


#include «Controls .hpp>
#include <5tdCtrls.hpp>
#include <Forms.hpp>
«include <DB.hpp>
#include <DBGrids .hpp>
«include <DBTables.hpp>
«include <ExtCtrls.hpp>
«include <Grids.hpp>
// ------- ............ - ..... ---------------------
class TForml : public TForm
{
_ published: // IDE-managed Components
TDBGrid «DBGridl;
liable "Tablel;
TDataSource *DataSourcel;
TPanel *Panell;
TCheckBox *CBEditing;
TCheckBox *CBAlwaysShowEditor ;
TCheckBox *CBTitles;
TCheckBox *CBIndicator ;
TCheckBox *CBColumnResize;
TCheckBox *CBColumnLines;
TCheckBox *CBRowLines;
TCheckBox *CBTabs;
TCheckBox *CBRowSelect ;
TCheckBox "CBAlwaysShowSelection;
TCheckBox "CBConfirmDelete;
TCheckBox *CBCancelOnExit ;
TCheckBox "CBMultiSelect ;
void _ fastcall FormCreate(TObject *Sender) ;
void _ fastcall CBEditingClick(TObject *Sender)
private: // User declarations
bool IsFormCreate ;
TCheckBox* CBOpt[l*] ;
public: // User declarations
_ fastcall TForml(TComponent* Owner);

extern PACKAGE TForml *Forml;


// ---- ........ ------ ........
#endif
Листинг З.4. Главный модуль приложения GridOptionsDemo

«include <vcl .h>


«pragma hdrstop
«include "Unitl.h"
148 Borland C++ Builder 6. Разработка приложений баз данных
//
#pragma package(smart_ini t)
#pragma resource "*.dfm"
TForml *Forml;
// __________________ _
_ fastcall TForml : :TForml (TComponent* Owner)
: TForm(Owner)

II ___________________________________
void _ fastcall TForml: : FormCreate(TObject *Sender)
{
int j=0;
IsFormCreate=false;

for(int i=0; iComponentCount ; i

TCheckBox* CBox=dynamic_cast<TCheckBox*> (Components [i] ) ;


if (CBox)
{
CBOpt[j]=CBox;

•for(int i=0; i!3;


CBOpt[i]->Checked=
DBGridl->Options . Contains (
(TDBGridOption) (CBOpt [i] ->Tag-l) ) ;

IsFormCreate=true ;
}
II
void _ fastcall TForml: :CBEdi tingClick(TObject *Sender)
{
if ( ! IsFormCreate) return;
TCheckBox* TCB;
TDBGridOptions Opt=DBGridl->Options ;
TCB=dynamic_cast<TCheckBox*> (Sender) ;
if(TCB)
{
if (TCB->Checked) DBGridl->Options = Opt
« ((TDBGridOption) (TCB->Tag-l) ) ;
else DBGridl->Options = Opt
» ((TDBGridOption) (TCB->Tag-l) ) ;
Глава 3. Использование механизма ВОЕ (продолжение) 149
lulsi*
p Column Resize & Tabs • Confirm Delete

Г t^limin liriss Г Row Select K? Cancel On b'xii


. Р Titles

• P indicator F RowLmee Г Always Snow Selection Г Mute Select

IternNo tern. . CetegoQ Descriptor!1 ' -*•)


> 1 Samsung ML-45GO A4. GDI. 03V 2 Мб. 600 dpi до 8 рргт— *
2 Samsung ML-1 210 A4. GDI, 03V 8 Мб, 600 dpi, до 1 2 pp- ;
3 Epson EPL-590QL A GDI, ОЗУ 2(13) Mfj.6DQdpi.no 1J -
A Canon LBP-81 0 A GDI 600 dpi, по В ppm, лоток It
5 Brother HL-1 240 Mono lasei A .03Y2M6,6QQ*6DOapi.ao12pF>r
6 Epson AcuLeseiCI 000 А цвет. ОЗУ 18(256) Мб. BOO dpi/2- j
'? Epson AcuLoser C2000 A . цвет, ОЗУ 64(5! 2) Mb, 600 dpi/2-т]

iU

Рис. 3.19. Приложение GridOptionsDemo после того, как был снят флажок Column Lines

Приемы использования событий сетки


С компонентом TDBGrid связан ряд интересных событий, продуманная обработка ко-
торых поможет придать приложению профессиональный вид. Событие OnEditButtonClick,
наступающее при щелчке мышкой на кнопке построителя в ячейке, уже описывалось ра-
нее, когда речь шла о свойстве Columns сетки. Обзор событий начнем с OnTitleClick.
Событие OnTitleClick происходит при щелчке мышкой на одном из заголовков столбцов
сетки непосредственно после того, как пользователь отпускает левую клавишу. Объявлено
оно следующим образом:
typedef void fastcall ( closure *TDBGridClickEvent)(TColumn* Column);
В обработчик события OnTitleClick передается аргумент типа TColumn, при помощи
которого можно определить, на заголовке какого из столбцов сделан щелчок. Кроме то-
го, с помощью аргумента Column можно проделать необходимые действия или с самим
столбцом, или с его заголовком.
Самый яркий пример использования события OnTitleClick — изменение порядка сор-
тировки записей в ответ на щелчок мышкой на заголовке столбца. Можно назвать много
приложений, использующих подобную практику (в частности, широко известный Windows
Commander и многие другие файл-менеджеры). При щелчке на заголовке столбца, записи
в нем сортируются по значениям. Повторный щелчок на заголовке того же столбца пере-
ключает порядок сортировки на обратный.

Пример использования события OnTitleClick


В качестве примера использования события OnTitleClick можно создать приложение,
реализующее описанную выше технологию. Итак, создайте новое приложение; на форме
разместите сетку (TDBGridl), а также компоненты TDataSource и TQuery. Оставьте для
всех элементов имена, присвоенные им по умочанию. Подключите сетку к источнику
данных DataSourcel, который, в свою очередь, подключите к набору данных Query].
Свойство DatabaseName объекта Query 1 установите в MyData.
То, что в качестве набора данных используется компонент TQuery, а не TTable, можно
объяснить следующим образом. Для сортировки записей в объекте ТТаЫе применяются
индексы. Нужный порядок сортировки можно установить, задав соответствующий ин-
декс при помощи свойства IndexName или IndexFieldNames. Если подходящего индекса
150 Borland C++ Builder 6. Разработка приложений баз данных

нет, его придется создать при помощи метода Addlndex. Когда индекс будет не нужен, ег
следует удалить. Например, таблица Items имеет шесть полей и три постоянных индекс
заданных при ее проектировании. Чтобы можно было задавать сортировку для всех поле
таблицы, нужно при выполнении программы создать еще 9 индексов (так как для каждо!
поля потребуется два индекса: один для сортировки по возрастанию, другой — по убыв;
нию). Кроме того, все операции управления индексами следует проводить для таблищ
открытой в исключительном режиме (свойство Exclusive нужно установить в значеш-
true). В связи со всем вышеизложенным, тестовая программа-пример получится излитш
запутанной. С другой стороны, компонент TQuery позволяет задавать любой порядок ее
ртировки по любому полю или группе полей.
Логика работы тестового приложения такова. Сразу после его загрузки записи набс
ра данных (таблицы Items) будут отсортированы по первичному индексу (поле ItemNc
в порядке возрастания. Для этого необходимо при создании формы задать соответств}
ющую строку оператора SQL в свойстве SQL объекта Query 1 и затем активизировать ег
(открыть набор данных). Чтобы обозначить, по какому из столбцов в данный момен
отсортированы записи, перед подписью заголовка соответствующего столбца будет дс
бавлен или символ «>» (сортировка по возрастанию), или «<» (по убыванию).
Щелчок мышкой на заголовке столбца должен привести к тому, что записи будут отсор
тированы по полю, ассоциированному с этим столбцом. (Будем называть такой столбе
в контексте данного описания активным столбцом сортировки^ Щелчок на заголовке ак
тивного столбца меняет порядок сортировки на противоположный. Соответствующим об
разом меняется также и сигнальный символ в заголовке столбца («>» на «<» и наоборот]
При щелчке на неактивном столбце (т.е. не являющемся активным столбцом сортиров
ки), он становится активным, т.е. по полю, ассоциированному с этим столбцом, буду
отсортированы записи набора данных. Порядок сортировки — по возрастанию. Подпис!
заголовка предыдущего активного столбца восстанавливается в значение по умолчанию
а к подписи заголовка нового активного столбца добавляется символ «>».
В дальнейшем нам понадобятся две вспомогательные переменные. В описание класс;
TForml (в заголовочном файле Unitl.h), в раздел private, введите следующие объявления
private:
AnsiString SQLString;
int Curlndex;
Строка SQLString предназначена для хранения начальной части оператора SQL, ко
торый будет использоваться для формирования запроса объекта Query 1. В переменно?
Curlndex целого типа будет храниться индекс текущего активного столбца сортировю-
в коллекции Columns.
Вся логика программы будет сосредоточена внутри двух обработчиков событий:
OnCreate формы и OnTitleClick сетки DBGridl. При создании формы необходимо про-
извести первоначальную настройку объекта Query! и инициализацию вспомогательных
переменных. Ниже приведен код обработчика события OnCreate формы Forml, выполня-
ющий нужные действия.
void fastcall TForml::FormCreate(TObject *Sender)
{
SQLString=AnsiString("select * from Items");
Curlndex=0;
Queryl->SQL->Clear();
Глава 3. Использование механизма ВОЕ (продолжение) 151

Queryl->SQL->Add(SQLString + " ; " ) ;


Queryl->Acti ve=true;
TColumnTitle* Ttl=DBGr idl->Columns->I terns [0] ->Title;
Ansi String TtlCaption=Ttl->Caption;
Ttl->Caption="> " + TtlCaption;
}
В первой строке обработчика инициализируются переменные SQLString и Curlndex.
Строке SQLString присваивается текст оператора запроса, возвращающего все поля всех
записей таблицы Items. Так как в этом операторе никакая другая сортировка не задана,
то записи будут отсортированы по первичному индексу (поле ItemNo) по возрастанию.
(Подробнее о языке запросов SQL — в одном из следующих разделов.) Переменная
Curlndex получает значение 0. Это подразумевает, что первоначально активным столбцом
сортировки будет нулевой столбец сетки, т.е. столбец ItemNo.
В следующих двух строках иода обработчика настраивается свойство SQL объекта Query 1.
Это свойство имеет тип TString, поэтому используются стандартные методы этого класса.
Метод Clear очищает список строк свойства SQL, а метод Add добавляет к этому списку стро-
ку оператора SQL и в конец этой строки — символ «;». Этим символом по соглашению должен
заканчиваться любой оператор SQL. Затем набор данных Query 1 активизируется.
И в заключение кода обработчика события OnCreate задается новая подпись для
первого столбца сетки (точнее, нулевого). Для этого сначала извлекается подпись заго-
ловка столбца ItemNo, затем она объединяется со строкой «> » (так как первоначально со-
ртировка идет по возрастанию), и затем полученная таким образом строка присваивается
свойству Caption заголовка того же столбца. Пробел, следующий за символом «>», нужен
для большей наглядности.
После того как приложение загрузится в память и на экране отобразится его главная
форма, можно щелкать мышкой на любом из заголовков столбцов, задавая нужный по-
рядок сортировки записей. Это обеспечивается обработчиком события OnTitleClick. Ниже
приведен его код.
void _ fastcall TForml: :DBGridlTi tleClick(TColumn «Column)
{
char fc=*(Column->Title->Caption.c_str()) ;
Ansi String ColField=Column->FieldName;
int ind=Column->Index;
AnsiString OrderByString;
AnsiString CString;
i f ( f c ==

OrderByString=" ORDER BY " + ColField + " DESC;


CString=AnsiString(" ") + ColField;

else
OrderByString=" ORDER BY " + ColField +
CString=AnsiString("> ") + ColField;
}
Query l->Active=f alse;
Queryl->SQL->Clear() ;
Queryl->SQL->Add(SQLString + OrderByString);
152 Borland C++ Builder 6. Разработка приложений баз данных

Queryl->Active=true;
DBGridl->Columns->Items[ind]->Title->Caption = CString;
if(Cur!ndex != ind)
{
TColumn* Col=DBGridl->Columns->Items[Curlndex];
Col->Ti tle->Caption=Col->DisplayName;
Curlndex=ind;
}
Queryl->First() ;
DBGridl->SelectedIndex=ind;
}
В трех первых строках этого отрывка кода объявляются и инициализируются три вспо
могательные переменные. Переменной fc типа char присваивается первый символ подпи
си заголовка столбца Column, для которого произошло событие OnTitleClick. Переменна)
ColField типа AnsiString инициализируется именем поля, ассоциированного со столбцо!^
Column, а переменная ind целою типа получает значение индекса столбца Column в масси-
ве Columns. Одна из основных причин, почему эти переменные используются, в том, чтс
в дальнейшем набор данных QueryI будет закрываться. После повторного его открытия
переменная Column больше не будет ссылаться на допустимый объект. Использование
в этом случае переменной Column приведет к самым непредсказуемым ошибкам, в том
числе ошибкам нарушения доступа (access violation).
Далее объявляются еще две вспомогательные переменные типа AnsiString.
Переменная OrderByString предназначена для формирования заключительной ча-
сти оператора SQL, при помощи которой будет задаваться порядок сортировки.
В переменной CString будет храниться строка, обозначающая порядок сортировки
столбца (символ «>» или «<» и пробел).
На следующем шаге проверяется переменная^/2; (первый символ подписи заголовка). Если
fc содержит символ «>», то щелчок мышкой был произведен на заголовке того же столбца
и порядок сортировки для него был установлен по возрастанию. В этом случае нужно уста-
новить порядок сортировки по убыванию и перед подписью заголовка столбца по умолчанию
добавить строку «< »(символ «<» и пробел). Иначе или для столбца был установлен порядок
сортировки по убыванию, или щелчок был произведен на другом столбце. Независимо от при-
чин нужно для столбца, вызвавшего событие OnTitleClick, установить порядок сортировки по
возрастанию и соответствующим образом настроить подпись его заголовка.
Таким образом, в зависимости от значения переменной fc задаются значения пере-
менных OrderByString и CString. В переменной OrderByString формируется элемент
оператора SQL, в котором задаются имена полей и порядок сортировки. Этим элементом
является предложение ORDER BY (отдельные элементы оператора SQL называются пред-
ложениями). Вслед за ключевым словом ORDER BY указывается имя поля, хранящееся
в переменной ColField, и порядок сортировки для него. Если за именем поля указано
ключевое слово DESC, то порядок сортировки для поля — по убыванию, а если ничего
не указано — по возрастанию. Переменная ColField используется и для формирования
строки CString, так как по умолчанию подпись заголовка столбца совпадает с именем по-
ля, данные которого отображаются в столбце.
Далее следует настроить свойство SQL объекта Query]. Сделать это можно, если набор
данных закрыт. Как только нужная строка оператора запроса сформирована в свойстве
SQL, набор данных Query! можно вновь открыть. Так как переменная Column после за-
Глава 3. Использование механизма ВОЕ (продолжение) 153
крытая и повторного открытия набора данных больше не ссылается на допустимый объ-
ект TColumn, то для задания нужной подписи для заголовка столбца следует воспользо-
ваться свойством Columns сетки и предварительно определенной переменной ind.

ftemNo • Kern JGa;sgorvJDescnpnon ' -!


j НгТ^ДУ^1лЩ|||рЩН||~ 4200 rpm, UDMA 100, 2.5" (дг™^
"3" (1 1 6") LG StudioWorks 563N 0 28 LR-NI. 1 024x763 (61 H:),
185 5" (1 3.8") LG SUidioWorks 575E 0.28 LR-ГЛ
5" Samsung 15- В LR-NI. Pivot Block TFT LCD:'
', ' 5"Samsungl51BM Pivot LR-NI. TFT LCD актив
218 5" Samsung 1 51 S.297 Pivot TFT LCD эктивнач м
1Э1 5" Samsung 550B ;0.24LR-Ni, 1280»:1024(60Нг) - '
90 5"SamsungS51S 0.24 LR-NI. 1 024x768 (68 Нг),
169 6" Sarntton Ь6Е LR-NI. 1024x768 (60 Нг). MPF
221 5" Sony SDM-N50 LCD -0297 LR-NI. TFT LCD а*.тив. . ,
:

7" (16'T Samsung 753DFX, ; 0.2C LR-NI. OSD digital cortrc


-••', 7" (16'T Samsung 753S 7 0 23 LR-NI. OSD digital conirc ^ j

iU Jj

Рис. 3.20. Набор данных отсортирован по полю Item в порядке возрастания


И последний важный момент. Если сортировка установлена для нового столбца, подпись
заголовка предыдущего столбца нужно восстановить в ее значение по умолчанию. Для про-
верки факта смены столбца сортировки используются переменные ind и Curlndex.
В заключение указатель текущей записи переводится на первую запись набора данных,
а курсор ввода — на ячейку отсортированного столбца. Откомпилируйте приложение
и запустите его на выполнение. На рис. 3.20 изображено тестовое приложение в момент,
когда набор данных отсортирован по полю Item в порядке возрастания.

Оформление отдельных ячеек сетки


при помощи события OnDrawColumnCell
Компонент TDBGrid имеет достаточно удобное событие OnDrawColumnCell, при помощи
которого можно управлять рисованием отдельных ячеек (или строк) сетки при выполнении
приложения. Событие OnDrawColumnCell происходит всякий раз, когда необходимо нарисо-
вать или перерисовать одну или несколько ячеек сетки. В частности, это событие происходит
при отображении на экране формы, содержащей сетку, для всех видимых ячеек. Кроме того,
оно может произойти при прокрутке сетки, при перемещении курсора ввода с одной ячейки на
другую, а также после того как перекрывающий одну или несколько ячеек объект был убран.
Обработчик этого события объявлен следующим образом:
typedef void fastcall ( closure *TDrawColumnCellEvent)
(System::TObject* Sender, const Types::TRect &Rect, int DataCol,
TColumn* Column, Grids::TGridDraw5tate State);
Первый аргумент — Sender, — передаваемый в тело обработчика, имеет тип указателя
на объект TDBGrid, для которого произошло событие. Прежде чем применить перемен-
ную Sender в этом качестве, необходимо воспользоваться динамическим приведением ее
к типу TDBGrid:
TDBGrid* grid=dynamic_cast<TDBGrid*>(Sender);
Следующий аргумент — переменная Rect типа TRect -указывает положение прямоу-
гольника, занимаемого текущей ячейкой, на канве сетки. Используя эту переменную, мож-
но организовать самостоятельный вывод в ячейку текстовой и графической информации,
а также задать для нее любое оформление.
154 Borland C++ Builder 6. Разработка приложений баз данных

Аргумент целого типа DataCol содержит индекс столбца, которому принадлежа


текущая ячейка, в коллекции Columns. Наличие этого аргумента не совсем понятно, та
как информацию об индексе столбца можно почерпнуть из свойства Index следующей
аргумента — переменной Column типа TColumn*. Переменная Column ссылается на o6i
ект-столбец, содержащий текущую ячейку.
Последний аргумент обработчика события OnDrawCoIumnCell, — State —- имеет ти
множества TGridDrawState и может содержать дополнительную информацию о состояни
текущей ячейки. Переменные этого типа могут содержать один из следующих элементе!
Q gdSelected—текущая ячейка выбрана. Определив это, вы можете оформить выделени
ячейки по-своему.
G gdFocused— текущая ячейка имеет фокус ввода. Отображение фокуса ввода можн<
дополнить каким-нибудь нестандартным образом.
U gdFixed— текущая ячейка находится в фиксированной области сетки.
Замечание
Компонент TDBGrid имеет еще одно похожее событие - OnDrawDataCe//. Оно проис-
ходит, если свойство State коллекции Columns имеет значение csDefault перед событием
OnDrawCoIumnCell. Однако это событие считается устаревшим, и пользоваться им не ре-
комендуется. Оно оставлено только для обратной совместимости.

В качестве примера использования события OnDrawCoIumnCell создадим тестовое


приложение, отображающее в сетке все столбцы и строки таблицы Items, окрашивая при
этом фон строк в разные цвета через одну (так называемая зебра). Такой прием часто ис-
пользуется для лучшего визуального восприятия и разделения строк. Как всегда создайте
новое приложение и расположите на его главной форме сетку (TDBGrid), а также объекты
TTable и TDataSource. Для всех объектов оставьте их имена по умолчанию. Сетку подклю-
чите к Tablel (посредством объекта DataSourcel), а объект Tablel — к таблице Items базы
данных MyData и откройте набор данных.
Создайте обработчик события OnDrawCoIumnCell для сетки и введите в Него следую-
щий код:
void fastcall TForml::DBGridlDrawColumnCell(TObject *Sender,
const TRect &Rect, int DataCol, TColumn *Column,
TGridDrawState State)
{
static TColor ColCol=clSkyBlue;
if(DataCol == 0)
{
if(ColCol == clSkyBlue) ColCol=clMoneyGreen;
else ColCol=clSkyBlue;
}
DBGridl->Canvas->Brush->Color=ColCol;
DBGridl->Canvas->FillRect(Rect);
DBGridl->DefaultDrawColumnCell(Rect,DataCol,Column,State);
}
В первой строке кода обработчика объявляется статическая переменная Со/Со/, име-
ющая тип TColor. Статичность переменной ColCol важна, так как ее значение не должно
Глава 3. Использование механизма ВОЕ (продолжение) 155
изменяться между вызовами обработчика события. Инициализируется эта переменная
значением цвета clSkyBlue.
Цвет строки в сетке должен меняться перед тем, как будет отрисовываться ее первая
ячейка. Это легко определить при помощи аргумента DataCol: если он равен 0, значит,
в данный момент отрисовывается первая ячейка строки. В этом случае нужно поменять
цвет clSkyBlue на цвет clMoneyGreen (или наоборот). (Пару цветов можно подбирать или
на свой вкус, или на вкус заказчика.)
Далее, полученный цвет устанавливается в качестве цвета кисти для канвы сетки.
Именно этим цветом в следующей строке кода заполняется прямоугольник Rect (иначе
говоря, отрисовывается фон текущей ячейки). Делается это при помощи метода FillRect
канвы сетки. И в последней строке обработчика события OnDrawColumnCell вызывается
метод сетки DefaultDrawColumnCell, в функции которого входит отрисовка текста в ячей-
ке. Если вы не организовали вывод текста собственными силами, нужно обязательно вы-
звать этот метод в конце кода обработчика события OnDrawColumnCell, иначе все ячейки
будут пустыми.
Замечание
На последовательность вывода текста в ячейку влияет значение логического свойства
DefaultDrawing. Если это свойство для сетки имеет значение true (так по умолчанию),
отрисовка текста в ячейке будет осуществляться стандартными средствами до вызова
обработчика события OnDrawColumnCell. Таким образом, в приведенном выше примере
текст в ячейках будет вначале отрисован черным цветом по белому фону. Затем в обра-
ботчике события OnDrawColumnCell ячейка будет окрашена заданным цветом и текст бу-
дет отрисован снова методом DefaultDrawColumnCell. Если для свойства DefaultDrawing
задать значение false, то вывод текста нужно организовать в обработчике события
OnDrawColumnCell самостоятельно (например, вызвав метод DefaultDrawColumnCell).

Откомпилируйте приложение и запустите его на выполнение. Примерный результат


изображен на рис. 3.21.

йЭ
Vw^nivjIJ^t'ipUii,-

MGBI.U <'?s,ir, миф n


2 Samsung MH21Q At 001. ОЗУ В Мб, 600 dpi. д

М GPL 600 dpi до 8 ppm. лот


AiG3V2M6.68CxS3Cdpi.fi03

Рис. 3.21. Объект TDBGrid с попеременным окрашиванием строк (зебра)

|l ri
•Vj }1Ч" ji •> " »| »~'
1 АИ. GDI ОЗУ г ма, ш dpi, до s p
1 А4, GQI,'ОЗУ SM&, 650 dpi, ДО! г
1 А4. GDI. ОЗУ 2(13) Мб. Ш dpi, дс
1 Л-1. GUI. ЫИ1 dpi. дм И ИМ'", nini..
э А< ОЗУ гмв.

Рис. 3.22. Если менять цвет ячеек, привязываясь


только к смене строки данных при отображении, возникнут проблемы при прокрутке
На первый взгляд все кажется нормальным, однако, если прокрутить содержимое сет-
ки в горизонтальном или вертикальном направлении, получится довольно неприглядная
156 Borland C++ Builder 6. Разработка приложений баз данных

картина (см. рис. 3.22). Этот сбой вызван тем, что при отрисовке новых ячеек изменен!
цвета или происходит неправильно, или не происходит совсем. Избежать этого можн
если менять окраску ячеек в зависимости от конкретной записи. Например, можно пров
рять значение ключевого поля ИетМои, в зависимости от его четности, использовать оди
из двух цветов. Но если удалить из таблицы несколько записей или применить к таблиг
фильтр, может случиться, что несколько подряд идущих строк будут окрашены одинаю
во. Тогда можно воспользоваться свойством RecNo компонента ТТаЫе. Это свойство С(
держит номер текущей записи. Ниже приведен исправленный вариант кода обработчик
события OnDrawColumnCell.
void fastcall TForml::DBGridlDrawColumnCell(TObject *Sender,
const TRect &Rect, int DataCol, TColumn *Column,
TGridDrawState State)

int ind=Tablel->RecNo & 1;


DBGridl->Canvas->Brush->Color=ColColfind];
DBGridl->Canvas->FillRect(Rect);
DBGridl->DefaultDrawColumnCell(Rect,DataCol,Column,State);

Глобальная переменная ColCol — это массив из двух элементов TColor; она объявлеш
следующим образом:
TColor ColCol []={ clSkyBlue, clMoneyGreen };
В первой строке кода извлекается номер текущей записи и при помощи операции по-
разрядного И (&) определяется его последний (младший) бит. Переменная целого типа
ind в результате получает или значение 0, или 1. В дальнейшем эта переменная использу-
ется для доступа к элементам массива ColCol. В остальном, код не претерпел изменений.
Откомпилируйте приложение и запустите его на выполнение. Можете убедиться, что те-
перь все работает правильно (см. рис. 3.23).

I
1 А4, GDI, ОЗУ 2 Мб. SOU dpi, до 0 ppm. лоток 16fj оистш, LPT. т»
I M, GDI. ОЛУ 8Мб, Ш <ipi «o4?fipm. литчк 1ЭД пиыаи. LPl,li-;f
1 М GDI ОЗУ 2(13) Мб, 600 dpt во 12 рргп, лоток 150 (650) писгов.
I A4.GD! eOfldpi aofippm, по™* Ш гнилоа LFT/!JSP -х.щаьД'А
1 ~..M, ОЗУ гМ5„6SOx«CO dpi, no 12 ppm, потах 2SO листов LPT/USB
,,-. .,
1 M user fJJJ Ifip^fiJ Ktfi, 6P,i .ir,:;:X[f>) -1p, ЙГГ, Jl0 C

Рис. 3.23. В исправленной версии приложения цвет ячеек сетки выбирается


в зависимости от номера отображаемой записи

Замечание
К сожалению, свойство RecNo можно применять для баз данных не всех типов. Для
баз данных в формате SQL свойство RecNo всегда содержит значение -1, независимо
от номера текущей записи. Чтобы воспользоваться этим свойством, необходимо пере-
определить методы GetRecNo и SetRecNo, применяемые для чтения и записи значения
свойства RecNo класса ТТаЬ/е.

Еще несколько аспектов использования события OnDrawColumnCell продемонстриру-


ем на следующем примере. Создадим тестовое приложение, в котором в сетке (компонент
Глава 3. Использование механизма ВОЕ (продолжение) 157

TDBGrid) отображаются остатки товаров на складе (таблица Storage). При этом, если
количество товара подходит к концу (например, оно меньше 5), значение поля Qty будем
отображать красным, а если количество товара на складе подходит к критической отметке
(например, не превышает 10), то текст в ячейке будем отображать цветом Lime (светло-зе-
леным). В остальных случаях цвет отображения текста в ячейках стандартный.
Создайте новое приложение и разместите на его главной форме сетку, два объекта
ТТаЫе и один объект TDataSource, как всегда, оставив все имена объектов по умолчанию.
Объект ТаЫе2 подключите к таблице Items базы данных MaData, a Tablel — к таблице
Storage той же базы. Сетку DBGridl подключите к объекту DataSourcel, &DataSour-
eel — к Tablel. Отобразите на экране редактор полей (Fields Editor), дважды щелкнув на
изображении объекта Tablel в форме. Добавьте в список постоянных полей определения
полей StoreltemNo, Qty и Item из таблицы Storage. Для свойства Visible поля Item задайте
значениеуа/se, так как в итоговом наборе данных оно нам не понадобится. Далее на осно-
вании связи между таблицами Storage и Items создайте новое поле подстановки (пункт
New field). Задайте для этого поля имя ItemName, тип String и размер 50. В области Lookup
definition укажите следующие значения: Item (Key Fields), Table2 (Dataset), ItemNo (Lookup
Fields) и Item (Result Field). Нажмите кнопку OK и откройте набор данных Tablel.
В тело обработчика события OnDrawColumnCell сетки DBGridl введите следующий код:
void fastcall TForml::DBGridlDrawColumnCell(TObject *Sender,
const TRect &Rect, int DataCol, TColumn *Column,
TGridDrawState State)
{
TColor Color;
TFontStyles Style=TFontStyles();
int Qty=Tablel->FieldByName("Qty")->Value;
Color=FDefaultColor;
if(Column->FieldName == "Qty")
{
if(Qty 5) Color=clRed;
if((Qty >= 5) && (Qty = 10))
Color=clLime;
if(Qty fsBold;
}
if(State.Contains(gdSelected)) Color=clWhite;
DBGridl->Canvas->Font->Color=Color;
DBGridl->Canvas->Font->5tyle=5tyle;
DBGridl->DefaultDrawColumnCell(Rect, DataCol, Column, State);
}
В этом отрывке кода используется переменная FDefaultColor, объявленная в секции
private класса TForml следующим образом:
.
private:
TColor FDefaultColor;
Переменная FDefaultColor инициализируется при создании формы Form! цветом, ис-
пользуемым по умолчанию для отрисовки текста ячеек сетки:
FDefaultColor=DBGridl->Canvas->Font->Color;
158 Borland C++ Builder 6. Разработка приложений баз данных

В начале обработчика события OnDrawColumnCell переменной Color типа TCol


присваивается значение переменной FDefaultColor. Далее, если отрисовывается ячей]
столбца Qty, надлежащим образом настраиваются переменные Color и Style (если това]
на складе меньше 11, его количество отображается полужирным шрифтом). Наконе
г
значения переменных Color и Style присваиваются соответствующим свойствам шриф
канвы сеткя и вызывается метод DefaultDrawColumnCell, отрисовывающий текст ячейк;
Осталось упомянуть только об одном моменте, а именно об операторе
if(State.Contains(gdSelected)) Color=clWhite;
Если его опустить, в выделенной ячейке сетки не будет отображаться текст. Труди
объяснить, почему так происходит в данной ситуации, но избежать этого можно имени
при помощи вышеприведенного оператора. Если ячейка выделена, т. е. аргумент-множе
ство State содержит элемент gdSelected, то для текста задается белый цвет. Главная форм
приложения изображена на рис. 3.24. Попробуйте закомментировать строку, приведенну!
выше, и посмотрите, что из этого выйдет.

SlofetenijgemName |OV [*]

<*ш
22 HPDeskJel656C 12 .
23 HP DeskJet 845C
Щ HP DeskJet 920C 33
25 HP DeskJet 940C 3
26 HP DeskJet 960C
27 HPDeskJetSSOC
26 HPDeskJet990C " 1 ijjji
29 HPPhotoSmartlllB 54
:
30 HP DeskJet 1220C •; '
31 HPPSC500 61 Щ
32 HP Office Jet G55 41 т

Рис. 3.24. Ячейка Qty сетки заполняется текстом,


цвет которого зависит от текущего значения поля Qty
В заключение обзора возможностей компонента TDBGrid следует упомянуть о еще
нескольких интересных его событиях. Событие OnColumnMoved происходит при пере-
мещении столбца сетки, но при условии, что в состав параметров сетки включена опция
dgColumnResize. Кроме стандартного аргумента Sender в тело обработчика события пере-
даются два аргумента целого типа. Аргумент Fromlndex содержит первоначальный индекс
столбца в коллекции Columns, а аргумент Tolndex — индекс, который получит столбец
в результате перемещения.
События OnColEnter и OnColExit происходят при перемещении фокуса ввода из
одного столбца в другой. Сначала возникает событие OnColExit, вслед за ним — событие
OnColEnter. Оба имеют стандартный тип TNotijyEvent. Парой этих событий можно вос-
пользоваться, например, чтобы сменить оформление активного столбца (цвет фона и/или
текста ячеек столбца или его заголовка) и для других подобных целей. Еще один вариант
их применения — организация фиксированных (непрокручиваемых) столбцов при пере-
мещении фокуса ввода с помощью клавиш управления курсором.
Последнее событие компонента TDBGrid, о котором хотелось бы упомянуть, —
OnCellClick — присходит при щелчке левой клавишей мышки на ячейке, не содержащей
фокус ввода. Точнее, событие происходит в момент, когда пользователь отпускает левую
клавишу мышки. Если щелчок был произведен на выбранной ячейке, событие не про-
изойдет. Скорее всего, при этом ячейка (сетка) перейдет в режим редактирования (это
зависит от настройки сетки). Обработчик события OnCellClick получает единственный
Глава 3. Использование механизма ВОЕ (продолжение) 159

аргумент — Column типа TColumn. Аргумент Column ссылается на столбец, содержащий


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

Запросы и компонент TQuery


Компонент TQuery — пожалуй, самый важный наследник класса TDataSet. TQuery
представляет собой программную оболочку вокруг одиночного запроса к базе данных.
Запрос к базе данных — это изложенное на языке SQL требование выполнить определен-
ные действия с данными или метаданными. К числу метаданных относятся описания таб-
лиц, индексов, запросов и других объектов, определяющие структуру базы данных. При
помощи компонента TQuery можно получать доступ или манипулировать данными одной
или нескольких таблиц, расположенных в базах данных формата SQL (серверы баз данных
InterBase, DB2, Oracle, Informix, MS SQL Server, Sybase), в локальных базах данных (MS
Access, Paradox, dBASE, FoxPro) и базах данных ODBC.
Использование объектов TQuery в подавляющем большинстве случаев более оправдан-
но, чем применение наборов данных ТТаЫе. Связано это с большой гибкостью и эффектив-
ностью языка запросов SQL, полагаемых в основу TQuery. Преимущества использования
компонента TQuery связаны с такими факторами, как:
Q возможность объединить несколько таблиц связями, не предусмотренными при проек-
тировании базы данных;
Q возможность в одном наборе данных сочетать информацию не только из разных таб-
лиц одной базы данных, но и из разных баз данных, в том числе баз данных разных
форматов;
О возможность легко задать фильтры любого уровня сложности, порядок сортировки за-
писей, набор полей, которые должны попасть в итоговую выборку, и т.д.;
Q возможность использовать параметры;
[Л простота создания вычисляемых и подстановочных полей, а также различных итого-
вых полей;
Q достаточная универсальность языка запросов SQL, позволяющая с минимальными по-
терями переходить от одного формата базы данных к другому;
G возможность создавать легко масштабируемые приложения.
Так как в основе компонента TQuery лежит строка оператора SQL, вполне естественно
начать изучение этого компонента с краткого обзора языка SQL.

Краткие сведения о языке запросов SQL


Несмотря на заявленную универсальность и существование стандарта ANSI SQL-
92, как водится, каждый из разработчиков серверов баз данных попытался расширить
узкие рамки стандарта, придав своей версии языка SQL дополнительные возможно-
сти, учитывающие особенности формата базы данных. И хотя основа языка остается
неизменной, существуют некоторые различия, зависящие от формата используемого
сервера базы данных. В этом разделе рассматривается так называемый локальный SQL
160 Borland C++ Builder 6. Разработка приложений баз данных

(Local SQL). Большая часть приведенной здесь информации, однако, подходит и до


других версий языка.
Локальный SQL — это подмножество стандарта SQL-92; он предназначен для достуг
к таблицам локальных баз данных (Paradox, dBASE, FoxPro). По сравнению со станда]
том ANSI SQL-92, локальный SQL вносит большое количество дополнений, учитываклщ
специфику вышеупомянутых баз данных. С другой стороны, в списке конструкций АК
SQL-92, которые не поддерживает локальный SQL, более полусотни элементов. В случ;
доступа к локальным таблицам, запросы на локальном SQL обрабатываются не какш
либо конкретным сервером баз данных, а непосредственно BDE. Механизм BDE тран<
лирует SQL-операторы в функции API BDE. В этом, наверное, еще одна причина, почем
локальный SQL так назван.
Операторы SQL делятся на две группы. К одной группе относятся операторы, предн;
значенные для манипулирования данными, а ко второй группе — метаданными. Перва
группа операторов SQL составляет язык (операторы) манипуляции данными (DML — Dai
Manipulation Language), а вторая группа операторов представляет собой язык (операторь
опеределения данных (DDL — Data Definition Language).
Замечание
Стандарт локального SQL требует, чтобы все ключевые слова были написаны заглав
ными буквами. Но так как BDE все равно приводит все ключевые слова оператора к такс
му виду, то, на самом деле, регистр ключевых слов не имеет значения.

Некоторые соглашения

Встроенные комментарии
В операторах SQL можно использовать встроенные комментарии в стиле языка С
Любой текст между парой символов /* и */ считается комментарием и удаляется из опера
тора SQL при его синтаксическом разборе.

Форматы констант даты и времени


Константы типа Дата (Date), как и текстовые константы, заключаются в двойные
кавычки. Существует два формата представления даты: американский и европейский
В американском формате в качестве разделителей используется символ косой черты («/»
и первым указывается месяц (одна или две цифры), затем день (одна или две цифры) и гол
(две или четыре цифры):
"mm/dd/yy"
или
"mm/dd/yyyy"
Европейский формат даты подразумевает использование в качестве разделителя сим-
вола точки («.»). Первым указывается день (одна или две цифры), затем месяц (одна или
две цифры) и год (две или четыре цифры):
"dd.mm.yy"
или
"dd.mm.yyyy"
Константы времени также заключаются в двойные кавычки. Локальный SQL допускает
использование констант времени в формате hh:mm:ss AM/PM, где hh — часы, mm — ми-
нуты, a ss — секунды. Модификаторы AM и РМ указывают, какая половина суток исполь-
Глава 3. Использование механизма ВОЕ (продолжение) 161

зуется для задания времени (РМ— до полудня, AM— после). Регистр ключевых слов AM
и РМ безразличен. Указывать модификаторы необязательно, в этом случае часть суток
определяется автоматически по значению времени. Например: «12:24:00».

Формат логических констант


Логические константы TRUE и FALSE в операторах SQL можно указывать нескольки-
ми способами. Можно указать полное имя логической константы в двойных кавычках или
без них. При этом регистр символов не важен. Например:
Where (Actual=FALSE) And (Reserved="True")
Кроме того, логическую константу можно указать при помощи только первой буквы:
«Т» или «F».

Язык манипуляции данными (DML)


Локальный SQL поддерживает четыре вида операторов DML — операторы выборки
(Select), удаления (Delete), вставки (Insert) и обновления (Update).

Оператор выборки (Select)


Этот оператор предназначен для отбора информации из таблиц. Ниже приведен его
полный синтаксис.
Select [Distinct] список_полей
From список_таблиц
[Where условие_отбора]
[Group By список_полей_группы]
[Having условие_отбора]
[Order By список_полей_сортировки]
Отдельные фрагменты оператора SQL, начинающиеся с ключевого слова SQL и предст-
авляющие собой законченные логические части, называются предложениями (Clauses).
Обозначается предложение ключевым словом, с которого оно начинается. Приведенный
выше оператор выборки может содержать до шести предложений; первые два предложе-
ния обязательны, остальные —'• нет. (При описании языковых конструкций SQL необяза-
тельные части заключаются в квадратные скобки.)

Предложение Select
Предложение Select — основное в операторе выборки, так как оно задает тип операто-
ра. Элемент список_полей должен содержать перечень наименований полей, разделенных
запятой. Необходимо указать как минимум одно поле. Кроме полей из таблиц, перечислен-
ных в предложении From, в списке также указываются вычисляемые поля. Вместо списка
полей можно указать символ звездочки (*). В этом случае в итоговую выборку попадут все
поля таблиц, указанных в предложении From.
Если список должен содержать поля из разных таблиц, но с одинаковыми именами,
а также в некоторых других случаях (например, если имя поля совпадает с одним из за-
резервированных слов), перед именем поля необходимо указать через точку имя таблицы:
Items.Index. Для поля таблицы, также как и для вычисляемого поля, можно задать новое
имя. Для этого используется ключевое слово AS: ItemNo^S Number.
Если нужно, чтобы оператор выборки возвращал только несовпадающие строки, вслед
за ключевым словом Select нужно указать ключевое слово Distinct.

6 Зап. 319
162 Borland C++ Builder 6. Разработка приложений баз данных

Предложение From
Предложение From — также обязательное в операторе выборки. Вслед за ключевы
словом From нужно указать список таблиц, участвующих в запросе. Все поля, перечисле!
ные в предложении Select, должны принадлежать одной из таблиц, указанной в предложи
нии From, иначе эти поля будут считаться вычисляемыми. В простейшем случае имен
таблиц указываются через запятую. Например:
Select Item, Qty
From Items, Storage;
Замечание
По соглашению каждый оператор SQL должен заканчиваться неким разделителем
По умолчанию в качестве разделителя используется точка с запятой. Но так как объек
TQuery может инкапсулировать только один запрос, разделитель в конце строки опера
, тора SQL можно опустить.

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


ньш правилам именования. В соответствии со стандартом ANSI имена таблиц должнь
состоять только из букв, цифр и символа подчеркивания ("_"). Локальный SQL допол
няет стандарт ANSI, позволяя указывать имена таблиц еще несколькими способами
Указать имя файла таблицы вместе с расширением можно при помощи одинарны>
кавычек: "Items, db". Если имя таблицы содержит не только алфавитно-цифровые
символы (а, например, также пробел), это имя нужно заключить в двойные кавычки
"Item Parts.db". В двойных кавычках также можно указать полный путь и имя файлг
таблицы: "d:\Temp\Items.db". Вместо пути к файлу таблицы можно использовать псев-
доним. В этом случае псевдоним с двух сторон обрамляется двоеточием: ":MyData:
Items, db". Кроме того, допускается использовать в качестве имени таблицы одно из за-
резервированных слов SQL, если заключить его в двойные кавычки: "Select". Список
зарезервированных слов локального SQL приведен в таблице 3.2.
Таблица 3.2. Список зарезервированных слов локального SQL
Active Add All After Alter
And Any As Asc Ascending
At Auto Autoinc Avg Base Name
Before Begin Between Blob Boolean
Both By Bj^tes Cache Cast
Check_Point_
Char Character Check Collate
Length
Column Commit Committed Computed Conditional
Constraint Containing Count Create Cstring
Current Cursor Database Date Day
Debug Dec Decimal Declare Default
Delete Desc Descending Distinct Do
Domain Double Drop Else End
Entry Point Escape Exception Execute Exists
Exit External Extract File Filter
Float For Foreign From Full
Function Gdscode Generator Gen Id Grant
Глава 3. Использование механизма ВОЕ (продолжение) 163
Group Commit
Group Having Hour If
Wait Time
In Int Inactive Index Inner
Input_Type Insert Integer Into Is
Isolation Join Key Long Length
Logfile Lower Leading Left Level
Maximum
Like Log_Buffer_Size Manual Max
Segment
Merge Message Min Minute Module Name
Money Month Names National Natural
Nchar No Not Null Num LogJBuffers
Numeric Of On Only Option
Or Order Outer Output Type Overflow
Page Size Page Pages Parameter Password
Plan Position Post Event Precision Procedure
Protected Primary Privileges Raw Partitions RdbSDb Key
Read Real Record Version References Reserv
Reserving Retain Returning Values Returns Revoke
Right Rollback Second Segment Select
Set Shared Shadow Schema Singular
Size Smallint Snapshot Some Sort
Sqlcode Stability Starting Starts Statistics
Sub Type Substring Sum Suspend Table
Then Time Timestamp Timezone Hour Timezone Minute
To Trailing Transaction Trigger Trim
Uncommitted Union Unique Update Upper
User Value Values Varchar Variable
Varying View Wait When Where
While With Work Write Year

Для таблицы, участвующей в предложении From, можно задать новое имя (псевдоним
таблицы). Псевдоним таблицы указывается вслед за именем таблицы через пробел, напри-
мер: "Items.db" It. Этот псевдоним потом нужно использовать в тексте оператора SQL
вместо имени таблицы (везде). Это удобно в нескольких случаях, например, для упроще-
ния записи вместо длинного имени таблицы можно использовать псевдоним из одной или
нескольких букв. Если имя таблицы указано как имя соответствующего файла с расши-
рением ("Items.db"), то для упрощения доступа к именам полей таблицы также удобно
использовать псевдоним. И, наконец, если в запросе участвует несколько экземпляров
одной и той же таблицы (например, в рекурсивной связи, когда таблица связывается сама
с собой по разным полям) без псевдонима просто не обойтись.
Предложение From служит также и для того, чтобы задать временные (т.е. суще-
ствующие только в рамках данного запроса) связи между таблицами. Простейший вид
связи между таблицами, указанными в предложении From, — отсутствие каких-либо
связей. Такой вид связи называется декартовым (Cartesian). Ниже приведен пример
декартовой связи:
Select * From Items, Storage;
164 Borland C++ Builder 6. Разработка приложений баз данных

Вышеприведенная строка SQL-оператора заставит BDE (как, впрочем, и любой се


вер баз данных) возвратить набор строк, содержащий все поля обеих таблиц, указаны!
в предложении From. Так как не был указан ни один из видов связи, в результирующ*
наборе данных каждой записи из таблицы Items будет соответствовать каждая запи
из таблицы Storage. В частности, если таблица Items содержит 10 записей, атабли;
Storage— 5, результирующий набор, заданный вышеприведенной декартовой связы
будет содержать 10 х 5 = 50 записей.
Реальную связь между таблицами в предложении From можно задать при помои
операторов связи. В операторах связи должны указываться пары полей таблиц, меж;
значениями которых устанавливается равенство (или другая операция отношенш
Предлагаемый в рамках локального SQL способ задания связи между таблицами — опер
торы Inner и Outer. Оператор Inner служит для задания внутреннего объединения табли
Внутреннее объединение таблиц означает, что в итоговый набор данных попадут толы
те записи из обеих таблиц, значения связанных полей которых совпадают. Внутренш
объединение двух таблиц задается так:
имя_таблицы! Inner Join имя_таблицы2
On (имя_таблицы!.имя_поля!=имя_таблицы2.имя_поля2)
Вслед за ключевым словом On указываются соотношения между полями таблиц, кс
торые используются для организации связи. Таких соотношений может быть несколью
В этом случае для их объединения в одно логическое выражение служат логические ош
раторы And (логическое И), Or (логическое Или) и Not (логическое отрицание). Вмест
знака равенства может использоваться любой знак операции отношения (<, >, = и т. д.). Н
так можно получить очень запутанное выражение для связи, которое иногда может привс
дить к непредсказумым результатам.
Например:
From Items Inner Join Storage On (Storage.Item = Items.ItemNo)
В приведенном выше предложении From задано внутреннее объединение между та
блицами Items и Storage. В итоговый набор данных попадут только те записи из обей:
таблиц, у которых значения поля Item (из таблицы Storage) и поля ItemNo (из таблиць
Items) совпадают.
Внешнее объединение таблиц можно задать при помощи оператора Outer. Внешне(
объединение таблиц означает, что в итоговый набор данных попадут не только запис!
таблиц, обусловленные внутренним объединением, но и все остальные записи одно!
из таблиц. При этом соответствующие поля другой таблицы не будут иметь никакого
значения (другими словами, будут иметь значение NULL). Синтаксис внешнего объеди-
нения следующий:
From им