Тема 10. Архитектура приложений
для локальных баз данных в BC++ Builder
2.
Организация
связи с базами данных в C++Builder
3.
Обзор
компонентов, используемых для связи с базами данных
4.
Наборы
данных Table.Основные свойства и события.
5.
Компоненты
визуализации и управления данными
6.
Проектирование приложений с несколькими связанными
таблицами
Для разных
задач целесообразно использовать различные модели баз данных, поскольку,
конечно, базу данных сведений о сотрудниках какого-то небольшого коллектива и
базу данных о каком-нибудь банке, имеющем филиалы во всех концах страны, надо
строить по-разному.
Процесс
определения того, какая база данных более подходит для конкретного приложения,
называется масштабированием. Необходимо иметь представление о возможных моделях
баз данных, поскольку это влияет на построение приложений в C++Builder.
Коротко
рассмотрим четыре модели баз данных:
§ Автономные
§ Файл-серверные
§ Клиент/сервер
§ Многоярусные
Работа с
данными в C++Builder в основном
осуществляется через Borland Database Engine
(BDE) - процессор БД фирмы Borland.
Она входит в состав BC++ Builder.
1.1 Автономные
базы данных
Автономные
локальные БД являются наиболее простыми. Они хранят свои данные в локальной
файловой системе на том компьютере, на котором установлены.
СУБД и BDE, осуществляющая к ним доступ, находятся
на том же самом компьютере. Сеть не используется. Разработчику автономной БД не
приходится иметь дело с проблемой параллельного доступа, когда два клиента
пытаются одновременно изменить одну и ту же запись.
1.2 Файл-серверные
базы данных
Файл-серверные
БД могут быть доступны многим клиентам через сеть. При этом изменения в таких
БД видят все пользователи.
Сама база
данных хранится на сетевом файл-сервере в единственном экземпляре. Для каждого
клиента во время работы создается локальная копия данных, с которой он
манипулирует.
При этом
возникают проблемы, связанные с возможным одновременным доступом нескольких
пользователей к одной и той же информации. Например, при проектировании
приложений, работающих с подобными базами данных, должны быть решены такие
проблемы: что делать, если пользователь прочел некоторую запись и, пока он ее
просматривает и собирается изменить, другой пользователь меняет или удаляет эту
запись.
Недостати БД файл-сервер:
1.
непроизводительная загрузка сети. При каждом запросе клиента данные в его
локальной копии полностью обновляются из БД на сервере. Даже если запрос
относится всего к одной записи, обновляются все записи данных. Если записей в
БД много, то даже при небольшом числе клиентов сеть будет загружена очень
основательно, что серьезно скажется на скорости выполнения запросов.
2. забота о
целостности данных возлагается на программы клиентов. Если они недостаточно
тщательно продуманы, в базу данных легко занести ошибки, которые могут
отразиться на всех пользователях.
1.3 БД клиент/сервер
Для больших БД
с множеством пользователей часто используются БД на платформе клиент/сервер. В
этом случае доступ к БД для группы клиентов выполняется специальным компьютером
- сервером. Клиент дает задание серверу выполнить те или иные операции поиска
или обновления базы данных. И сервер, ориентированный на операции с запросами
самым оптимальным способом, выполняет их и сообщает клиенту результаты своей
работы.
Подобная
организация работы повышает эффективность выполнения приложений за счет
использования мощности сервера, разгружает сеть, обеспечивает хороший контроль
целостности данных.
В БД
клиент/сервер возникает дополнительная проблема - спроектировать приложение
так, чтобы оно максимально использовало возможности сервера и минимально
нагружало сеть, передавая через нее только минимум информации.
1.4 Многоярусные базы данных
Это
сравнительно новая технология обработки данных в сети.
Наиболее
распространен трехъярусный вариант:
ü На первом
уровне располагаются приложения клиентов, обеспечивающих пользовательский
интерфейс.
ü На втором
уровне располагается сервер приложений, обеспечивающий обмен данными между
пользователями и распределенными базами данных. Сервер приложений размещается в
узле сети, доступном всем клиентам
ü На третьем
уровне расположен удаленный сервер баз данных, принимающий информацию от
серверов приложений и управляющий ими.
Первый,
элементарный уровень состоит из «тонких клиентов», то есть несложных
терминалов, предназначенных, в основном, для ввода-вывода.
Второй,
средний уровень - это рабочие станции и
серверы приложений, на которых выполняются программы, критичные к загрузке
процессора.
Третий и
последний уровень - мощные специализированные серверы баз данных.
Отметим одну
особенность многоярусных распределенных баз данных: в них на первом уровне не
требуется установка BDE. В этом заключается одно из преимуществ такой
организации баз данных.
2. Организация связи с базами данных в C++Builder
В C++Builder основой работы с базами данных является BDE - процессор БД фирмы Borland.
BDE
служит посредником между приложением и базами данных. Он предоставляет
пользователю единый интерфейс для работы, развязывающий пользователя от
конкретной реализации базы данных. Благодаря этому не надо менять приложение
при смене реализации базы данных.
Приложение С++Builder обращается к БД через BDE. В этом случае общение с БД соответствует схеме,
приведенной на рис. 1.
Рис. 1. Схема связи
приложения C++Builder с базами
данных
Приложение C++Builder для связи с
БД обращается к BDE и сообщает
обычно псевдоним БД и необходимую таблицу в ней. BDE
реализован
в виде динамически присоединяемых библиотек DLL
- IDAPI (Integrated Database Application Program Interface) - список процедур и функций для
работы с базами данных, которым и пользуются приложения.
BDE
по псевдониму находит подходящий для указанной БД драйвер. Драйвер - это вспомогательная
утилита, которая понимает, как общаться с базами данных определенного типа.
Если в BDE имеется собственный драйвер соответствующей СУБД,
то BDE связывается через него с базой данных и с нужной
таблицей в ней, обрабатывает запрос пользователя и возвращает в приложение
результаты обработки. BDE поддерживает
естественный доступ к таким базам данных, как Microsoft Access, FoxPro, Paradox, dBase.
Если
собственного драйвера нужной СУБД в BDE нет, то
используется драйвер ODBC. ODBC (Open Database Connectivity)
- это DLL, аналогичная по функциям BDE,
но разработанная Microsoft. Она хранится
в файле ODBC.DLL. Поскольку Microsoft включила поддержку ODBC в свои офисные продукты, фирма Borland включила в BDE
драйвер,
позволяющий использовать ODBC.
BDE
поддерживает SQL - стандартизованный язык запросов,
позволяющий обмениваться данными с SQL-серверами,
такими, как Sybase, Microsoft SQL, Oracle, Interbase. Эта возможность используется
особенно широко при работе на платформе клиент/сервер и в распределенных базах
данных.
Альтернативный
доступ к базам данных, минуя BDE - это
технология ActiveX Data
Objects (ADO).
ADO - это пользовательский интерфейс к
любым типам данных, электронную почту, системные, текстовые и графические
файлы. Связь с данными осуществляется посредством так называемой технологии OLE DB.
Использование ADO обеспечивает более эффективную работу с данными и
позволяет избежать некоторых неприятностей, которые иногда возникают в BDE. Однако надо сказать, что возможности ADO в C++Builder пока в некоторых отношениях ниже, чем
возможности BDE.
Еще один
альтернативный доступ к базам данных Interbase это технологии
InterBase Express
(IBX). В библиотеке компонентов на странице InterBase содержатся компоненты для работы с InterBase, минуя BDE.
Эти компоненты обеспечивают повышенную производительность и позволяют
использовать новые возможности сервера InterBase,
недоступные обычным компонентам BDE.
Следующий
альтернативные доступ к базам данных - это технологии dbExpress
- набор драйверов, обеспечивающих доступ
к серверам SQL на основе единого интерфейса.
3.Обзор
компонентов, используемых для связи с базами данных
В C++Builder каждое приложение, использующее базы
данных, обычно имеет по крайне мере по одному компоненту следующих трех типов:
Компоненты -
наборы данных (data set),
непосредственно связывающиеся с базой данных. Это такие компоненты, как Table, Query,
StoredProc, BDEClientDataSet - размещены на странице BDE. Для
других технологий имеются аналогичные компоненты наборов данных.
Компонент -
источник данных (data source), осуществляющий обмен информацией
между компонентами первого типа и компонентами визуализации и управления
данными. Размещен на странице Data Access. Таким компонентом является DataSource.
Компоненты
визуализации и управления данными, такие, как DBGrid,
DBText, DBEdit и множество других. Размещены на
странице Data Control.
Связь этих
компонентов друг с другом и с базой данных можно представить следующей схемой.
Таблица
базы данных |
|
Набры данных Table, Query, StoreProc |
|
Источник
данных DataSource |
|
Визуализация и управление: DBGrid, DBEdit, DBNavigator |
4. Наборы данных Table.Основные
свойства и события.
Построим
простейшее приложение, работающее с базой данных. Создадим в DBD базу данных dbP
и в ней создадим таблицы Pers и Dep.
Номер |
Отдел |
Фамилия |
Имя |
Отчество |
Год
рожд. |
Пол |
Характер-ка |
Фотография |
Num |
Dep |
Fam |
Nam |
Par |
Year_b |
Pol |
Charact |
Photo |
1 |
Бухгалтер |
Иванов |
Иван |
Иванович |
1950 |
м |
|
|
2 |
Цех
1 |
Петров |
Петр |
Петрович |
1960 |
м |
|
|
3 |
Цех
2 |
Сидоров |
Сидор |
Сидорович |
1955 |
м |
|
|
4 |
Цех
1 |
Иванова |
Ирина |
Ивановна |
1961 |
ж |
|
|
Отдел |
Тип |
Dep |
Proisv |
Бухгалтерия |
управление |
Цех
1 |
производство |
Цех
2 |
производство |
Поместим на
форму компоненты Table и DataSource.
Оба эти компонента невизуальные. В качестве компонента визуализации данных
поместим на форму компонент DBGrid. Это визуальный компонент, в котором будут
отображаться данные таблицы. Поэтому в его свойстве Align установим значение alClient
Установим цепочку
связей между этими компонентами.
Главное свойство DBGrid и других
компонентов визуализации и управления данными - DataSource.
Выделим
на форме компонент: DBGrid1 и щелкнем на
его свойстве DataSource в ИО. В
выпадающем списе перечислены все имеющиеся на форме источники данных. В данном
случае имеется только один источник данных - DataSource1.
Установим
его в качестве значения свойства DataSource.
Далее
установим связь между источником данных и набором данных. Выделим компонент DataSource1 и в ИО его главное свойство DataSet
из выпадающего списка установим в Table1.
Чтобы связать
компонент Table1 с необходимой
таблицей БД используются два его свойства: DatabaseName
и TableName. Сперва надо установить свойство DatabaseName. В выпадающем списке этого свойства в
ИО отображаются все доступные BDE псевдонимы
баз данных. Выберем созданный ранее псевдоним dbP.
Затем
устанавим значение свойства TableName. В выпадающем
списке выберем таблицу Pers.
Теперь можно в
процессе проектирования соединиться с БД через свойство Active = true и в поле
компонента DBGrid1 отобразятся
данные из таблицы.
Следует
отметить, что заранее выставлять для таблиц Active = true допустимо
только в процессе отладки приложения, работающего с локальными базами данных.
Запустим
приложение и можем просматривать данные, редактировать их (редактированные
данные будут помещаться в базу данных в момент перехода от редактируемой записи
к любой другой).
Разрешить
пользователю редактировать данные в таблице в большинстве случаев недопустимо.
Предотвратить редактирование данных можно, установив свойство ReadOnly компонента DBGrid1 в true.
Другой
способ - установить в свойстве Options подсвойство dgEditing в false.
Свойство - Exclusive определяет доступ к таблице при
одновременном обращении к ней нескольких приложений. Если задать значение true, то таблица будет закрыта для
других приложений. Свойство можно изменять только при Active = false.
Добавим в
приложение компонент DBNavigator (страница Data Control), управляющий
работой с таблицей. Компонент имеет ряд
кнопок, служащих для управления данными.
nbFirst |
перемещение
к первой записи |
nbPrior |
перемещение
к предыдущей записи |
nbNext |
перемещение
к следующей записи |
nbLast |
перемещение
к последней записи |
nblnsert |
вставить
новую запись перед текущей |
nbDelete |
удалить
текущую запись |
nbEdit |
редактировать
текущую запись |
nbPost |
послать
отредактированную информацию в базу данных |
nbCancel |
отменить
результаты редактирования или добавления новой записи |
nbRefresh |
очистить
буфер, связанный с набором данных |
Компонент
имеет помимо свойства Hint - надпись на ярлычке
компонента, дополнительное свойства Hints - надписи ярлычков отдельных кнопок.
Свойство VisibleButtons позволяет
убрать ненужные в данном приложении кнопки. Например, если нужно запретить
редактирование, то можно оставить только кнопки nbFirst,
nbPrior, nbNext и nbLast,
а
все остальные убрать.
Чтобы
приложение с навигатором работало, надо установить основное свойство навигатора
- DataSource - источник
данных.
Свойства полей
В приложении, во-первых,
последовательность записей определяется ключевым полем Num, а желательно расположить по
алфавиту или по отделам и алфавиту. Шапка таблицы содержит непонятные имена
полей Num,
Fam и т.д., а
надо, чтобы были написаны нормальные заголовки по-русски. В графе Pol значения true и false, a нужны нормальные обозначения типа «м», «ж» или «мужской», «женский».
Начнем с
упорядочивания записей. Выделим на форме компонент Table1.
В
ИО свойстве IndexName содержит выпадающий
список индексов, созданных для таблицы. Выберем, например, индекс fio, и увидим, что записи окажутся
упорядоченными по алфавиту, поскольку в этот индекс включены поля Fam, Nam и Par. При индексе depfio упорядочивание
будет по подразделениям, а внутри каждого подразделения - по алфавиту.
В свойстве
IndexFieldName перечисляются предусмотренные
комбинации полей.
Для
редактирования полей служит окно Редактора
Полей. Вызывается двойным щелчком на компоненте Table1. Щелкнем в
окне правой кнопкой мыши и из всплывающего меню выбираем раздел Add fields. Откроется окно, в котором содержится
список всех полей таблицы. Выбираем из него необходимые поля, щелкнем на ОК и вернемся к основному окну Редактора Полей.
Эти поля будут
соответствовать колонкам таблицы. Изменить последовательность их расположения
можно, перетащив мышью идентификатор какого-то поля на нужное место.
Выделим в
списке какое-то поле и посмотрим его свойства в ИО. Увидим, что каждое поле -
это объект, причем его класс зависит от типа поля: TStringField,
TSmallintField, TBooleanField и т.п. Все эти
классы являются производными от TField - базового
класса полей. Таким образом, каждое поле является объектом и обладает
множеством свойств. Рассмотрим основные из них, которые чаще всего необходимо задавать.
Свойство Alignment определяет
выравнивание отображаемого текста внутри колонки таблицы: влево, вправо или по
центру.
Свойство DisplayLabel соответствует заголовку столбца
данного поля. Например, для Fam значение DisplayLabel можно задать равным «Фамилия», для Nam - «Имя» и т.д.
Свойство DisplayWidth определяет
ширину колонки - число символов.
Свойства EditMask для строк и EditFormat для чисел
определяют форматы отображения данных.
Для логических
полей очень важным свойством является DisplayValues
- определяет, какие значения должны
отображаться, если поле имеет значение true
или false. Отображаемые значения разделяются точкой с
запятой. Первым пишется значение, соответствующее true.
Например: «м;ж» или «мужской;женский».
Свойство ReadOnly, установленное
в true, запрещает пользователю вводить в данное поле
значения. Свойство Visible определяет, будет ли видно
пользователю соответствующее поле. В примере можно задать Visible = false
для поля Num.
Отметим еще одну особенность Редактора Полей - возможность
перетаскивать из него поля на форму с помощью мыши. Захватим в Редакторе Полей
какое-нибудь поле, например, Fam, и перетащим
его на форму. На форме появится связанный с этим полем компонент типа TDBEdit и метка,
содержащая его свойство DisplayLabel. При переносе
на форму булева поля Pol на ней появится связанный с этим
полем индикатор - компонент типа TDBCheckBox. При
перетаскивании полей Charact и Photo появятся
соответственно связанные с этими полями компоненты типов TDBMemo и TDBImage. И во всех этих компонентах,
если переключить свойство Active компонента ТТаble1 в true,
сразу
отобразятся соответствующие данные из таблицы.
Вычисляемые поля
Попробуем
сформировать в таблице новое поле, не предусмотренное при ее создании, значение
которого вычисляется на основании значений других полей записи. Подобные поля
называются вычисляемыми полями (calculated fields).
Пусть
в примере нужно добавить поле, вычисляющее возраст сотрудника по его году
рождения. Выполним двойной щелчок на Table1.
Вызывается
Редактор Полей. Щелкнем в Редакторе
Полей правой кнопкой мыши и во всплывшем меню выберем раздел New field (новое поле). Появится окно
добавления нового поля.
В разделе Field properties (свойства поля) нужно указать имя
поля (Name) - назовем это поле Age, тип данных (Туре) - в нашем случае это Smallint,
и
для некоторых типов - размер (Size). Размер указывается для строк и
других полей неопределенных размеров.
Затем щелкнем
на ОК и в окне Редактора Полей
появится новое поле Age. Зададим для него в ИО значение DisplayLabel равным «Возраст».
Теперь нужно в
программном коде указать, как вычислить значение этого поля. Для этого выделим Table1, в Инспекторе Объектов на
странице Events щелкнем на событии OnCalcFields. Это событие наступает каждый раз, как
надо обновить значение вычисляемых полей таблицы.
Чтобы
вычислить возраст по году рождения, в обработчике этого события написать
операторы:
void _fastcall
TForm1::Table1CalcFields(TDataSet *DataSet)
{
unsigned short Year, Month, Day;
Date ().DecodeDate (&Year, &Month, &Day);
Table1Age->Value = Year
- Table1Year_b->Value;
}
В этом
операторе Table1Age->Value и Table1Year_b->Value - значения
полей Age и Year_b соответственно.
В этом коде
введены переменные Year, Month и Day для хранения
текущего года, месяца и дня. Здесь функция Date возвращает текущую дату типа TDateTime, а функция DecodeDate преобразовывает
дату в целые значения года, месяца и дня. В результате переменная Year становится
равной текущему году.
Фильтрация данных
Компонент Table позволяет фильтровать записи по
определенным критериям.
Фильтрация
задавается свойствами Filter, Filtered и FilterOptions. Свойство Filtered включает или выключает использование
фильтра.
Сам фильтр
записывается в свойство Filter в виде строки, содержащей
определенные ограничения на значения полей. Например, если задать в свойстве Filter Dep='Цех 1' и
установить
свойство Filtered = true, то уже в процессе проектирования в
таблице отобразятся только те записи, в которых поле Dep
имеет значение «Цех 1». В условии
фильтрации строки заключаются в одинарные кавычки.
В условиях
сравнения строк можно использовать символ звездочки "*",
который, означает: «любое количество
любых символов». Например, фильтр
Dep='Цех*'
приведет к
отображению всех записей, в которых значение поля Dep
начинается с «Цех». Но для того, чтобы это сработало, надо, чтобы в опциях,
содержащихся в свойстве FilterOptions была выключена опция foNoPartialCompare, запрещающая
частичное совпадение при сравнении. Другая опция в свойстве FilterOptions - foCaselnsensitive делает
сравнение строк нечувствительным к регистру, в котором записано условие
фильтра. Если включить эту опцию, то слова «Цех 1» и «цех 1» будут считаться
идентичными.
При записи
условий можно использовать операции отношения =, >, >=, <, <=,
<>, а также логические операции and, or и not. Например, вы
можете написать фильтр
(Dep='Цех l') and (Year_b<=1970) and (Year_b>=1940)
и отобразятся
записи сотрудников Цеха 1, чей год рождения лежит в заданных пределах. Использовать
в фильтре имена вычисляемых полей нельзя.
Свойства,
определяющие фильтрацию, можно задавать как в процессе проектирования, так и
программно, во время выполнения. Установим в компоненте DBGrid1 свойство Align равным alNone, увеличим вертикальный размер
формы и перенесем на форму группу радиокнопок RadioGroup (назовем ее RGF), выпадающий список ComboBox (CBDep), два элемента CSpinEdit со страницы Samples (SEmin и SEmax),
кнопку,
написав на ней «Обновить» и метки.
Компонент CBDep будет
позволять выбирать подразделение, по которому проводится фильтрация. В его
свойство Items занесем список
имен подразделений в таблице. Компоненты SEmin и SEmax будут служить для задания диапазона
возраста при фильтрации по этому критерию. Зададим в них разумные значения
свойств Max Value, Min Value и Value. В группе радиокнопок RGF введем
соответствующие надписи (в свойствах Items и Caption),
зададим
Itemlndex=0 и Columns = 2. Напишем теперь операторы,
обеспечивающие фильтрацию. Ниже приведен соответствующий текст.
unsigned short Year, Month, Day;
void _fastcall
TForm1::Table1CalcFields(TDataSet *DataSet)
{
Table1Age->Value = Year -
Table1Year_b->Value;
}
void _fastcall TForm1::RGFClick (TObject
*Sender)
{
Table1->IndexName = "depfio";
if (RGF->ItemIndex == 0)
Table1->Filtered = false;
else
{
if (RGF->ltemIndex = = 2)
Table1->Filter = "Dep=' "+CBDep->Text+" '
";
else
if (RGF->ItemIndex == 3)
{
Table1->Filter = "(Year_b<="+ IntToStr
(Year-SEmin->Value) + ") and (Year_b>="+IntToStr
(Year-SErnax->Value) + ")";
Table1->IndexName = "Year";
}
else
Table1->Filter = "(Dep=' "+CBDep->Text+" ')
and (Year_b<="+IntToStr(Year-SEmin->Value) +
")and(Year_b>=" + IntToStr(Year-SEmax->Value)+")" ;
Table1->Filtered = true;
}
void _fastcall
TForml::FormCreate(TObject *Sender)
{
Date ().DecodeDate(&Year,&Month,&SDay);
Table1->Active = true;
}
void _fastcall
TForml::FormDestroy(TObject *Sender)
Table1->Active = false;
}
Текст включает
три процедуры. Процедура Table1CalcFields уже была рассмотрена
ранее. Ее отличие заключается только в том, что переменные Year, Month и Day сделаны глобальными и обращение к
функциям Date и DecodeDate перенесено в
процедуру FormCreate - обработчик
события формы OnCreate для того,
чтобы можно было воспользоваться значением текущего года Year в двух
процедурах - Table1CalcFields и RGFClick. В процедуру FormCreate внесен также
оператор
Table1->Active = true; открывающий связь с базой данных.
Остановимся на
основной процедуре приложения - RGFClick, которая осуществляет
фильтрацию и упорядочивание данных. Обращение к этой процедуре делается при
событиях OnClick компонента RGF - группы
радиокнопок, и кнопки Обновить. Эта
кнопка введена в приложение, чтобы можно было, не переключая радиокнопок,
обновлять отображение данных после изменения пользователем имени подразделения
или диапазона возраста. Удобно также сделать обращение к процедуре RGFClick при событии OnChange выпадающего
списка отделов.
Первый
оператор процедуры RGFClick задает индекс depfio, обеспечивающий упорядочивание
данных по подразделениям и алфавиту. Следующий оператор анализирует индекс
группы радиокнопок, т.е. анализирует заданный пользователем способ фильтрации.
Если индекс равен нулю (отсутствие фильтрации), то фильтр отключается установкой
свойства Filtered в false. При других значениях индекса
это свойство устанавливается в true. Если индекс
равен 2 (фильтрация по отделам), то значение свойства Filter формируется
равным Dep='...', где вместо точек фигурирует текст,
отображаемый в выпадающем списке CBDep. Если индекс
равен 3 (фильтрация по возрасту), то значение свойства Filter
формируется равным (Year_b<=...)and(Year_b>=...), где
вместо точек подставляются данные, введенные пользователем в компонентах SEmin и SEmax. При этом приходится
пересчитывать заданный пользователем диапазон возраста в диапазон годов
рождения, поскольку наложить ограничения непосредственно на вычисляемое поле
возраста Age невозможно. Выполните приложение.
Имеется еще
другой способ фильтрации - обработчик события OnFilterRecord
- происходит
каждый раз при смене текущей записи, если свойство Filtered
установлено
в true.
В обработчик события передается по ссылке параметр Accept булева типа.
Если проверка полей показывает, что запись удовлетворяет фильтру, то Accept должен быть
установлен в true,
а если условия фильтрации не выполнены,
то Accept должен быть установлен в false. Обработчик события OnFilterRecord может иметь вид:
void
_fastcall
TForml::Table1FilterRecord(TDataSet *DataSet, bool &Accept)
{
Accept = (RGF->ItemIndex ==0) || ( (RGF->ItemIndex == 2)
&&
(Table1Dep->Value==CBDep->Text)) || ( (RGF->ItemIndex == 3)
&&
(Table1Year_b->Value <=
(Year-SEmax->Value)) &&
(Table1YearjD->Value >= (Year-SEmin->Value))) II
((Table1Dep->Value == CBDep->Text) &&
(Table1Year_b->Value <= (Year-SEmax->Value)) && (Table1YearjD->Value >= (Year-SEmin->Value)));
}
Здесь свойству
Accept
непосредственно присваивается значение результата анализа условий фильтрации в
зависимости от значения свойства ItemIndex группы радиокнопок RGF.
Приведенный
оператор обеспечивает нужную фильтрацию. Но чтобы он работал, необходимо
выполнить еще два условия. Во-первых, надо установить в компоненте Table1 значение Filtered
= true.
Во-вторых, надо обеспечить, чтобы при смене условий отображения таблицы
проводилась бы новая фильтрация. Иначе просто события OnFilterRecord не будут
происходить. Поэтому в обработчик RGFClick события OnClick компонента RGF
надо вставить операторы:
Table1->Filtered = false;
Table1->Filtered = true;
if (RGF->ItemIndex == 3)
Table1->IndexName =
"Year";
else Table1->IndexName = "depfio";
Первые два
оператора обеспечивают отключение и включение фильтрации. При включении и
происходят события OnFilterRecord. А третий
оператор просто изменяет индексацию в зависимости от того, что задал
пользователь. На этот обработчик RGFClick надо сослаться и в событии OnClick кнопки Обновить.
Ограничения
вводимых значений
C++Builder предусматривает разнообразные
возможности ограничения значений в приложении. В таблице устанавливаются обычно
достаточно широкие пределы, пригодные для любых ее применений. А в конкретном
приложении могут потребоваться более узкие пределы.
Несколько
возможностей ограничения предоставляют свойства полей, которые можете увидеть,
сделав двойной щелчок на компоненте Table и выделив в окне Редактора Полей
требуемое поле. Для числовых полей имеются свойства MinValue и MaxValue,
устанавливающие
допустимые пределы вводимых в поле значений. Например, если требуется принимать
на работу сотрудником только в узком возрастном диапазоне, можно для поля Year_b установить ограничения MinValue = 1970 и
MaxValue = 1980. Эти
ограничения не скажутся на отображаемых данных. Но при нарушении этих пределов
в процессе редактирования записи или в новой записи будет генерироваться
исключение.
Другая
возможность ограничения - использование свойств CustomConstraint и ConstraintErrorMessage. Свойство CustomConstraint позволяет
написать ограничение на значение поля в виде строки SQL. Например, для того же поля Year_b можете написать в свойстве CustomConstraint:
X
<
1980 and X > 1970
Если задали
свойство CustomConstraint, то необходимо
задать и свойство ConstraintErrorMessage. Оно содержит
строку текста, который будет показан пользователю в случае, если он вводит
данные, не удовлетворяющие поставленным ограничениям. Например, «Возраст
претендента на должность не подходит». Таким образом, этот вариант обычно много
удобнее задания MinValue и MaxValue, поскольку не требует перехвата
исключений. Может применяться не только к числовым полям.
Описанные выше
способы проверки данных относятся к конкретному полю. Имеется также возможность
осуществлять проверку на уровне записи, анализируя различные ее поля. Для этого
служит свойство Constraints компонента Table. Нажмите кнопку в этом свойстве.
Откроется окно. Щелкая в нем на кнопке Add New можете
занести в свойство Constraints набор
ограничений. Каждое из них представляет собой самостоятельный объект. Выделив
одно из ограничений, увидите в Инспекторе Объектов его свойства. Свойство CustomConstraint представляет
собой строку SQL, определяющую допустимые значения. Свойство ErrorMessage определяет
строку текста, которая будет предъявлена пользователю в случае нарушения
ограничений. Например, вы можете написать в свойстве CustomConstraint:
((Pol=true)
and (Year_b >1955)) or (Pol=false)
and (Year_b>1965) )
а в свойстве
ErrorMessage: «Принимаем только мужчин >
Для
комплексной проверки данных можно также использовать различные события
компонента Table. Это
рассмотрим при
рассмотрении методов программирования работы с данными.
5.
Некоторые компоненты визуализации и управления данными
В примерах в
качестве компонента визуализации и управления данными использовали DBGrid. В таком виде пользователю
неудобно вводить многие данные. Например, вводя имя подразделения, пользователь
должен полностью его, написать: «Бухгалтерия» или «Цех l».
Этот
недостаток можно устранить, если воспользоваться свойством Columns компонента DBGrid - представляет
собой набор объектов, каждый из которых отражает один столбец таблицы.
Попробуем теперь сформировать их, используя свойство Columns.
Выполним
щелчок на этом свойстве в Инспекторе Объектов. Появится пустое окно Редактора
Столбцов. Здесь можно добавлять столбцы по одному, щелкая на кнопке Add и указывая для них в Инспекторе
Объектов соответствующие поля в свойстве FieldName. To
же самое можно делать, щелкая правой кнопкой мыши и выбирая в контекстном меню
раздел Add или выбрать из контекстного меню раздел Add All Fields или щелкнуть
на соответствующей быстрой кнопке. В окне Редактора Столбцов появится список
столбцов всех полей, которые добавлены в Редакторе Полей компонента Table. Затем кнопкой Delete можете
удалить ненужные столбцы.
Выделим в
Редакторе Полей столбец, связанный с полем Dep и в Инспекторе Объектов выберем
свойство ButtonStyle - определяет стиль ввода данных в
поле текущей записи. Свойство ButtonStyle может
принимать значения:
cbsAuto - появление при редактировании кнопки,
связанной с выпадающим списком допустимых значений.
cbsEllipsis - появление при редактировании кнопки с
многоточием "..,", при щелчке на которой наступает событие OnEditButtonClick компонента DBGrid.
cbsNone - обычное редактирование без каких-либо
кнопок.
Если
установить значение cbsAuto
(по умолчанию), то можно дополнительно установить свойство столбца PickList, которое
представляет собой список допустимых значений поля. Тогда при попытке
пользователя редактировать данное поле в соответствующей ячейке таблицы
появится кнопка, связанная с выпадающим списком, содержащим допустимые
значения.
Свойство DropDownRows (по
умолчанию) определяет допустимое число строк в списке, отображаемое без
появления полосы прокрутки. Если реальное число строк меньше DropDownRows, размер списка устанавливается
автоматически.
Если значение
свойства ButtonStyle
равно cbsEllipsis,
то при попытке пользователя редактировать данное поле в нем появляется кнопка с
многоточием "...". При щелчке на этой кнопке наступает событие OnEditButtonClick компонента DBGrid. В обработчике этого события можете предусмотреть
показ пользователю какого-то списка возможных значений или предложить диалог, в
котором пользователь может выбрать требуемое значение. В обработчике события OnEditButtonClick можете
узнать, какое поле редактируется, по свойству SelectedField компонента DBGrid.
Это же свойство позволит занести в поле установленное пользователем значение.
Например:
if(DBGrid1->SelectedField == Table1Dep)
{
DBGrid1->SelectedField->Value
= ...;
}
Если значение
свойства ButtonStyle равно cbsNone, то
редактирование поля производится без каких-либо подсказок в виде кнопок.
Имеются и
другие компоненты визуализации и управления данными, расположенные на странице Data Control библиотеки компонентов, нередко
предпочтительнее DBGrid.
Отметим
некоторые из этих компонентов.
DBText - аналог
обычной метки Label, но связанный с
данными - позволяет отображать данные некоторого поля без редактирования. Тип
отображаемого поля может быть различным: строка, число, булева величина.
Компонент автоматически переводит соответствующие типы в отображаемые символы.
DBEdit - связанный с
данными аналог обычного окна редактирования Edit
- позволяет отображать и редактировать данные
полей различных типов: строка, число, булева величина. Впрочем, если задать в
компоненте ReadOnly=true,
то
он, как и DBText, будет служить элементом
отображения, но более изящным, чем DBText.
DBMemo - связанный с
данными аналог обычного многострочного окна редактирования Memo - позволяет отображать и редактировать данные
поля типа Memo, а также данные любых типов,
указанных выше для предыдущих компонентов.
DBRichEdit - связанный с
данными аналог обычного многострочного окна редактирования текста в обогащенном
формате RTF. Область применения та же, что для компонента DBMemo.
DBImage - связанный с
данными аналог обычного компонента Image - позволяет
отображать графические поля, например, фотографии сотрудников.
DBCheckBox - связанный с
данными аналог обычного индикатора CheckBox - позволяет отображать и редактировать
данные поля булева типа. Если при выводе данных поле имеет значение true, то индикатор включается, и
наоборот, при редактировании поля присваиваемое ему значение определяется
состоянием индикатора.
DBRadioGroup - связанный с
данными аналог группы радиокнопок RadioGroup. Компонент
позволяет отображать и редактировать поля с ограниченным множеством возможных
значений. Свойство Items определяет
число кнопок и надписи около них. Но есть еще свойство Values, которое определяет значения
полей, соответствующие кнопкам. Таким образом, надписи у кнопок и значения
полей в общем случае могут не совпадать друг с другом. Вели свойство Values не задано, то
в качестве значений полей берутся строки из свойства Items, т.е. надписи около кнопок.
Компонент DBRadioGroup предоставляет
еще одну возможность ввода пользователем данных путем выбора, а не написанием
полного значения поля.
Все
перечисленные компоненты имеют два основных свойства: DataSource - источник данных и DataField - поле, с
которым связан компонент.
Характерной
особенностью всех этих компонентов является отсутствие в окне Инспектора
Объектов их основных свойств, отображающих содержание: Caption в DBText, Text в DBEdit, Picture в DBImage и т.п. Все эти свойства имеются в
компонентах, но они доступны только во время выполнения. Так что программно их
можно читать, редактировать, устанавливать, но во время проектирования задать
их значения невозможно. Это естественно, так как эти свойства - это значения
соответствующих полей таблицы базы данных.
Перечисленные
компоненты могут размещаться непосредственно на форме, на любой панели, а могут
размещаться в специальном компоненте - таблице DBCtrlGrid. Этот компонент состоит из нескольких
панелей, из которых проектируется только первая, а остальные повторяют
структуру первой, но относятся к последующим записям таблицы.
DBCtrlGrid имеет
свойство DataSource,
которое автоматически передается всем расположенным на панелях компонентам,
связанным с данными.
Свойство RowCount определяет
число строк в таблице (по умолчанию 3).
Свойство ColCount - число
колонок в таблице (по умолчанию 1).
Ширину и
высоту панелей DBCtrlGrid
можно задавать свойствами PanelWidth
и PanelHeight
соответственно, или задавая общую ширину и высоту компонента свойствами Width и Height. В этом случае С++Builder может автоматически округлить
заданные значения так, чтобы обеспечить равные ширину и высоту всех панелей.
Размещение
компонентов визуализации на отдельной форме не вызывает никаких принципиальных
сложностей. Надо только в модуле дополнительной формы сослаться с помощью
директивы препроцессора #include на модуль
основной формы, чтобы получить доступ к расположенному на ней компоненту типа TDataSource.
Эту
ссылку проще всего сделать командой File | Include Unit Hdr. Тогда при
задании свойств DataSource компонентов,
расположенных на вспомогательной форме, в выпадающем списке появится источник
данных Forml->DataSource1,
который
и надо выбрать.
А в
главной форме надо аналогичным образом сослаться на вспомогательную форму,
чтобы в нужный момент можно было сделать ее видимой. Тогда в обработчике
события OnClick кнопки можно
написать оператор:
if (! Form2->Visible) Form2->Show();
который делает
вспомогательную форму Form2 видимой, если
она не была видимой до этого.
6.
Приложения с несколькими связанными таблицами
Связь головной и
вспомогательной таблиц
Рассмотрим
технологию проектирования приложений с несколькими связанными друг с другом
таблицами.
Две таблицы
могут быть связаны друг с другом по ключу. Одна из этих связанных таблиц
является головной (master), а другая - вспомогательной,
детализирующей (detail).
Например, хотим построить приложение, в котором имеется таблица Dep, содержащая
список подразделений учреждения (поле Dep)
и
характеристику этих подразделений (поле Proizv булева типа, в котором true означает
«Производство», а false -
«Управление»). И хотим, чтобы пользователь, перемещаясь по этой таблице, видел
не только характеристику подразделения, но и список сотрудников этого
подразделения, т.е. записи из таблицы Pers, в которых
значение поля Dep совпадает со
значением поля Dep в текущей
записи первой таблицы.
В этом случае
головной является таблица Dep, вспомогательной - таблица Pers, а ключом,
определяющим их связь, являются поля Dep из обеих таблиц.
Разместим на
форме 2 комплекта Table, DataSource и средств отображения данных. В
первом комплекте используем компонент отображения DBCtrlGrid,
на
панелях которого располагаются метки DBText. Комплект обычным образом
настраивается на первую, головную (master) таблицу - Dep. Таблица
должна быть индексирована по полю Dep, которое будет
ключом для связи таблиц. Метки, размещенные на DBCtrlGrid,
связываются
с полями Dep и Proizv.
Второй
комплект компонентов использует для отображения данных компонент DBGrid. Комплект настраивается на
вторую вспомогательную таблицу - Pers. Для таблицы должен быть задан
индекс, содержащий ключевое поле связи Dep.
Можно выполнить приложение.
Теперь свяжем
эти таблицы. Установим сперва Active = false компонента Table2. Далее в свойстве MasterSource компонента Table2 установиv имя головной таблицы. Затем выполним щелчок на свойстве MasterFields, откроется окно
редактора связей полей (Field Link Designer). В окне Detail Fields расположены
имена полей вспомогательной таблицы, но только тех, по которым таблица
индексирована. Именно поэтому надо индексировать таблицу так, чтобы индекс
включал ключевое поле связи Dep. В окне Master Fields расположены поля головной таблицы.
Выделим в одном и другом окне ключевое поле (в нашем случае Dep). Нажмем на кнопку Add. Ключевые поля переносятся в окно Joined Fields - соединяемые поля. Если ключ
составной (например, фамилия, имя, отчество) - эта операция повторяется для
других полей. В конце формирования связей выполним щелчок на ОК и в свойстве MasterFields компонента Table появится текст
- связанные поля. После этого можно восстановить связь с базой данных (Active = true) и запустить
приложение.
Здесь, зависимости
от того, какая запись выделена в списке отделов, отображается список
сотрудников этого отдела. Таким образом, курсор скользит по головной таблице, а
вспомогательная таблица отображает только те записи, в которых ключевые поля
совпадают с ключевыми полями головной таблицы.