Навигация (Перемещение по записям)

 

После открытия таблицы следующим шагом Вы должны узнать как перемещаться по записям внутри него.

Следующий обширный набор методов и свойства 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. Затем установите свойства таблицы:

1. В DatabaseName имя подкаталога (или псевдоним, в нашем случае – это Example);

2. В TableName установите имя таблицы Example.

Если Вы запустили программу, которая содержит видимый элемент TDBGrid, то увидите, что можно перемещаться по записям таблицы с помощью полос прокрутки (scrollbar) на нижней и правой сторонах DBGrid.

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

Поместите две кнопки на форму и назовите их «Предыдущий» и «Следующий», как показано на рис. 1.3.

Рис. 1.3. Кнопки «Предыдущий» и «Следующий»

позволяют Вам перемещаться по БД

Дважды щелкните на кнопке «Следующий» - появится заготовка обработчика события:

 

procedure TForm1.NextClick(Sender: TObject);

begin

end;

 

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

 

procedure TForm1.NextClick(Sender: TObject);

begin

Table1.Next;

end;

Повторите те же самые действия с кнопкой «Предыдущий» так, чтобы функция связанная с ней выглядела так:

 

procedure TForm1.PriorClick(Sender: TObject);

begin

Table1.Prior;

end;

 

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

Теперь добавьте еще две кнопки и назовите их «На первую запись» и «На последнюю запись», как показано на рис. 1.4.

Сделайте то же самое для новых кнопок.

 

procedure TForm1.FirstClick(Sender: TObject);

begin

Table1.First;

end;

 

Рис. 1.4. Программа со всеми четырьмя кнопками

 

procedure TForm1.LastClick(Sender: TObject);

begin

Table1.Last;

end;

 

Нет ничего более простого чем эти навигационные функции. First перемещает Вас в начало таблицы, Last перемещает Вас в конец таблицы, а Next и Prior перемещают Вас на одну запись вперед или назад.

TDataSet.BOF - read-only Boolean свойство, используется для проверки, находитесь ли Вы в начале таблицы. Свойства BOF возвращает true в трех случаях:

1. После того, как Вы открыли файл;

2. После того, как Вы вызывали TDataSet.First;

3. После того, как вызов TDataSet.Prior не выполняется.

Первые два пункта - очевидны. Когда Вы открываете таблицу, Delphi помещает Вас на первую запись; когда Вы вызываете метод First, Delphi также перемещает Вас в начало таблицы. Третий пункт, однако, требует небольшого пояснения: после того, как Вы вызывали метод Prior много раз, Вы могли добраться до начала таблицы, и следующий вызов Prior будет неудачным - после этого BOF и будет возвращать True.

Следующий код показывает самый общий пример использования Prior, когда Вы попадаете к началу файла:

 

while not Table.Bof do

begin

//Список операторов

Table1.Prior;

end;

 

В коде, показанном здесь, список операторов будет вызван сперва на текущей записи и затем на каждой следующей записи (от текущей и до начала таблицы). Цикл будет продолжаться до тех пор, пока вызов Table1.Prior не сможет больше переместить Вас на предыдущую запись в таблице. В этот момент BOF вернет True и программа выйдет из цикла. (Чтобы оптимизировать вышеприведенный код, установите DataSource1.Enabled в False перед началом цикла и верните его в True после окончания цикла.)

Все сказанное относительно BOF также применимо и к EOF. Другими словами, код, приведенный ниже, показывает простой способ пробежать по всем записям в dataset:

 

Table1.First;

while not Table.Eof do

begin

//Список операторов

Table1.Next;

end;

 

Классическая ошибка в случаях, подобных этому: Вы входите в цикл while или repeat, но забываете вызывать Table1.Next:

Table1.First;

repeat

//Список операторов

until Table1.EOF;

 

Если Вы случайно написали такой код, то ваша машина зависнет, и Вы сможете выйти из цикла, только нажав Ctrl-Alt-Del и прервав текущий процесс. Также, этот код мог бы вызвать проблемы, если Вы открыли пустую таблицу. Так как здесь используется цикл repeat, «Список операторов» был бы вызван один раз, даже если бы нечего было обрабатывать. Поэтому, лучше использовать цикл while вместо repeat в ситуациях подобных этой.

EOF возвращает True в следующих трех случаях:

1. После того, как Вы открыли пустой файл;

2. После того, как Вы вызывали TDataSet.Last;

3. После того, как вызов 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;

 

Если Вы работали с записью в которой первое поле называется Sht_faktura, тогда код, показанный выше поместит строку “ Sht_faktura ” в переменную S. Если Вы хотите получить доступ к имени второго поля в вышеупомянутом примере, тогда Вы могли бы написать:

 

S := Fields[1].FieldName;

 

Короче говоря, индекс, передаваемый в Fields (начинающийся с нуля), и определяет номер поля, к которому Вы получите доступ, т.е. первое поле - ноль, второе - один и так далее.

Если Вы хотите прочитать текущее содержание конкретного поля конкретной записи, то Вы можете использовать свойство Fields или метод FieldsByName. Для того, чтобы найти значение первого поля записи, прочитайте первый элемент массива Fields:

 

S := Fields[3].AsString;

 

Если хотите, Вы можете использовать функцию FieldsByName вместо свойства Fields:

 

S := FieldsByName(‘Pokupatel’).AsString;

 

Как показано в примерах выше, и FieldsByName, и Fields возвращают те же самые данные. Два различных синтаксиса используются исключительно для того, чтобы обеспечить программистов гибким и удобным набором инструментов для программного доступа к содержимому DataSet.

Давайте посмотрим на простом примере как можно использовать доступ к полям таблицы во время выполнения программы. Создайте новый проект, перетащите на форму компоненты DataSource, TTable, два объекта ListBox и две кнопки - “Fields” и “Values” (см. рис. 1.5).

Сделайте Double click на кнопке «Поля» и создайте метод, который выглядит так:

 

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;

Рис. 1.5. Программа, показывающая как использовать свойство Fields

 

Обработчик события начинается с очистки первого ListBox1, затем он проходит через все поля, добавляя их имена один за другим в ListBox1. Заметьте, что цикл, показанный здесь пробегает от 0 до FieldCount - 1. Если Вы забудете вычесть единицу из FieldCount, то Вы получите ошибку “List Index Out of Bounds”, так как Вы будете пытаться прочесть имя поля, которое не существует.

В 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. Этот и несколько связанных методов обеспечивают простой и гибкий способ доступа к данным, связанным с конкретным полем. Вот список доступных методов, который Вы можете найти в описании класса TField:

 

property AsBoolean

property AsFloat

property AsInteger

property AsString

property AsDateTime

 

Всякий раз (когда это имеет смысл) Delphi сможет сделать преобразования. Например, Delphi может преобразовывать поле Boolean к Integer или Float, или поле Integer к String. Но не будет преобразовывать String к Integer, хотя и может преобразовывать Float к Integer. Если Вы хотите работать с полями Date или DateTime, то можете использовать AsString и AsFloat для доступа к ним.

Как было объяснено выше, свойство FieldByName позволяет Вам получить доступ к содержимому определенного поля, просто указав имя этого поля:

 

S := Table1.FieldByName(‘Pokupatel’).AsString;

 

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

Наше приложение выглядит, конечно, очень плохо. Шапка таблицы содержит непонятные пользователю имена полей, а надо, чтобы были написаны нормальные заголовки по-русски. Для редактирования отдельных полей служит Редактор Полей. Вызвать его проще всего двойным щелчком на компоненте Table1.Сначала вы увидите пустое поле этого редактора (рис. 1.6,а). Щелкните на нем правой кнопкой мыши и из всплывающего меню выберите раздел Add fields... (добавить поля). Вы увидите окно, изображенное на рис. 1.6,б, в котором содержится список всех полей таблицы. Выберите из него курсором мыши интересующие поля. Если вы при этом будете держать нажатой клавишу Ctrl, то может выделить любую комбинацию полей. Однако имейте в виду, что только к тем полям, которые вы добавите, вы сможете в дальнейшем обращаться. Так что в данном случае вам имеет смысл выделить все поля. Выделив поля, щелкните на ОК, и вы вернетесь к основному окну Редактору Полей, но в нем уже будет содержаться список добавленных полей (рис. 1.6, в).

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

Выделите в списке какое-то поле и посмотрите его свойства в Инспекторе Объектов. Вы увидите, что каждое поле - это объект, причем его класс зависит от типа поля: TStringField, TSmallintField, TBooIeanFicldи т.п. Все эти классы являются производными от TField— базового класса полей. Таким образом, каждое поле является объектом и обладает множеством свойств. Рассмотрим основные из них, которые чаще всего необходимо задавать.

 

а) б)

в)

Рис. 1.6. Редактор полей: исходное состояние (а), окно выбора (б), состояние после выбора (в).

Свойство Alignmentопределяет выравнивание отображаемого текста внутри солонки таблицы: влево, вправо или по центру.

Свойство DisplayLabelсоответствует заголовку столбца данного поля. Например, для поля Sht_faktura значение DisplayLabelможно задать равным «Счет-фактура», для “Pokupatel” — «Покупатель» и т.д.

Свойство DisplayWidthопределяет ширину колонки — число символов.

Рис. 1.7. Приложение с установленными свойствами полей

 

Свойства EditMaskдля строк и EditFormatдля чисел определяют форматы дображения данных.

Свойство ReadOnly,установленное в true,запрещает пользователю вводить в данное поле значения. Свойство Visibleопределяет, будет ли видно пользователю соответствующее поле.

После установки всех необходимых свойств приложение уже приобретает при­емлемый вид (рис. 1.7).

 

Работа с данными

 

Следующие методы позволяют Вам изменить данные, связанные с TTable:

 

 

procedure Append;

procedure Insert;

procedure Cancel;

procedure Delete;

procedure Edit;

procedure Post;

 

Все эти методы - часть TDataSet, они унаследованы и используются TTable и TQuery.

Всякий раз, когда Вы хотите изменить данные, Вы должны сначала перевести DataSet в режим редактирования. Как Вы увидите, большинство визуальных компонент делают это автоматически, и когда Вы используете их, то совершенно не будете об этом заботиться. Однако если Вы хотите изменить TTable программно, Вам придется использовать вышеупомянутые функции.

Имеется типичная последовательность, которую Вы могли бы использовать при изменении поля текущей записи:

 

Table1.Edit;

Table1.FieldByName(‘Pokupatel’).AsString := ‘ООО «Свет»’;

Table1.Post;

 

Первая строка переводит БД в режим редактирования. Далее строка присваивает значение ‘ООО «Свет»’ полю ‘Pokupatel’. Наконец, данные записываются на диск, когда Вы вызываете Post.

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

 

Table1.Edit;

Table1.FieldByName(‘Pokupatel’).AsString := ‘ООО «Свет»’;

Table1.Next;

 

Общее правило, которому нужно следовать, - всякий раз, когда Вы сдвигаетесь с текущей записи, введенные Вами данные будут записаны автоматически. Это означает, что вызовы First, Next, Prior и Last всегда выполняют Post, если Вы находились в режиме редактирования.

Вы можете все же отменить результаты вашего редактирования в любое время до тех пор, пока не вызвали напрямую или косвенно метод Post. Например, если Вы перевели таблицу в режим редактирования и изменили данные в одном или более полей, Вы можете всегда вернуть запись в исходное состояние вызовом метода Cancel.

Существуют два метода, названные Append и Insert, которые Вы можете использовать всякий раз, когда Вы хотите добавить новую запись в DataSet. Очевидно имеет больше смысла использовать Append для DataSets, которые не индексированы, но Delphi не будет генерировать exception, если Вы используете Append на индексированной таблице. Фактически, всегда можно использовать и Append, и Insert.

Продемонстрируем работу методов на простом примере. Чтобы создать программу, используйте TTable, TDataSource и TdbGrid. Открыть таблицу. Затем разместите две кнопки на форме и назовите их ‘Вставить’ и ‘Удалить’. Когда Вы все сделаете, то должна получиться форма, показанная на рис.1.8.

Следующим шагом Вы должен связать код с кнопками “Вставить” и “Удалить”.

 

Для кнопки «Вставить»:

 

procedure TForm1.InsertClick(Sender: TObject);

begin

Table1.Insert;

Table1.FieldByName(‘Sht_faktura’).AsString := '6';

Table1.FieldByName(‘Prodavec’).AsString := 'ООО «Альфа»';

Table1.FieldByName(‘Pokupatel’).AsString := 'ООО «Свет»';

Table1.FieldByName(‘edinica’).AsString := ‘шт.';

Table1.FieldByName(‘kolvo’).AsString := ‘2';

Table1.FieldByName(‘Price’).AsString := ‘265000';

Table1.Post;

end;

 

Рис.1.8. Программа может вставлять и удалять запись из таблицы

 

и соответственно для кнопки «Удалить»:

procedure TForm1.DeleteClick(Sender: TObject);

begin

Table1.Delete;

end;

Процедура, показанная здесь, сначала переводит таблицу в режим вставки (новая запись с незаполненными полями вставляется в текущую позицию dataset). После вставки пустой записи следующим этапом нужно назначить значения одному или большему количеству полей. Существует, конечно, несколько различных путей присвоить эти значения. В нашей программе Вы могли бы просто ввести информацию в новую запись через DBGrid. Или Вы могли бы разместить на форме стандартную строку ввода (TEdit) и затем установить каждое поле равным значению, которое пользователь напечатал в этой строке:

 

Table1.FieldByName(‘Name’).AsString := Edit1.Text;

 

Можно было бы использовать компоненты, специально предназначенные для работы с данными в DataSet.

Просматривая код, показанный выше, Вы увидите, что просто вставка записи и заполнение ее полей не достаточны для того, чтобы изменить физические данные на диске. Если Вы хотите, чтобы информация записалась на диск, Вы должны вызывать Post.

Если после вызова Insert Вы решаете отказаться от вставки новой записи, то Вы можете вызвать Cancel. Если Вы сделаете это прежде, чем Вы вызовете Post, то все что Вы ввели после вызова Insert будет отменено, и dataset будет находиться в состоянии, которое было до вызова Insert.

Одно дополнительное свойство, которое Вы должны иметь в виду, называется CanModify. Если CanModify возвращает False, то TTable находиться в состоянии ReadOnly. В противном случае CanModify возвращает True, и Вы можете редактировать или добавлять записи в нее по желанию. CanModify - само по себе ‘read only’ свойство. Если Вы хотите установить DataSet в состояние только на чтение (Read Only), то Вы должны использовать свойство ReadOnly, не CanModify.

 

Лабораторное задание и методические указания по его выполнению

Лабораторное задание

 

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

 

Вариант База данных Поля
БД сотрудников Отдел, фамилия, имя, отчество, год рождения, пол
БД деталей Название, цвет, длина, ширина, высота, форма сечения
БД анкетных данных Фамилия, имя, отчество, год рождения, место жительства, номер телефона
БД компьютерной техники Процессор, монитор, видеокарта, память, материнская плата, цена