Статья содержит всесторонний обзор основных фактов которые Вы должны знать, прежде чем начать писать программы, работающие с Базами Данных (БД). Прочитав эту статью, Вы должны понять большинство механизмов доступа к данным, которые есть в Delphi.
Более подробно здесь рассказывается о TTable и TDataSource.
Имеются несколько основных компонент(объектов), которые Вы будете использовать постоянно для доступа к БД. Эти объекты могут быть разделены на три группы:
Первая группа включаеторые используются для управления таблицами и запросами. Эта группа сосредотачивается вокруг компонент типа TTable, TQuery, TDataSet и TField. В Палитре Компонент эти объекты расположены на странице Data Access.
Вторая важная группа классов - визуальные, которые показывают данные пользователю, и позволяют ему просматривать и модифицировать их. Эта группа классов включает компоненты типа TDBGrid, TDBEdit, TDBImage и TDBComboBox. В Палитре Компонент эти объекты расположены на странице Data Controls.
Имеется и третий тип, который используется для того, чтобы связать предыдущие два типа объектов. К третьему типу относится только невизуальный компонент TDataSource.
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.
Если Вы используете 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);
Дадим краткий обзор их функциональных возможностей:
Чтобы начать использовать эти навигационные методы, Вы должны поместить TTable, TDataSource и TDBGrid на форму, также, как Вы делали это в предыдущем уроке. Присоедините DBGrid1 к DataSource1, и DataSource1 к Table1. Затем установите свойства таблицы:
Если Вы запустили программу, которая содержит видимый элемент 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 в трех случаях:
Первые два пункта - очевидны. Когда Вы открываете таблицу, 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 в следующих трех случаях:
Единственная навигационная процедура, которая еще не упоминалась - 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.
Для того, чтобы найти некоторую величину в таблице, программист на 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.
Процедура 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;
Для использования этих процедур необходимо:
Программа RANGE, которая есть среди примеров Delphi, показывает, как использовать эти процедуры. Чтобы создать программу, поместите TTable, TDataSource и TdbGrid на форму. Соедините их так, чтобы Вы видеть таблицу CUSTOMERS из подкаталога DEMOS. Затем поместите два объекта TLabel на форму и назовите их 'Start Range' и 'End Range'. Затем положите на форму два объекта TEdit. Наконец, добавьте кнопки 'ApplyRange' и 'CancelRange'. Когда Вы все выполните, форма имеет вид, как на рис.7
Рис.7: Программа RANGE показывает как ограничивать число записей таблицы для просмотра.
Процедуры SetRangeStart и SetRangeEnd позволяют Вам указать первое и последнее значения в диапазоне записей, которые Вы хотите видеть. Чтобы начать использовать эти процедуры, сначала выполните double-clickv на кнопке 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;
Как Вы уже знаете, любая таблица, которую Вы открываете всегда "подвержена изменению". Короче говоря, Вы должны расценить таблицу скорее как меняющуюся, чем как статическую сущность. Даже если Вы - единственное лицо, использующее данную TTable, и даже если Вы не работаете в сети, всегда существует возможность того, что программа с которой Вы работаете, может иметь два различных пути изменения данных в таблице. В результате, Вы должны всегда знать, необходимо ли Вам обновить вид таблицы на экране.
Функция Refresh связана с функцией Open, в том смысле что она считывает данные, или некоторую часть данных, связанных с данной таблицей. Например, когда Вы открываете таблицу, Delphi считывает данные непосредственно из файла БД. Аналогично, когда Вы Регенерируете таблицу, Delphi считывает данные напрямую из таблицы. Поэтому Вы можете использовать эту функцию, чтобы перепрочитать таблицу, если Вы думаете что она могла измениться. Быстрее и эффективнее, вызывать Refresh, чем вызывать Close и затем Open.
Имейте ввиду, однако, что обновление TTable может иногда привести к неожиданным результатам. Например, если a пользователь рассматривает запись, которая уже была удалена, то она исчезнет с экрана в тот момент, когда будет вызван Refresh. Аналогично, если некий другой пользователь редактировал данные, то вызов Refresh приведет к динамическому изменению данных. Конечно маловероятно, что один пользователь будет изменять или удалять запись в то время, как другой просматривает ее, но это возможно.
Часто бывает полезно отметить текущее местоположение в таблице так, чтобы можно было быстро возвратиться к этому месту в дальнейшем. Delphi обеспечивает эту функциональную возможность посредством трех методов, которые используют понятие закладки.
function GetBookmark: TBookmark; (устанавливает закладку в таблице) procedure GotoBookmark(Bookmark: TBookmark); (переходит на закладку) procedure FreeBookmark(Bookmark: TBookmark); (освобождает память)
Как Вы можете видеть, вызов GetBookmark возвращает переменную типа TBookmark. TBookmark содержит достаточное количество информации, чтобы Delphi мог найти местоположение к которому относится этот TBookmark. Поэтому Вы можете просто передавать этот TBookmark функции GotoBookmark, и будете немедленно возвращены к местоположению, связанному с этой закладкой.
Обратите внимание, что вызов GetBookmark распределяет память для TBookmark, так что Вы должны вызывать FreeBookmark до окончания вашей программы, и перед каждой попыткой повторного использования Tbookmark (в GetBookMark).
Связанные курсоры позволяют программистам определить отношение один ко многим (one-to-many relationship). Например, иногда полезно связать таблицы CUSTOMER и ORDERS так, чтобы каждый раз, когда пользователь выбирает имя заказчика, то он видит список заказов связанных с этим заказчиком. Иначе говоря, когда пользователь выбирает запись о заказчике, то он может просматривать только заказы, сделанные этим заказчиком.
Программа LINKTBL демонстрирует, как создать программу которая использует связанные курсоры. Чтобы создать программу заново, поместите два TTable, два TDataSources и два TDBGrid на форму. Присоедините первый набор таблице CUSTOMER, а второй к таблице ORDERS. Программа в этой стадии имеет вид, показанный на рис.8
Рис.8: Программа LINKTBL показывает, как определить отношения между двумя таблицами.
Следующий шаг должен связать таблицу ORDERS с таблицей CUSTOMER так, чтобы Вы видели только те заказы, которые связанные с текущей записью в таблице заказчиков. В первой таблице заказчик однозначно идентифицируется своим номером - поле CustNo. Во второй таблице принадлежность заказа определяется также номером заказчика в поле CustNo. Следовательно, таблицы нужно связывать по полю CustNo в обоих таблицах (поля могут иметь различное название, но должны быть совместимы по типу). Для этого, Вы должны сделать три шага, каждый из которых требует некоторого пояснения:
Если Вы теперь запустите программу, то увидите, что обе таблицы связаны вместе, и всякий раз, когда Вы перемещаетесь на новую запись в таблице CUSTOMER, Вы будете видеть только те записи в таблице ORDERS, которые принадлежат этому заказчику.
Свойство MasterSource в Table2 определяет DataSource от которого Table2 может получить информацию. То есть, оно позволяет таблице ORDERS знать, какая запись в настоящее время является текущей в таблице CUSTOMERS.
Но тогда возникает вопрос: Какая еще информация нужна Table2 для того, чтобы должным образом отфильтровать содержимое таблицы ORDERS? Ответ состоит из двух частей:
Чтобы правильно воспользоваться информацией описанной здесь, Вы должны сначала проверить, что таблица 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 используется в качестве проводника между 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 имеет три ключевых события, связанных с состоянием БД 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.
В предыдущей части Вы узнали, как использовать 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 на форме, в то время, как во втором этого не требуется.
Назад | Содержание | Вперед