Тема 10. Архитектура приложений для локальных баз данных в BC++ Builder

1.         Модели баз данных

2.         Организация связи с базами данных в C++Builder

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

4.         Наборы данных Table.Основные свойства и события.

5.         Компоненты визуализации и управления данными

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

 

Презентация к лекции

 

1. Модели баз данных

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

Процесс определения того, какая база данных более подходит для конкретного приложения, называется масштабированием. Необходимо иметь представление о возможных моделях баз данных, поскольку это влияет на построение приложений в 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: «Принимаем только мужчин >1955 г.р. и женщин >1965 г.р.»

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

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