- Содержание
- Обзор
- Класс TDataSet
- Открытие и
закрытие DataSet
- Навигация
(Перемещение по записям)
- Поля
- Изменение
Данных
- Использование
SetKey для Поиска в таблице
- Использование
фильтров для ограничения числа
записей в DataSet
- Обновление
- Закладки
- Создание
связанных курсоров
- Основные
понятия TDataSource
- Использование
TDataSource для проверки состояния
БД
- Отслеживание
состояния DataSet
-
- Обзор
Статья
содержит всесторонний обзор
основных фактов которые Вы должны
знать, прежде чем начать писать
программы, работающие с Базами
Данных (БД). Прочитав эту статью, Вы
должны понять большинство
механизмов доступа к данным,
которые есть в Delphi.
Более
подробно здесь рассказывается о
TTable и TDataSource.
Имеются
несколько основных
компонент(объектов), которые Вы
будете использовать постоянно для
доступа к БД. Эти объекты могут быть
разделены на три группы:
- невизуальные:
TTable, TQuery, TDataSet, TField
- визуальные:
TDBGrid, TDBEdit
- связующие:
TDataSource
Первая
группа включает невизуальные
классы, которые используются для
управления таблицами и запросами.
Эта группа сосредотачивается
вокруг компонент типа TTable, TQuery, TDataSet
и TField. В Палитре Компонент эти
объекты расположены на странице Data
Access.
Вторая
важная группа классов - визуальные,
которые показывают данные
пользователю, и позволяют ему
просматривать и модифицировать их.
Эта группа классов включает
компоненты типа TDBGrid, TDBEdit, TDBImage и
TDBComboBox. В Палитре Компонент эти
объекты расположены на странице Data
Controls.
Имеется и
третий тип, который используется
для того, чтобы связать предыдущие
два типа объектов. К третьему типу
относится только невизуальный
компонент TDataSource.
- Класс
TDataSet
- TDataSet
класс - один из
наиболее важных
объектов БД. Чтобы
начать работать с ним,
Вы должны взглянуть на
следующую иерархию:
TDataSet
|
TDBDataSet
|
|--
TTable
|--
TQuery
|--
TStoredProc
TDataSet
содержит абстрактные
методы там, где должно
быть непосредственное
управление данными.
TDBDataSet знает, как
обращаться с паролями
и то, что нужно
сделать, чтобы
присоединить Вас к
определенной таблице.
TTable знает (т.е. уже все
абстрактные методы
переписаны), как
обращаться с таблицей,
ее индексами и т.д.
Как
Вы увидите в далее, TQuery
имеет определенные
методы для обработки
SQL запросов.
TDataSet
- инструмент, который
Вы будете
использовать чтобы
открыть таблицу, и
перемещаться по ней.
Конечно, Вы никогда не
будете
непосредственно
создавать объект типа
TDataSet. Вместо этого, Вы
будете использовать
TTable, TQuery или других
потомков TDataSet
(например, TQBE). Полное
понимание работы
системы, и точное
значение TDataSet, будут
становиться все более
ясными по мере
прочтения этой главы.
На
наиболее
фундаментальном
уровне, Dataset это просто
набор записей, как
изображено на рис.1

Рис.1:
Любой dataset состоит из
ряда записей (каждая
содержит N полей) и
указатель на текущую
запись.
В
большинстве случаев
dataset будет иметь a
прямое, один к одному,
соответствие с
физической таблицей,
которая существует на
диске. Однако, в других
случаях Вы можете
исполнять запрос или
другое действие,
возвращающие dataset,
который содержит либо
любое подмножество
записей одной таблицы,
либо объединение (join)
между несколькими
таблицами. В тексте
будут иногда
использоваться
термины DataSet и TTable как
синонимы.
Обычно
в программе
используются объекты
типа TTable или TQuery,
поэтому в следующих
нескольких главах
будет предполагаться
существование объекта
типа TTable называемого
Table1.
Итак,
самое время начать
исследование TDataSet. Как
только Вы
познакомитесь с его
возможностями, Вы
начнете понимать,
какие методы
использует Delphi для
доступа к данным,
хранящимся на диске в
виде БД. Ключевой
момент здесь - не
забывать, что почти
всякий раз, когда
программист на Delphi
открывает таблицу, он
будет использовать
TTable или TQuery, которые
являются просто
некоторой надстройкой
над TDataSet.
- Открытие
и закрытие DataSet
В этой главе
Вы узнаете некоторые факты об
открытии и закрытии DataSet.
Если Вы
используете TTable для доступа к
таблице, то при открытии данной
таблицы заполняются некоторые
свойства TTable (количество записей
RecordCount, описание структуры таблицы
и т.д.).
Прежде всего,
Вы должны поместить во время
дизайна на форму объект TTable и
указать, с какой таблицей хотите
работать. Для этого нужно заполнить
в Инспекторе объектов свойства
DatabaseName и TableName. В DatabaseName можно либо
указать директорию, в которой лежат
таблицы в формате dBase или Paradox
(например, C:\DELPHI\DEMOS\DATA), либо выбрать
из списка псевдоним базы данных
(DBDEMOS). Псевдоним базы данных (Alias)
определяется в утилите Database Engine
Configuration. Теперь, если свойство Active
установить в True, то при запуске
приложения таблица будет
открываться автоматически.
Имеются два
различных способа открыть таблицу
во время выполнения программы. Вы
можете написать следующую строку
кода:
Table1.Open;
Или, если Вы
предпочитаете, то можете
установить свойство Active равное True:
Table1.Active := True;
Нет никакого
различия между результатом
производимым этими двумя
операциями. Метод Open, однако, сам
заканчивается установкой свойства
Active в True, так что может быть даже
чуть более эффективно использовать
свойство Active напрямую.
Также, как
имеются два способа открыть a
таблицу, так и есть два способа
закрыть ее. Самый простой способ
просто вызывать Close:
Table1.Close;
Или, если Вы
желаете, Вы можете написать:
Table1.Active := False;
Еще раз
повторим, что нет никакой
существенной разницы между двумя
этими способами. Вы должны только
помнить, что Open и Close это методы
(процедуры), а Active - свойство.
Навигация
(Перемещение по записям)
После
открытия a таблицы, следующим шагом
Вы должны узнать как перемещаться
по записям внутри него.
Следующий
обширный набор методов и свойства
TDataSet обеспечивает все , что Вам
нужно для доступа к любой
конкретной записи внутри таблицы:
procedure First;
procedure Last;
procedure Next;
procedure Prior;
property BOF: Boolean read FBOF;
property EOF: Boolean read FEOF;
procedure MoveBy(Distance: Integer);
Дадим
краткий обзор их функциональных
возможностей:
- Вызов
Table1.First перемещает Вас к первой
записи в таблице.
- Table1.Last
перемещает Вас к последней
записи.
- Table1.Next
перемещает Вас на одну запись
вперед.
- Table1.Prior
перемещает Вас на одну запись
Назад.
- Вы можете
проверять свойства BOF или EOF,
чтобы понять, находитесь ли Вы
в начале или в конце таблицы.
- Процедура
MoveBy перемещает Вас на N записей
вперед или назад в таблице. Нет
никакого функционального
различия между запросом Table1.Next
и вызовом Table1.MoveBy(1). Аналогично,
вызов Table1.Prior имеет тот же самый
результат, что и вызов Table1.MoveBy(-1).
Чтобы начать
использовать эти навигационные
методы, Вы должны поместить TTable,
TDataSource и TDBGrid на форму, также, как Вы
делали это в предыдущем уроке.
Присоедините DBGrid1 к DataSource1, и DataSource1
к Table1. Затем установите свойства
таблицы:
- в DatabaseName
имя подкаталога, где находятся
демонстрационные таблицы (или
псевдоним DBDEMOS);
- в TableName
установите имя таблицы CUSTOMER.
Если Вы
запустили программу, которая
содержит видимый элемент TDBGrid, то
увидите, что можно перемещаться по
записям таблицы с помощью полос
прокрутки (scrollbar) на нижней и правой
сторонах DBGrid.
Однако,
иногда нужно перемещаться по
таблице “программным путем”, без
использования возможностей,
встроенных в визуальные
компоненты. В следующих нескольких
абзацах объясняется как можно это
сделать.
Поместите
две кнопки на форму и назовите их Next
и Prior, как показано на рис.2.

Рис.2
: Next и Prior кнопки позволяют Вам
перемещаться по БД.
Дважды
щелкните на кнопке Next - появится
заготовка обработчика события:
procedure TForm1.NextClick(Sender:
TObject);
begin
end;
Теперь
добавьте a одну строчку кода так,
чтобы процедура выглядела так:
procedure TForm1.NextClick(Sender:
TObject);
begin
Table1.Next;
end;
Повторите те
же самые действия с кнопкой Prior, так,
чтобы функция связанная с ней
выглядела так:
procedure TForm1.PriorClick(Sender:
TObject);
begin
Table1.Prior;
end;
Теперь
запустите программу, и нажмите на
кнопки. Вы увидите, что они легко
позволяют Вам перемещаться по
записям в таблице.
Теперь
добавьте еще две кнопки и назовите
их First и Last, как показано на рис.3

Рис.3: Программа со
всеми четырьмя кнопками.
Сделайте то
же самое для новых кнопок.
procedure TForm1.FirstClick(Sender:
TObject);
begin
Table1.First;
end;
procedure TForm1.LastClick(Sender:
TObject);
begin
Table1.Last;
end;
Нет ничего
более простого чем эти
навигационные функции. First
перемещает Вас в начало таблицы, Last
перемещает Вас в конец таблицы, а Next
и Prior перемещают Вас на одну запись
вперед или назад.
TDataSet.BOF - read-only
Boolean свойство, используется для
проверки, находитесь ли Вы в начале
таблицы. Свойства BOF возвращает true в
трех случаях:
- После
того, как Вы открыли файл;
- После
того, как Вы вызывали TDataSet.First;
- После
того, как вызов TDataSet.Prior не
выполняется.
Первые два
пункта - очевидны. Когда Вы
открываете таблицу, Delphi помещает
Вас на первую запись; когда Вы
вызываете метод First, Delphi также
перемещает Вас в начало таблицы.
Третий пункт, однако, требует
небольшого пояснения: после того,
как Вы вызывали метод Prior много раз,
Вы могли добраться до начала
таблицы, и следующий вызов Prior будет
неудачным - после этого BOF и будет
возвращать True.
Следующий
код показывает самый общий пример
использования Prior, когда Вы
попадаете к началу a файла:
while not Table.Bof do begin
DoSomething;
Table1.Prior;
end;
В коде,
показанном здесь, гипотетическая
функция DoSomething будет вызвана сперва
на текущей записи и затем на каждой
следующей записи (от текущей и до
начала таблицы). Цикл будет
продолжаться до тех пор, пока вызов
Table1.Prior не сможет больше
переместить Вас на предыдущую
запись в таблице. В этот момент BOF
вернет True и программа выйдет из
цикла. (Чтобы оптимизировать
вышеприведенный код, установите
DataSource1.Enabled в False перед началом
цикла, и верните его в True после
окончания цикла.)
Все
сказанное относительно BOF также
применимо и к EOF. Другими словами,
код, приведенный ниже показывает
простой способ пробежать по всем
записям в a dataset:
Table1.First;
while not Table1.EOF do begin
DoSomething;
Table1.Next;
end;
Классическая
ошибка в случаях, подобных этому: Вы
входите в цикл while или repeat, но
забываете вызывать Table1.Next:
Table1.First;
repeat
DoSomething;
until Table1.EOF;
Если Вы
случайно написали такой код, то
ваша машина зависнет, и Вы сможете
выйти из цикла только нажав Ctrl-Alt-Del
и прервав текущий процесс. Также,
этот код мог бы вызвать проблемы,
если Вы открыли пустую таблицу. Так
как здесь используется цикл repeat, DoSomething был бы вызван
один раз, даже если бы нечего было
обрабатывать. Поэтому, лучше
использовать цикл while вместо repeat в ситуациях подобных
этой.
EOF возвращает
True в следующих трех случаях:
- После
того, как Вы открыли пустой
файл;
- После
того, как Вы вызывали TDataSet.Last;
- После
того, как вызов TDataSet.Next не
выполняется.
Единственная
навигационная процедура, которая
еще не упоминалась - MoveBy, которая
позволяет Вам переместиться на N
записей вперед или назад в таблице.
Если Вы хотите переместиться на две
записи вперед, то напишите:
MoveBy(2);
И если Вы
хотите переместиться на две записи
назад, то:
MoveBy(-2);
При
использовании этой функции Вы
должны всегда помнить, что DataSet - это
изменяющиеся объекты, и запись,
которая была пятой по счету в
предыдущий момент, теперь может
быть четвертой или шестой или
вообще может быть удалена...
Prior и Next - это
простые функции, которые вызывают
MoveBy.
- Поля
- В
большинстве случаев,
когда Вы хотите
получить доступ из
программы к
индивидуальные полям
записи, Вы можете
использовать одно из
следующих свойств или
методов, каждый из
которых принадлежат
TDataSet:
property Fields[Index:
Integer];
function
FieldByName(const FieldName: string):
TField;
property
FieldCount;
Свойство
FieldCount возвращает
число полей в текущей
структуре записи. Если
Вы хотите программным
путем прочитать имена
полей, то используйте
свойство Fields для
доступа к ним:
var
S: String;
begin
S :=
Fields[0].FieldName;
end;
Если
Вы работали с записью
в которой первое поле
называется CustNo, тогда
код показанный выше
поместит строку
“CustNo” в переменную S.
Если Вы хотите
получить доступ к
имени второго поля в
вышеупомянутом
примере, тогда Вы
могли бы написать:
S :=
Fields[1].FieldName;
Короче
говоря, индекс
передаваемый в Fields
(начинающийся с нуля),
и определяет номер
поля к которому Вы
получите доступ, т.е.
первое поле - ноль,
второе один, и так
далее.
Если
Вы хотите прочитать
текущее содержание
конкретного поля
конкретной записи, то
Вы можете
использовать свойство
Fields или
метод FieldsByName. Для
того, чтобы найти значение
первого поля записи,
прочитайте первый
элемент массива Fields:
S :=
Fields[0].AsString;
Предположим,
что первое поле в
записи содержит номер
заказчика, тогда код,
показанный выше,
возвратил бы строку
типа “1021”, “1031” или
“2058”. Если Вы хотели
получить доступ к этот
переменный, как к
числовой величине,
тогда Вы могли бы
использовать AsInteger
вместо AsString.
Аналогично, свойство
Fields включают AsBoolean,
AsFloat и AsDate.
Если
хотите, Вы можете
использовать функцию FieldsByName
вместо свойства Fields:
S :=
FieldsByName(‘CustNo’).AsString;
Как
показано в примерах
выше, и FieldsByName, и Fields
возвращают те же самые
данные. Два различных
синтаксиса
используются
исключительно для
того, чтобы обеспечить
программистов гибким
и удобным набором
инструментов для
программного доступа
к содержимому DataSet.
Давайте
посмотрим на простом
примере, как можно
использовать доступ к
полям таблицы во время
выполнения программы.
Создайте новый проект,
положите на форму
объект TTable, два
объекта ListBox и две
кнопки - “Fields” и
“Values” (см рис.4).
Соедините
объект TTable с таблицей
CUSTOMER, которая
поставляется вместе с
Delphi (DBDEMOS), не забудьте
открыть таблицу (Active =
True).

Рис.4:
Программа FLDS
показывает, как
использовать свойство
Fields.
Сделайте
Double click на кнопке Fields и
создайте a метод
который выглядит так:
procedure
TForm1.FieldsClick(Sender: TObject);
var
i: Integer;
begin
ListBox1.Clear;
for i := 0 to
Table1.FieldCount - 1 do
ListBox1.Items.Add(Table1.Fields[i].FieldName);
end;
Обработчик
события начинается с
очистки первого ListBox1,
затем он проходит
через все поля,
добавляя их имена один
за другим в ListBox1.
Заметьте, что цикл
показанный здесь
пробегает от 0 до FieldCount
- 1. Если Вы забудете
вычесть единицу из
FieldCount, то Вы получите
ошибку “List Index Out of
Bounds”, так как Вы
будете пытаться
прочесть имя поля
которое не существует.
Предположим,
что Вы ввели код
правильно, и заполнили
ListBox1 именами всех
полей в текущей
структуре записи.
В
Delphi существуют и
другие средства
которые позволяют Вам
получить ту же самую
информацию, но это
самый простой способ
доступа к именам полей
в Run Time.
Свойство
Fields позволяет Вам
получить доступ не
только именам полей
записи, но также и к
содержимому полей. В
нашем примере, для
второй кнопки напишем:
procedure
TForm1.ValuesClick(Sender: TObject);
var
i: Integer;
begin
ListBox2.Clear;
for i := 0 to
Table1.FieldCount - 1 do
ListBox2.Items.Add(Table1.Fields[i].AsString);
end;
Этот
код добавляет
содержимое каждого из
полей во второй listbox.
Обратите внимание, что
вновь счетчик
изменяется от нуля до
FieldCount - 1.
Свойство
Fields позволяет Вам
выбрать тип
результата написав
Fields[N].AsString. Этот и
несколько связанных
методов обеспечивают a
простой и гибкий
способ доступа к
данным, связанными с
конкретным полем. Вот
список доступных
методов который Вы
можете найти в
описании класса TField:
property
AsBoolean
property
AsFloat
property
AsInteger
property
AsString
property
AsDateTime
Всякий
раз (когда это имеет
смысл), Delphi сможет
сделать
преобразования.
Например, Delphi может
преобразовывать поле
Boolean к Integer или Float, или
поле Integer к String. Но не
будет преобразовывать
String к Integer, хотя и может
преобразовывать Float к
Integer. BLOB и Memo поля -
специальные случаи, и
мы их рассмотрим
позже. Если Вы хотите
работать с полями Date
или DateTime, то можете
использовать AsString и
AsFloat для доступа к ним.
Как
было объяснено выше,
свойство FieldByName
позволяет Вам
получить доступ к
содержимому
определенного поля
просто указав имя
этого поля:
S :=
Table1.FieldByName(‘CustNo’).AsString;
Это
- удобная технология,
которая имеет
несколько
преимуществ, когда
используется
соответствующим
образом. Например,
если Вы не уверены в
местонахождении поля,
или если Вы думаете,
что структура записи,
с которой Вы работаете
могла измениться, и
следовательно,
местонахождение поля
не определено.
- Работа
с Данными
- Следующие
методы позволяют Вам
изменить данные,
связанные с TTable:
procedure Append;
procedure
Insert;
procedure
Cancel;
procedure
Delete;
procedure
Edit;
procedure
Post;
Все
эти методы - часть
TDataSet, они унаследованы
и используются TTable и
TQuery.
Всякий
раз, когда Вы хотите
изменить данные, Вы
должны сначала
перевести DataSet в режим
редактирования. Как Вы
увидите, большинство
визуальных компонент
делают это
автоматически, и когда
Вы используете их, то
совершенно не будете
об этом заботиться.
Однако, если Вы хотите
изменить TTable
программно, Вам
придется использовать
вышеупомянутые
функции.
Имеется
a типичная
последовательность,
которую Вы могли бы
использовать при
изменении поля
текущей записи:
Table1.Edit;
Table1.FieldByName(‘CustName’).AsString
:= ‘Fred’;
Table1.Post;
Первая
строка переводит БД в
режим редактирования.
Следующая строка
присваивает значение
‘Fred’ полю ‘CustName’.
Наконец, данные
записываются на диск,
когда Вы вызываете Post.
При
использовании такого
подхода, Вы всегда
работаете с записями.
Сам факт перемещения к
следующей записи
автоматически
сохраняет ваши данные
на диск. Например,
следующий код будет
иметь тот же самый
эффект, что и код
показанный выше, плюс
этому будет
перемещать Вас на
следующую запись:
Table1.Edit;
Table1.FieldByName(‘CustNo’).AsInteger
:= 1234;
Table1.Next;
Общее
правило, которому
нужно следовать -
всякий раз, когда Вы
сдвигаетесь с текущей
записи, введенные Вами
данные будут записаны
автоматически. Это
означает, что вызовы First, Next, Prior и Last всегда
выполняют Post,
если Вы находились в
режиме
редактирования. Если
Вы работаете с данными
на сервере и
транзакциями, тогда
правила, приведенные
здесь, не применяются.
Однако, транзакции -
это отдельный вопрос с
их собственными
специальными
правилами, Вы увидите
это, когда прочитаете
о них в следующих
уроках.
Тем
не менее, даже если Вы
не работаете со
транзакциями, Вы
можете все же отменить
результаты вашего
редактирования в
любое время, до тех
пор, пока не вызвали
напрямую или косвенно
метод Post.
Например, если Вы
перевели таблицу в
режим редактирования,
и изменили данные в
одном или более полей,
Вы можете всегда
вернуть запись в
исходное состояние
вызовом метода Cancel.
Существуют
два метода, названные Append и Insert,
который Вы можете
использовать всякий
раз, когда Вы хотите
добавить новую запись
в DataSet. Очевидно имеет
больше смысла
использовать Append для
DataSets которые не
индексированы, но Delphi
не будет генерировать
exception если Вы
используете Append на
индексированной
таблице. Фактически,
всегда можно
использовать и Append, и Insert.
Продемонстрируем
работу методов на
простом примере. Чтобы
создать программу,
используйте TTable,
TDataSource и TdbGrid. Открыть
таблицу COUNTRY. Затем
разместите две кнопки
на форме и назовите их
‘Insert’ и ‘Delete’. Когда
Вы все сделаете, то
должна получиться
программа, показанная
на рис.5

Рис.5:
Программа может
вставлять и удалять
запись из таблицы COUNTRY.
Следующим
шагом Вы должен
связать код с кнопками
Insert и Delete:
procedure
TForm1.InsertClick(Sender: TObject);
begin
Table1.Insert;
Table1.FieldByName('Name').AsString
:= 'Russia';
Table1.FieldByName('Capital').AsString
:= 'Moscow';
Table1.Post;
end;
procedure
TForm1.DeleteClick(Sender: TObject);
begin
Table1.Delete;
end;
Процедура
показанная здесь
сначала переводит
таблицу в режим
вставки (новая запись
с незаполненными
полями вставляется в
текущую позицию dataset).
После вставки пустой
записи, следующим
этапом нужно
назначить значения
одному или большему
количеству полей.
Существует, конечно,
несколько различных
путей присвоить эти
значения. В нашей
программе Вы могли бы
просто ввести
информацию в новую
запись через DBGrid. Или
Вы могли бы разместить
на форме стандартную
строку ввода (TEdit) и
затем установить
каждое поле равным
значению, которое
пользователь
напечатал в этой
строке:
Table1.FieldByName(‘Name’).AsString
:= Edit1.Text;
Можно
было бы использовать
компоненты,
специально
предназначенные для
работы с данными в
DataSet.
Назначение
этой главы, однако,
состоит в том, чтобы
показать, как вводить
данные из программы.
Поэтому, в примере
вводимая информация
скомпилирована прямо
в код программы:
Table1.FieldByName('Name').AsString
:= 'Russia';
Один
из интересных
моментов в этом
примере это то, что
нажатие кнопки Insert
дважды подряд
автоматически
вызывает exception ‘Key
Violation’. Чтобы
исправить эту
ситуацию, Вы должны
либо удалить текущую
запись, или изменять
поля Name и Capital вновь
созданной записи.
Просматривая
код показанный выше,
Вы увидите, что просто
вставка записи и
заполнения ее полей не
достаточно для того,
чтобы изменить
физические данные на
диске. Если Вы хотите,
чтобы информация
записалась на диск, Вы
должны вызывать Post.
Если
после вызова Insert, Вы
решаете отказаться от
вставки новой записи,
то Вы можете вызвать
Cancel. Если Вы сделаете
это прежде, чем Вы
вызовете Post, то все что
Вы ввели после вызова
Insert будет отменено, и
dataset будет находиться
в состоянии, которое
было до вызова Insert.
Одно
дополнительное
свойство, которое Вы
должны иметь в виду
называется CanModify.
Если CanModify возвращает
False, то TTable находиться
в состоянии ReadOnly. В
противном случае CanModify
возвращает True и Вы
можете редактировать
или добавлять записи в
нее по желанию. CanModify -
само по себе ‘read only’
свойство. Если Вы
хотите установить DataSet
в состояние только на
чтение (Read Only), то Вы
должны использовать
свойство ReadOnly, не
CanModify.
- Использование
SetKey для поиска в
таблице
- Для
того, чтобы найти
некоторую величину в
таблице, программист
на Delphi может
использовать две
процедуры SetKey и GotoKey. Обе
эти процедуры
предполагают, что поле
по которому Вы ищете
индексировано. Delphi
поставляется с
демонстрационной
программой SEARCH,
которая показывает,
как использовать эти
запросы.
Чтобы
создать программу SEARCH,
поместите TTable, TDataSource,
TDBGrid, TButton, TLabel и TEdit на
форму, и расположите
их как показано на
рис.6. Назовите кнопку
Search, и затем соедините
компоненты БД так,
чтобы Вы видели в DBGrid1
таблицу Customer.

Рис.6:
Программа SEARCH
позволяет Вам ввести
номер заказчика и
затем найти его по
нажатию кнопки.
Вся
функциональность
программы SEARCH скрыта в
единственном методе,
который присоединен к
кнопке Search. Эта
функция считывает
строку, введенную в
окно редактора, и ищет
ее в колонке CustNo, и
наконец помещает
фокус на найденной
записи. В простейшем
варианте, код
присоединенный к
кнопке Search выглядит
так:
procedure
TSearchDemo.SearchClick(Sender: TObject);
begin
Table1.SetKey;
Table1.FieldByName(’CustNo’).AsString
:= Edit1.Text;
Table1.GotoKey;
end;
Первый
вызов в этой процедуре
установит Table1 в режим
поиска. Delphi должен
знать, что Вы
переключились в режим
поиска просто потому,
что свойство Fields
используется по
другому в этом режиме.
Далее, нужно присвоить
свойству Fields значение,
которое Вы хотите
найти. Для
фактического
выполнения поиска
нужно просто вызывать
Table1.GotoKey.
Если
Вы ищете не по
первичному индексу
файла, тогда Вы должны
определить имя
индекса, который Вы
используете в
свойстве IndexName.
Например, если таблица
Customer имеет вторичный
индекс по полю City,
тогда Вы должны
установить свойство
IndexName равным имени
индекса. Когда Вы
будете искать по этому
полю, Вы должны
написать:
Table1.IndexName
:= ’CityIndex’;
Table1.Active
:= True;
Table1.SetKey;
Table1.FieldByName(’City’).AsString
:= Edit1.Text;
Table1.GotoKey;
Запомните:
поиск не будет
выполняться, если Вы
не назначите
правильно индекс
(св-во IndexName). Кроме
того, Вы должны
обратить внимание, что
IndexName - это свойство
TTable, и не присутствует
в других прямых
потомках TDataSet или
TDBDataSet.
Когда
Вы ищете некоторое
значение в БД, всегда
существует
вероятность того, что
поиск окажется
неудачным. В таком
случае Delphi будет
автоматически
вызывать exception, но если
Вы хотите обработать
ошибку сами, то могли
бы написать примерно
такой код:
procedure
TSearchDemo.SearchClick(Sender: TObject);
begin
Cust.SetKey;
Cust.FieldByName('CustNo').AsString:=
CustNoEdit.Text;
if not
Cust.GotoKey then
raise
Exception.CreateFmt('Cannot find CustNo
%g',
[CustNo]);
end;
В
коде, показанном выше,
либо неверное
присвоение номера,
либо неудача поиска
автоматически
приведут к сообщению
об ошибке ‘Cannot find CustNo %g’.
Иногда
требуется найти не
точно совпадающее
значение, а близкое к
нему, для этого
следует вместо GotoKey
пользоваться методом GotoNearest.
- Использование
фильтров для
ограничения числа
записей в DataSet
Процедура ApplyRange позволяет Вам
установить фильтр, который
ограничивает диапазон
просматриваемых записей. Например,
в БД Customers, поле CustNo имеет диапазон
от 1,000 до 10,000. Если Вы хотите видеть
только те записи, которые имеют
номер заказчика между 2000 и 3000, то Вы
должны использовать метод ApplyRange, и еще два связанных с
ним метода. Данные методы работают
только с индексированным полем.
Вот
процедуры, которые Вы будете чаще
всего использовать при установке
фильтров:
procedure SetRangeStart;
procedure SetRangeEnd;
procedure ApplyRange;
procedure CancelRange;
Кроме того, у
TTable есть дополнительные методы для
управления фильтрами:
procedure EditRangeStart;
procedure EditRangeEnd;
procedure SetRange;
Для
использования этих процедур
необходимо:
- Сначала
вызвать SetRangeStart и
использовать свойство Fields для
определения начала диапазона.
- Затем
вызвать SetRangeEnd и вновь
использовать свойство Fields для
определения конца диапазона.
- Первые два
шага подготавливают фильтр, и
теперь все что Вам необходимо,
это вызвать ApplyRange, и новый
фильтр вступит в силу.
- Когда
нужно прекратить действие
фильтра - вызовите CancelRange.
Программа
RANGE, которая есть среди примеров
Delphi, показывает, как использовать
эти процедуры. Чтобы создать
программу, поместите TTable, TDataSource и
TdbGrid на форму. Соедините их так,
чтобы Вы видеть таблицу CUSTOMERS из
подкаталога DEMOS. Затем поместите
два объекта TLabel на форму и назовите
их ‘Start Range’ и ‘End Range’. Затем
положите на форму два объекта TEdit.
Наконец, добавьте кнопки ‘ApplyRange’ и
‘CancelRange’. Когда Вы все выполните,
форма имеет вид, как на рис.7

Рис.7: Программа RANGE
показывает как ограничивать число
записей таблицы для просмотра.
Процедуры SetRangeStart и SetRangeEnd позволяют Вам
указать первое и последнее
значения в диапазоне записей,
которые Вы хотите видеть. Чтобы
начать использовать эти процедуры,
сначала выполните double-click на кнопке ApplyRange, и создайте процедуру,
которая выглядит так:
procedure
TForm1.ApplyRangeBtnClick(Sender: TObject);
begin
Table1.SetRangeStart;
if RangeStart.Text <> '' then
Table1. Fields[0].AsString :=
RangeStart.Text;
Table1.SetRangeEnd;
if RangeEnd.Text <> '' then
Table1.Fields[0].AsString :=
RangeEnd.Text;
Table1.ApplyRange;
end;
Сначала
вызывается процедура SetRangeStart, которая
переводит таблицу в режим
диапазона (range mode). Затем Вы должны
определить начало и конец
диапазона. Обратите внимание, что
Вы используете свойство Fields для
определения диапазона:
Table1.Fields[0].AsString :=
RangeStart.Text;
Такое
использование свойства Fields - это
специальный случай, так как
синтаксис, показанный здесь, обычно
используется для установки
значения поля. Этот специальный
случай имеет место только после
того, как Вы перевели таблицу в
режим диапазона, вызвав SetRangeStart.
Заключительный
шаг в процедуре показанной выше -
вызов ApplyRange. Этот вызов
фактически приводит ваш запрос в
действие. После вызова ApplyRange, TTable
больше не в находится в режиме
диапазона, и свойства Fields
функционирует как обычно.
Обработчик
события нажатия кнопки ‘CancelRange’:
procedure
TForm1.CancelRangeBtnClick(Sender: TObject);
begin
Table1.CancelRange;
end;
- Обновление
(Refresh)
- Как
Вы уже знаете, любая
таблица, которую Вы
открываете всегда
“подвержена
изменению”. Короче
говоря, Вы должны
расценить таблицу
скорее как меняющуюся,
чем как статическую
сущность. Даже если Вы
- единственное лицо,
использующее данную
TTable, и даже если Вы не
работаете в сети,
всегда существует
возможность того, что
программа с которой Вы
работаете, может иметь
два различных пути
изменения данных в
таблице. В результате,
Вы должны всегда
знать, необходимо ли
Вам обновить вид
таблицы на экране.
Функция
Refresh
связана с функцией Open, в том
смысле что она
считывает данные, или
некоторую часть
данных, связанных с
данной таблицей.
Например, когда Вы
открываете таблицу,
Delphi считывает данные
непосредственно из
файла БД. Аналогично,
когда Вы
Регенерируете
таблицу, Delphi считывает
данные напрямую из
таблицы. Поэтому Вы
можете использовать
эту функцию, чтобы
перепрочитать
таблицу, если Вы
думаете что она могла
измениться. Быстрее и
эффективнее, вызывать Refresh, чем
вызывать Close и
затем Open.
Имейте
ввиду, однако, что
обновление TTable может
иногда привести к
неожиданным
результатам. Например,
если a пользователь
рассматривает запись,
которая уже была
удалена, то она
исчезнет с экрана в
тот момент, когда
будет вызван Refresh.
Аналогично, если некий
другой пользователь
редактировал данные,
то вызов Refresh приведет
к динамическому
изменению данных.
Конечно маловероятно,
что один пользователь
будет изменять или
удалять запись в то
время, как другой
просматривает ее, но
это возможно.
- Закладки
(Bookmarks)
- Часто
бывает полезно
отметить текущее
местоположение в
таблице так, чтобы
можно было быстро
возвратиться к этому
месту в дальнейшем.
Delphi обеспечивает эту
функциональную
возможность
посредством трех
методов, которые
используют понятие закладки.
function GetBookmark:
TBookmark;
(устанавливает
закладку в таблице)
procedure
GotoBookmark(Bookmark: TBookmark);
(переходит
на закладку)
procedure
FreeBookmark(Bookmark: TBookmark);
(освобождает
память)
Как
Вы можете видеть,
вызов GetBookmark
возвращает переменную
типа TBookmark. TBookmark
содержит достаточное
количество
информации, чтобы Delphi
мог найти
местоположение к
которому относится
этот TBookmark. Поэтому Вы
можете просто
передавать этот TBookmark
функции GotoBookmark, и
будете немедленно
возвращены к
местоположению,
связанному с этой
закладкой.
Обратите
внимание, что вызов GetBookmark
распределяет память
для TBookmark, так что Вы
должны вызывать FreeBookmark до
окончания вашей
программы, и перед
каждой попыткой
повторного
использования Tbookmark (в
GetBookMark).
-
- Создание
Связанных Курсоров
(Linked cursors)
Связанные
курсоры позволяют программистам
определить отношение один ко
многим (one-to-many relationship). Например,
иногда полезно связать таблицы
CUSTOMER и ORDERS так, чтобы каждый раз,
когда пользователь выбирает имя
заказчика, то он видит список
заказов связанных с этим
заказчиком. Иначе говоря, когда
пользователь выбирает запись о
заказчике, то он может
просматривать только заказы,
сделанные этим заказчиком.
Программа
LINKTBL демонстрирует, как создать
программу которая использует
связанные курсоры. Чтобы создать
программу заново, поместите два
TTable, два TDataSources и два TDBGrid на форму.
Присоедините первый набор таблице
CUSTOMER, а второй к таблице ORDERS.
Программа в этой стадии имеет вид,
показанный на рис.8

Рис.8:
Программа LINKTBL показывает, как
определить отношения между двумя
таблицами.
Следующий
шаг должен связать таблицу ORDERS с
таблицей CUSTOMER так, чтобы Вы видели
только те заказы, которые связанные
с текущей записью в таблице
заказчиков. В первой таблице
заказчик однозначно
идентифицируется своим номером -
поле CustNo. Во второй таблице
принадлежность заказа
определяется также номером
заказчика в поле CustNo.
Следовательно, таблицы нужно
связывать по полю CustNo в обоих
таблицах (поля могут иметь
различное название, но должны быть
совместимы по типу). Для этого, Вы
должны сделать три шага, каждый из
которых требует некоторого
пояснения:
- Установить
свойство Table2.MasterSource = DataSource1
- Установить
свойство Table2.MasterField = CustNo
- Установить
свойство Table2.IndexName = CustNo
Если Вы
теперь запустите программу, то
увидите, что обе таблицы связаны
вместе, и всякий раз, когда Вы
перемещаетесь на новую запись в
таблице CUSTOMER, Вы будете видеть
только те записи в таблице ORDERS,
которые принадлежат этому
заказчику.
Свойство
MasterSource в Table2 определяет DataSource от
которого Table2 может получить
информацию. То есть, оно позволяет
таблице ORDERS знать, какая запись в
настоящее время является текущей в
таблице CUSTOMERS.
Но тогда
возникает вопрос: Какая еще
информация нужна Table2 для того,
чтобы должным образом
отфильтровать содержимое таблицы
ORDERS? Ответ состоит из двух частей:
- Требуется
имя поля по которому связанны
две таблицы.
- Требуется
индекс по этому полю в таблице
ORDERS (в таблице ‘многих
записей’), которая будет
связываться с таблицей
CUSTOMER(таблице в которой
выбирается ‘одна запись’).
Чтобы
правильно воспользоваться
информацией описанной здесь, Вы
должны сначала проверить, что
таблица ORDERS имеет нужные индексы.
Если этот индекс первичный, тогда
не нужно дополнительно указывать
его в поле IndexName, и поэтому Вы можете
оставить это поле незаполненным в
таблице TTable2 (ORDERS). Однако, если
таблица связана с другой через
вторичный индекс, то Вы должны явно
определять этот индекс в поле IndexName
связанной таблицы.
В примере
показанном здесь таблица ORDERS не
имеет первичного индекса по полю
CustNo, так что Вы должны явно задать в
свойстве IndexName индекс CustNo.
Недостаточно,
однако, просто yпомянуть имя
индекса, который Вы хотите
использовать. Некоторые индексы
могут содержать несколько полей,
так что Вы должны явно задать имя
поля, по которому Вы хотите связать
две таблицы. Вы должны ввести имя
‘CustNo’ в свойство Table2.MasterFields. Если
Вы хотите связать две таблицы
больше чем по одному полю, Вы должны
внести в список все поля, помещая
символ ‘|’ между каждым:
Table1.MasterFields := ‘CustNo |
SaleData | ShipDate’;
В данном
конкретном случае, выражение,
показанное здесь, не имеет смысла,
так как хотя поля SaleData и ShipDate также
индексированы, но не дублируются в
таблице CUSTOMER. Поэтому Вы должны
ввести только поле CustNo в свойстве
MasterFields. Вы можете определить это
непосредственно в редакторе
свойств, или написать код подобно
показанному выше. Кроме того, поле
(или поля) связи можно получить,
вызвав редактор связей - в
Инспекторе Объектов дважды
щелкните на свойство MasterFields (рис.10)

Рис.10:
Редактор связей для построения
связанных курсоров.
Важно
подчеркнуть, что данная глава
охватила только один из нескольких
путей, которым Вы можете создать
связанные курсоры в Delphi. В главе о
запросах будет описан второй метод,
который будет обращен к тем кто
знаком с SQL.
- Основные
понятия о TDataSource
- Класс
TDataSource используется в
качестве проводника
между TTable или TQuery и
компонентами,
визуализирующими
данные, типа TDBGrid, TDBEdit
и TDBComboBox (data-aware components).
В большинстве случаев,
все, что нужно сделать
с DataSource - это указать в
свойстве DataSet
соответствующий TTable
или TQuery. Затем, у data-aware
компонента в свойстве
DataSource указывается
TDataSource, который
используется в
настоящее время.
TDataSource
также имеет свойство
Enabled, и оно может быть
полезно всякий раз,
когда Вы хотите
временно отсоединить,
например, DBGrid от
таблицы или запроса.
Эти требуется,
например, если нужно
программно пройти
через все записи в
таблице. Ведь, если
таблица связана с
визуальными
компонентами (DBGrid, DBEdit
и т.п.), то каждый раз,
когда Вы вызываете
метод TTable.Next,
визуальные компоненты
будут
перерисовываться.
Даже если само
сканирование в
таблице двух или трех
тысяч записей не
займет много времени,
то может
потребоваться
значительно больше
времени, чтобы столько
же раз перерисовать
визуальные
компоненты. В случаях
подобных этому, лучше
всего установить поле
DataSource.Eabled в False. Это
позволит Вам
просканировать записи
без перерисовки
визуальных компонент.
Это единственная
операция может
увеличить скорость в
некоторых случаях на
несколько тысяч
процентов.
Свойство
TDataSource.AutoEdit указывает,
переходит ли DataSet
автоматически в режим
редактирования при
вводе текста в data-aware
объекте.
- Использование
TDataSource для проверки
состояния БД:
- TDataSource
имеет три ключевых
события, связанных с
состоянием БД
OnDataChange
OnStateChange
OnUpdateData
OnDataChange
происходит всякий раз,
когда Вы переходите на
новую запись, или
состояние DataSet
сменилось с dsInactive на
другое, или начато
редактирование.
Другими словами, если
Вы вызываете Next, Previous,
Insert, или любой другой
запрос, который должен
привести к изменению
данных, связанных с
текущей записью, то
произойдет событие
OnDataChange. Если в
программе нужно
определить момент,
когда происходит
переход на другую
запись, то это можно
сделать в обработчике
события OnDataChange:
procedure
TForm1.DataSource1DataChange(Sender:
TObject; Field: TField);
begin
if
DataSource1.DataSet.State = dsBrowse then
begin
DoSomething;
end;
end;
Событие
OnStateChange
событие происходит
всякий раз, когда
изменяется текущее
состояние DataSet. DataSet
всегда знает, в каком
состоянии он
находится. Если Вы
вызываете Edit, Append или
Insert, то TTable знает, что
он теперь находится в
режиме редактирования
(dsEdit или dsInsert).
Аналогично, после
того, как Вы делаете
Post, то TTable знает что
данные больше не
редактируется, и
переключается обратно
в режим просмотра
(dsBrowse).
Dataset
имеет шесть различных
возможных состояний,
каждое из которых
включено в следующем
перечисляемом типе:
TDataSetState
= (dsInactive, dsBrowse, dsEdit,
dsInsert,
dsSetKey,
dsCalcFields);
В
течение обычного
сеанса работы, БД
часто меняет свое
состояние между Browse,
Edit, Insert и другими
режимами. Если Вы
хотите отслеживать
эти изменения, то Вы
можете реагировать на
них написав примерно
такой код:
procedure
TForm1.DataSource1StateChange(Sender:
TObject);
var
S: String;
begin
case
Table1.State of
dsInactive: S
:= 'Inactive';
dsBrowse: S
:= 'Browse';
dsEdit: S :=
'Edit';
dsInsert: S
:= 'Insert';
dsSetKey: S
:= 'SetKey';
dsCalcFields:
S := 'CalcFields';
end;
Label1.Caption
:= S;
end;
OnUpdateData
событие происходит
перед тем, как данные в
текущей записи будут
обновлены. Например,
OnUpdateEvent будет
происходить между
вызовом Post и
фактическим
обновлением
информации на диске.
События,
генерируемые TDataSource
могут быть очень
полезны. Иллюстрацией
этого служит
следующий пример. Эта
программа работает с
таблицей COUNTRY, и
включает TTable, TDataSource,
пять TEdit, шесть TLlabel,
восемь кнопок и
панель.
Действительное
расположение
элементов показано на
рис.11. Обратите
внимание, что шестой
TLabel расположен на
панели внизу главной
формы.

Рис.11:
Программа STATE
показывает, как
отслеживать текущее
состояние таблицы.
Для
всех кнопок напишите
обработчики, вроде:
procedure
TForm1.FirstClick(Sender: TObject);
begin
Table1.First;
end;
В
данной программе есть
одна маленькая
хитрость, которую Вы
должны понять, если
хотите узнать, как
работает программа.
Так как есть пять
отдельных редакторов
TEdit на главной форме,
то хотелось бы иметь
некоторый способ
обращаться к ним
быстро и легко. Один
простой способ
состоит в том, чтобы
объявить массив
редакторов:
Edits:
array[1..5] of TEdit;
Чтобы
заполнить массив, Вы
можете в событии OnCreate
главной формы
написать:
procedure
TForm1.FormCreate(Sender: TObject);
var
i: Integer;
begin
for i := 1 to
5 do
Edits[i] :=
TEdit(FindComponent('Edit' +
IntToStr(i)));
Table1.Open;
end;
Код
показанный здесь
предполагает, что
первый редактор,
который Вы будете
использовать назовем
Edit1, второй Edit2, и т.д.
Существование этого
массива позволяет
очень просто
использовать событие
OnDataChange, чтобы
синхронизировать
содержание объектов
TEdit с содержимом
текущей записи в DataSet:
procedure
TForm1.DataSource1DataChange(Sender:
TObject;
Field:
TField);
var
i: Integer;
begin
for i := 1 to
5 do
Edits[i].Text
:= Table1.Fields[i - 1].AsString;
end;
Всякий
раз, когда вызывается
Table1.Next, или любой
другой из
навигационных
методов, то будет
вызвана процедура
показанная выше. Это
обеспечивает то, что
все редакторы всегда
содержат данные из
текущей записи.
Всякий
раз, когда вызывается
Post, нужно выполнить
противоположное
действие, то есть
взять информацию из
редакторов и
поместить ее в текущую
запись. Выполнить это
действие, проще всего
в обработчике события
TDataSource.OnUpdateData, которое
происходит всякий раз,
когда вызывается Post:
procedure
TForm1.DataSource1UpdateData(Sender:
TObject);
var
i: Integer;
begin
for i := 1 to
5 do
Table1.Fields[i
- 1].AsString := Edits[i].Text;
end;
Программа
будет автоматически
переключатся в режим
редактирования каждый
раз, когда Вы вводите
что-либо в одном из
редакторов. Это
делается в
обработчике события
OnKeyDown (укажите этот
обработчик ко всем
редакторам):
procedure
TForm1.Edit1KeyDown(Sender: TObject;
var Key:
Word; Shift: TShiftState);
begin
if
DataSource1.State <> dsEdit then
Table1.Edit;
end;
Этот
код показывает, как Вы
можете использовать
св-во State
DataSource, чтобы
определить текущий
режим DataSet.
Обновление
метки в статусной
панели происходит при
изменении состояния
таблицы:
procedure
TForm1.DataSource1StateChange(Sender:
TObject);
var
s : String;
begin
case
DataSource1.State of
dsInactive :
s:='Inactive';
dsBrowse :
s:='Browse';
dsEdit :
s:='Edit';
dsInsert :
s:='Insert';
dsSetKey :
s:='SetKey';
dsCalcFields
: s:='CalcFields';
end;
Label6.Caption:=s;
end;
Данная
программа является
демонстрационной и ту
же задачу можно решить
гораздо проще, если
использовать объекты
TDBEdit.
- Отслеживание
состояния DataSet
В предыдущей
части Вы узнали, как использовать
TDataSource, чтобы узнать текущее
состоянии TDataSet. Использование
DataSource - это простой путь выполнения
данной задачи. Однако, если Вы
хотите отслеживать эти события без
использования DataSource, то можете
написать свои обработчики событий
TTable и TQuery:
property OnOpen
property OnClose
property BeforeInsert
property AfterInsert
property BeforeEdit
property AfterEdit
property BeforePost
property AfterPost
property OnCancel
property OnDelete
property OnNewRecord
Большинство
этих свойств очевидны. Событие
BeforePost функционально подобно
событию TDataSource.OnUpdateData, которое
объяснено выше. Другими словами,
программа STATE работала бы точно
также, если бы Вы отвечали не на
DataSource1.OnUpdateData а на Table1.BeforePost.
Конечно, в первом случае Вы должен
иметь TDataSource на форме, в то время,
как во втором этого не требуется.
|