Append — Процедура Delphi


Содержание

Функции, процедуры файлового ввода и вывода в Дельфи

Append (Var F:Text) – открывает текстовый файл, связанный с переменной F, для добавления его в конец.

CloseFile (var F) – закрывает открытый файл, связанный с файловой переменной F.

AssignFile (var F; FileName: String) – связывает имя внешнего файла FileName с файловой переменной F.

BlockRead / BlockWrite (var F:file; var Buf; Count: Integer[; var Result: Integer]) – читает Count записей из нетипизированного файла, связанного с файловой переменной F, в буфер Buf. Если задан параметр Result, то в него возврашается число действительно прочитанных записей. BlockWrite соответственно записывает Count записей из буфера Buf в файл.

Eof (var F):Boolean – возвращает true при достижении конца файла.

Eoln (var F:Text):Boolean – возвращает признак конца строки в текстовом файле.

Eraise (var F) – удаление внешнего файла связанного с переменной F.

FileSize (var F):Integer – текущий размер файла, связанного с переменной F. Не используется для текстовых файлов.

Flush (var F:Text) – очистка буфера выходного текстового файла, связанного с переменной F.

FilePos (var F): LongInt – возвращает текущую позицию типизированного или нетипизированного файла, связанного с переменной F. Позиция, соответствующая началу файла – 0.

IOResult: Integer – возвращает целое значение, характеризующее выполнение последней операции ввода или вывода.

Read (F, ) – читает из файла, связанного с переменной F, одно или более значений в одну или более переменную.

ReadLn (F, ) – читает одно или более значений в одну или более переменную и переходит на начало новой строки текстового файла.

Rename (var F: File; NewName:String) – переименование файла. В NewName указан полный путь к переименованному файлу.

Reset (var F[: File; RecSize: Word]) – открывает существующий файл связанный с файловой переменной F. RecSize задается для нетипизированных файлов и устанавливает длину записи в байтах (по умолчанию 128).

ReWrite (var F[: File; RecSize: Word]) – создает и открывает новый файл, связанный с файловой переменной F. RecSize задается для нетипизированных файлов и устанавливает длину записи в байтах (по умолчанию 128).

Seek (var F; N:LongInt) – перемещает текущую позицию в позицию N. Для текстовых файлов не используется.

SeekEof (var F:Text): Boolean – возвращает true при достижении конца текстового файла, связанного с файловой переменной F.

SeekEoln (var F:Text): Boolean – возвращает true при достижении конца строки текстового файла, связанного с файловой переменной F.

Truncate (var F) – усекает типизированный или нетипизированный файл, связанный файловой переменной F, на текущей позиции.

Write (var F; ) – записывает значение(я) в файл, связанный файловой переменной F.

WriteLn (var F; ) – записывает значение(я) в файл, затем заносит маркер конца строки в текстовый файл, связанный файловой переменной F.

. Программирование файлового ввода Delphi функции файлового ввод вывод в с++

Append — Процедура Delphi

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

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

1. Объявить файловую переменную необходимого типа.

2. При помощи функции AssignFile связать эту переменную с требуемым файлом.

3. Открыть файл при помощи функций Append, Reset, Rewrite.

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

5. Закрыть файл при помощи функции CloseFile .

По сравнению с Turbo Pascal изменились названия только двух функций: Assign стала AssignFile , a Close превратилась в CloseFile .

В качестве примера рассмотрим небольшой фрагмент исходного кода.

then AssignFiie(F, OpenDlg.FileName)

else Exit; Reset(F);

while Not EOF(F) do

Если в диалоге открытия файла OpenDlg был выбран файл, то его имя связывается с файловой переменной F при помощи процедуры AssignFiie . В качестве имени файла рекомендуется всегда передавать полное имя файла (включая его маршрут). Как раз в таком виде возвращают результат выбора файла диалоги работы с файлами TOpenDialog, TOpenPictureDiaiog . Затем при помощи процедуры Reset этот файл открывается для чтения и записи.

В цикле выполняется чтение из файла текстовых строк и запись их в компонент TMemo . Процедура Readin осуществляет чтение текущей строки файла и переходит на следующую строку. Цикл выполняется, пока функция EOF не сообщит о достижении конца файла.

После завершения чтения файл закрывается.

Такой же исходный код можно использовать и для записи данных в файл. Необходимо только заменить процедуру чтения на процедуру записи.

Теперь остановимся подробнее на назначении используемых для файлового ввода/вывода функций.

Открытие файла может осуществляться тремя процедурами — в зависимости от типа его дальнейшего использования.

procedure Reset(var F: File [; RecSize: Word ]);

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

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

procedure Rewrite(var F: File [; RecSize: Word ]);

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

Переменная RecSize используется только при работе с нетипизированными файлами и определяет размер одной записи для операции передачи данных. Если этот параметр опущен, то по умолчанию RecSize равно 128 байт.

Чтение данных из типизированных и текстовых файлов выполняют процедуры Read И Readin.

Процедура Read имеет различное объявление для текстовых и других типизированных файлов:

  • procedure Read([var F: Text;] VI [, V2. Vn]);
  • procedure Read(F, VI [, V2. Vn]);

для других типизированных файлов.

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

procedure Readln([ var F: Text; ] VI [, V2. Vn ]);

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

Процедуры для записи в файл write и writein описаны аналогично:

procedure Write([var F: Text; ] PI [, P2. Pn]) ; procedure Writein([ var F: Text; ] PI [, P2. Pn ]);

Параметры P1, P2, . Pn могут быть одним из целых или вещественных типов, одним из строковых типов или логическим типом. Но у них есть возможность дополнительного форматирования при выводе. Каждый параметр записи может иметь форму:

Р n — выводимая переменная или выражение;

MinWidth — минимальная ширина поля в символах, которая должна быть больше 0;

DecPlaces — содержит количество десятичных символов после запятой при отображении вещественных чисел с фиксированной точкой.

Обратите внимание, что для текстовых файлов в функциях Read и write файловая переменная F может быть опущена. В этом случае чтение и запись осуществляются в стандартные файлы ввода/вывода. Когда программа компилируется как консольное приложение (флаг <$APPTYPE CONSOLE>), Delphi автоматически связывает входной и выходной файлы с окном консоли.

Для контроля за текущей позицией в файле применяются две основные функции. Функция EOF (F) возвращает значение True , если достигнут конец файла. Функция EOLN(F) аналогично сигнализирует о достижении конца строки. Естественно, в качестве параметра в функции необходимо передавать файловую переменную.

обеспечивает смещение текущей позиции на N элементов. Размер одного элемента в байтах зависит от типа данных файла (от типизированной переменной).

Рассмотрим теперь режим блочного ввода/вывода данных между файлом и областью адресного пространства (буфером). Этот режим отличается значительной скоростью передачи данных, причем скорость пропорциональна размеру одного передаваемого блока — чем больше блок, тем больше скорость.

Для реализации этого режима необходимо использовать только нетипизированные файловые переменные. Размер блока определяется в процедуре открытия файла ( Reset, Rewrite ). Непосредственно для выполнения операций используются процедуры BlockRead и BlockWrite .

procedure BlockRead(var F: File; var Buf; Count: Integer

[; var AmtTransferred: Integer]);

выполняет запись блока из файла в буфер. Параметр F ссылается на нетипизированную файловую переменную, связанную с нужным файлом.

Параметр Buf определяет любую переменную (число, строку, массив, структуру), в которую читаются байты из файла. Параметр Count содержит число считываемых блоков. Наконец, необязательный параметр AmtTransferred возвращает число реально считанных блоков.

При использовании блочного чтения или записи размер блока необходимо выбирать таким образом, чтобы он был кратен размеру одного значения того типа, который хранится в файле. Например, если в файле хранятся значения типа Double (8 байт), то размер блока может быть равен 8, 16, 24, 32 и т. д. Фрагмент исходного кода блочного чтения при этом выглядит следующим образом:

DoubleArray: array [0..255] of Double;

if OpenDlg.Execute then AssignFile(F, OpenDlg.FileName) else Exit;

BlockRead(F, DoubleArray, 32, Transferee!);

ShowMessage(‘Считано ‘+IntToStr(Transfered)+’ блоков’);

Как видно из примера, размер блока установлен в процедуре Reset и кратен размеру элемента массива DoubleArray , в который считываются данные. В переменной Transfered возвращается число считанных блоков. Если размер файла меньше заданного в процедуре BlockRead числа блоков, ошибка не возникает, а в переменной Transfered передается число реально считанных блоков.

procedure BlockWrite(var f: File; var Buf; Count: Integer

[; var AmtTransferred: Integer]);

Оставшиеся функции ввода/вывода, работающие с файловыми переменными, подробно описаны в табл. 9.1.

Таблица 9.1. Процедуры и функции для работы с файлом

function ChangeFileExt (const FileName, Extension: string): string;

Функция позволяет изменить расширение файла. При этом сам файл не переименовывается

procedure ChDir(S: string);

Процедура изменяет текущий каталог на другой, путь к которому описан в строке s

procedure CloseFile (var F) ;

Вызов процедуры разрывает связь между файловой переменной и файлом на диске.

Имя этой процедуры изменено из-за конфликта имен в Delphi (в Borland Pascal используется процедура Close )

function DeleteFile (const FileName: string): Boolean;

Функция производит удаление файла FileName с диска и возвращает значение False , если файл удалить не удалось или файл не существует

function ExtractFileExt (const FileName: string): string;

Функция возвращает расширение файла

function ExtractFileName (const FileName: string) : string;

Извлекает имя и расширение файла, содержащегося в параметре FileName

function ExtractFilePath( const FileName: string): string;

Функция возвращает полный путь к файлу

procedure Erase (var F);

Удаляет файл, связанный с файловой переменной F

function FileSearch (const Name, DirList: string) : string;

Данная процедура производит поиск в каталогах DirList файла Name . Если в процессе выполнения FileSearch обнаруживается искомое имя файла, то функция возвращает в строке типа string полный путь к найденному файлу. Если файл не найден, то возвращается пустая строка

function FileSetAttr (const FileName: string; Attr: Integer): Integer;

Присваивает файлу с именем FileName атрибуты Attr. Функция возвращает 0, если присвоение атрибутов прошло успешно. В противном случае возвращается код ошибки

function FilePos (var F): Longint;

Возвращает текущую позицию файла. Функция используется для нетекстовых файлов. Перед вызовом FilePos файл должен быть открыт

function FileSize (var F): Integer;

FileSize возвращает размер файла в байтах или количество записей в файле, содержащем записи. Перед вызовом данной функции файл должен быть открыт.

Для текстовых файлов функция FileSize не используется

procedure Flush (var F: Text);

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

Когда текстовый файл открыт для записи с использованием функции Rewrite или Append, Flush очищает выходной буфер, связанный с файлом. После выполнения данной процедуры все символы, которые направлены для записи в файл, будут гарантированно записаны в нем

procedure GetDir(D: Byte; var S: string);

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

0 — по умолчанию (текущий);

Процедура не генерирует код ошибки. Если имя диска в D оказывается ошибочным, то в строке S возвращается значение Х:\, как если бы текущая папка была на этом ошибочно указанном диске

function lOResult: Integer;

Функция возвращает статус последней произведенной операции ввода/вывода, если контроль ошибок выключен

procedure MkDir(S: string);

Процедура создает новый каталог, который описывается в строке S

procedure Rename (var F; NewName: string) ;

Процедура изменяет имя файла, связанного с файловой переменной F. Переменная NewName является строкой типа string или PChar (если включена поддержка расширенного синтаксиса)

procedure RmDir(S: string);

Процедура удаляет пустой каталог, путь к которому задается в строке S. Если указанный каталог не существует или он не пустой, то возникает сообщение об ошибке ввода/вывода

procedure Seek (var F; N: Longint) ;

Перемещает текущую позицию курсора на N позиций. Данная процедура используется только для открытых типизированных или нетипизированных файлов для проведения чтения/записи с нужной позиции файла. Началу файла соответствует нулевой номер позиции. Для добавления новой информации в конец существующего файла необходимо установить указатель на символ, следующий за последним. Для этого можно использовать выражение Seek (F, FileSize(F))

function SeekEof[(var F: Text) ] : Boolean;

Возвращает значение True , если указатель текущей позиции находится на символе конца файла. SeekEof может быть использован только с открытым текстовым файлом

function SeekEoln[ (var F: Text) ] : Boolean;

Возвращает значение True , если указатель текущей позиции находится на символе конца строки.

SeekEoln может быть использован только с открытым текстовым файлом

procedure SetTextBuf (var F: Text; var Buf [; Size: Integer] );

Связывает с текстовым файлом буфер ввода/вывода. F — файловая переменная текстового типа. Каждая файловая переменная текстового типа имеет внутренний буфер емкостью 128 байт, в котором накапливаются данные при чтении и записи. Такой буфер пригоден для большинства операций. Однако при выполнении программ с интенсивным вводом/выводом буфер может переполниться, что приведет к записи операций ввода/вывода на диск и, как следствие, к существенному замедлению работы приложения. SetTextBuf позволяет помещать в текстовый файл F информацию об операциях ввода/вывода вместо ее размещения в буфере. Size указывает размер буфера в байтах. Если этот параметр опускается, то полагается размер, равный SizeOf (Buf). Новый буфер действует до тех пор, пока F не будет связана с новым файлом процедурой AssignFile

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

Вещество и поле не есть что-то отдельное от эфира, также как и человеческое тело не есть что-то отдельное от атомов и молекул его составляющих. Оно и есть эти атомы и молекулы, собранные в определенном порядке. Также и вещество не есть что-то отдельное от элементарных частиц, а оно состоит из них как базовой материи. Также и элементарные частицы состоят из частиц эфира как базовой материи нижнего уровня. Таким образом, всё, что есть во вселенной — это есть эфир. Эфира 100%. Из него состоят элементарные частицы, а из них всё остальное. Подробнее читайте в FAQ по эфирной физике.

НОВОСТИ ФОРУМА
Рыцари теории эфира
01.10.2020 — 05:20: ВОСПИТАНИЕ, ПРОСВЕЩЕНИЕ, ОБРАЗОВАНИЕ — Upbringing, Inlightening, Education ->
[center][Youtube]69vJGqDENq4[/Youtube][/center]
[center]14:36[/center]
Osievskii Global News
29 сент. Отправлено 05:20, 01.10.2020 г.’ target=_top>Просвещение от Вячеслава Осиевского — Карим_Хайдаров.
30.09.2020 — 12:51: ВОСПИТАНИЕ, ПРОСВЕЩЕНИЕ, ОБРАЗОВАНИЕ — Upbringing, Inlightening, Education ->
[center][Ok]376309070[/Ok][/center]
[center]11:03[/center] Отправлено 12:51, 30.09.2020 г.’ target=_top>Просвещение от Дэйвида Дюка — Карим_Хайдаров.
30.09.2020 — 11:53: ВОСПИТАНИЕ, ПРОСВЕЩЕНИЕ, ОБРАЗОВАНИЕ — Upbringing, Inlightening, Education ->
[center][Youtube]VVQv1EzDTtY[/Youtube][/center]
[center]10:43[/center]

интервью Раввина Борода https://cursorinfo.co.il/all-news/rav.
мой телеграмм https://t.me/peshekhonovandrei
мой твиттер https://twitter.com/Andrey54708595
мой инстаграм https://www.instagram.com/andreipeshekhonow/

[b]Мой комментарий:
Андрей спрашивает: Краснодарская синагога — это что, военный объект?
— Да, военный, потому что имеет разрешение от Росатома на манипуляции с радиоактивными веществами, а также иными веществами, опасными в отношении массового поражения. Именно это было выявлено группой краснодарцев во главе с Мариной Мелиховой.

[center][Youtube]CLegyQkMkyw[/Youtube][/center]
[center]10:22 [/center]

Доминико Риккарди: Россию ждёт страшное будущее (хотелки ЦРУ):
https://tainy.net/22686-predskazaniya-dominika-rikardi-o-budushhem-rossii-sdelannye-v-2000-godu.html

Завещание Алена Даллеса / Разработка ЦРУ (запрещено к ознакомлению Роскомнадзором = Жид-над-рус-надзором)
http://av-inf.blogspot.com/2013/12/dalles.html

[center][b]Сон разума народа России [/center]

[center][Youtube]CLegyQkMkyw[/Youtube][/center]
[center]10:22 [/center]

Доминико Риккарди: Россию ждёт страшное будущее (хотелки ЦРУ):
https://tainy.net/22686-predskazaniya-dominika-rikardi-o-budushhem-rossii-sdelannye-v-2000-godu.html


Завещание Алена Даллеса / Разработка ЦРУ (запрещено к ознакомлению Роскомнадзором = Жид-над-рус-надзором)
http://av-inf.blogspot.com/2013/12/dalles.html

[center][b]Сон разума народа России [/center]

Открытие файла для вывода Delphi

Продолжаем тему «Файлы в языке Delphi». В предыдущей статье мы рассмотрели назначение файла и вывод данных в файл. Однако прежде чем вывести в файл необходимую информацию, файл для начала нужно открыть. В чем заключается открытие файла для вывода Delphi?

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

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

  • перезапись, что означает проведение записи нового файла поверх существующего файла, либо формирование нового файла;
  • добавление в уже существующий файл.

Для открытия файла, используя режим создания нового файла, либо замены уже имеющегося файла, нужно воспользоваться процедурой Rewrite(fail), откуда fail — является файловой переменной, имеющей тип данныхTextFile. Для открытия файла, используя режим добавления данных к уже существующему файлу, используйте процедуру Append(fail), где fail — есть файловая переменная, имеющая тип данных TextFile.

Примеры программ на открытие файла для вывода Delphi

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

А частичный исходный код (расположен ниже) программы немного прокомментируем.

Append — Процедура Delphi

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

Более подробно здесь рассказывается о TTable и TDataSource.

Имеются несколько основных компонент(объектов), которые Вы будете использовать постоянно для доступа к БД. Эти объекты могут быть разделены на три группы:

  • невизуальные: TTable, TQuery, TDataSet, TField
  • визуальные: TDBGrid, TDBEdit
  • связующие: TDataSource

Первая группа включает невизуальные классы, которые используются для управления таблицами и запросами. Эта группа сосредотачивается вокруг компонент типа TTable, TQuery, TDataSet и TField. В Палитре Компонент эти объекты расположены на странице Data Acc ess.

Вторая важная группа классов — визуальные, которые показывают данные пользователю, и позволяют ему просматривать и модифицировать их. Эта группа классов включает компоненты типа TDBGrid, TDBEdit, TDBImage и TDBComboBox. В Палитре Компонент эти объекты расположены на странице Data Controls.

Имеется и третий тип, который используется для того, чтобы связать предыдущие два типа объектов. К третьему типу относится только невизуальный компонент TDataSource.

      1. Класс TDataSet
      2. TDataSet класс — один из наиболее важных объектов БД. Чтобы начать работать с ним, Вы должны взглянуть на следующую иерархию:

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

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

      Или, если Вы предпочитаете, то можете установить свойство Active равное True:

      Нет никакого различия между результатом производимым этими двумя операциями. Метод Open, однако, сам заканчивается установкой свойства Active в True, так что может быть даже чуть более эффективно использовать свойство Active напрямую.

      Также, как имеются два способа открыть a таблицу, так и есть два способа закрыть ее. Самый простой способ просто вызывать Close:

      Или, если Вы желаете, Вы можете написать:

      Еще раз повторим, что нет никакой существенной разницы между двумя этими способами. Вы должны только помнить, что Open и Close это методы (процедуры), а Active — свойство.

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

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

      Следующий обширный набор методов и свойства TDataSet обеспечивает все , что Вам нужно для доступа к любой конкретной записи внутри таблицы:

      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.

      Дважды щелкните на кнопке Next — появится заготовка обработчика события:

      procedure TForm1.NextClick(Sender: TObject);

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

      procedure TForm1.NextClick(Sender: TObject);

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

      procedure TForm1.PriorClick(Sender: TObject);

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

      Теперь добавьте еще две кнопки и назовите их First и Last, как показано на рис.3

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

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

      procedure TForm1.FirstClick(Sender: TObject);

      procedure TForm1.LastClick(Sender: TObject);

      Нет ничего более простого чем эти навигационные функции. 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 не сможет больше переместить Вас на предыдущую запись в таблице. В этот момент BOF вернет True и программа выйдет из цикла. (Чтобы оптимизировать вышеприведенный код, установите DataSource1.Enabled в False перед началом цикла, и верните его в True после окончания цикла.)

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

      while not Table1.EOF do begin

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

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

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

      • После того, как Вы открыли пустой файл;
      • После того, как Вы вызывали TDataSet.Last;
      • После того, как вызов TDataSet.Next не выполняется.

      Единственная навигационная процедура, которая еще не упоминалась — MoveBy, которая позволяет Вам переместиться на N записей вперед или назад в таблице. Если Вы хотите переместиться на две записи вперед, то напишите:

      И если Вы хотите переместиться на две записи назад, то:

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

      Prior и Next — это простые функции, которые вызывают MoveBy.

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

          property Fields[Index: Integer];

          function FieldByName(const FieldName: string): TField;

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

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

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

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

          Предположим, что первое поле в записи содержит номер заказчика, тогда код, показанный выше, возвратил бы строку типа “1021”, “1031” или “2058”. Если Вы хотели получить доступ к этот переменный, как к числовой величине, тогда Вы могли бы использовать AsInteger вместо AsString. Аналогично, свойство Fields включают AsBoolean, AsFloat и AsDate.

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

          Как показано в примерах выше, и 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);

          for i := 0 to Table1.FieldCount — 1 do

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

          Предположим, что Вы ввели код правильно, и заполнили ListBox1 именами всех полей в текущей структуре записи.

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

          Свойство Fields позволяет Вам получить доступ не только именам полей записи, но также и к содержимому полей. В нашем примере, для второй кнопки напишем:

          procedure TForm1.ValuesClick(Sender: TObject);

          for i := 0 to Table1.FieldCount — 1 do

          Этот код добавляет содержимое каждого из полей во второй listbox. Обратите внимание, что вновь счетчик изменяется от нуля до FieldCount — 1.

          Свойство Fields позволяет Вам выбрать тип результата написав Fields[N].AsString. Этот и несколько связанных методов обеспечивают a простой и гибкий способ доступа к данным, связанными с конкретным полем. Вот список доступных методов который Вы можете найти в описании класса TField:

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

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

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

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

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

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

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

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

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

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

          procedure TForm1.DeleteClick(Sender: TObject);

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

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

          Назначение этой главы, однако, состоит в том, чтобы показать, как вводить данные из программы. Поэтому, в примере вводимая информация скомпилирована прямо в код программы:

          Один из интересных моментов в этом примере это то, что нажатие кнопки 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.

        3. Использование SetKey для поиска в таблице
        4. Для того, чтобы найти некоторую величину в таблице, программист на 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);

          Первый вызов в этой процедуре установит Table1 в режим поиска. Delphi должен знать, что Вы переключились в режим поиска просто потому, что свойство Fields используется по другому в этом режиме. Далее, нужно присвоить свойству Fields значение, которое Вы хотите найти. Для фактического выполнения поиска нужно просто вызывать Table1.GotoKey.

          Если Вы ищете не по первичному индексу файла, тогда Вы должны определить имя индекса, который Вы используете в свойстве IndexName. Например, если таблица Customer имеет вторичный индекс по полю City, тогда Вы должны установить свойство IndexName равным имени индекса. Когда Вы будете искать по этому полю, Вы должны написать:

          Запомните: поиск не будет выполняться, если Вы не назначите правильно индекс (св-во IndexName). Кроме того, Вы должны обратить внимание, что IndexName — это свойство TTable, и не присутствует в других прямых потомках TDataSet или TDBDataSet.

          Когда Вы ищете некоторое значение в БД, всегда существует вероятность того, что поиск окажется неудачным. В таком случае Delphi будет автоматически вызывать exception, но если Вы хотите обработать ошибку сами, то могли бы написать примерно такой код:


          procedure TSearchDemo.SearchClick(Sender: TObject);

          if not Cust.GotoKey then

          raise Exception.CreateFmt(‘Cannot find CustNo %g’,

          В коде, показанном выше, либо неверное присвоение номера, либо неудача поиска автоматически приведут к сообщению об ошибке ‘ Cannot find CustNo %g ’.

          Иногда требуется найти не точно совпадающее значение, а близкое к нему, для этого следует вместо GotoKey пользоваться методом GotoNearest.

        5. Использование фильтров для ограничения числа записей в DataSet

    Процедура ApplyRange позволяет Вам установить фильтр, который ограничивает диапазон просматриваемых записей. Например, в БД Customers, поле CustNo имеет диапазон от 1,000 до 10,000. Если Вы хотите видеть только те записи, которые имеют номер заказчика между 2000 и 3000, то Вы должны использовать метод ApplyRange , и еще два связанных с ним метода. Данные методы работают только с индексированным полем.

    Вот процедуры, которые Вы будете чаще всего использовать при установке фильтров:

    Кроме того, у TTable есть дополнительные методы для управления фильтрами:

    Для использования этих процедур необходимо:

    1. Сначала вызвать SetRangeStart и использовать свойство Fields для определения начала диапазона.
    2. Затем вызвать SetRangeEnd и вновь использовать свойство Fields для определения конца диапазона.
    3. Первые два шага подготавливают фильтр, и теперь все что Вам необходимо, это вызвать ApplyRange , и новый фильтр вступит в силу.
    4. Когда нужно прекратить действие фильтра — вызовите 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);

    if RangeStart.Text <> » then

    Table1. Fields[0].AsString := RangeStart.Text;

    if RangeEnd.Text <> » then

    Сначала вызывается процедура SetRangeStart , которая переводит таблицу в режим диапазона (range mode). Затем Вы должны определить начало и конец диапазона. Обратите внимание, что Вы используете свойство Fields для определения диапазона:

    Такое использование свойства Fields — это специальный случай, так как синтаксис, показанный здесь, обычно используется для установки значения поля. Этот специальный случай имеет место только после того, как Вы перевели таблицу в режим диапазона, вызвав SetRangeStart.

    Заключительный шаг в процедуре показанной выше — вызов ApplyRange . Этот вызов фактически приводит ваш запрос в действие. После вызова ApplyRange, TTable больше не в находится в режиме диапазона, и свойства Fields функционирует как обычно.

    Обработчик события нажатия кнопки ‘CancelRange’:

    procedure TForm1.CancelRangeBtnClick(Sender: TObject);

        1. Обновление (Refresh)
        2. Как Вы уже знаете, любая таблица, которую Вы открываете всегда “подвержена изменению”. Короче говоря, Вы должны расценить таблицу скорее как меняющуюся, чем как статическую сущность. Даже если Вы — единственное лицо, использующее данную TTable, и даже если Вы не работаете в сети, всегда существует возможность того, что программа с которой Вы работаете, может иметь два различных пути изменения данных в таблице. В результате, Вы должны всегда знать, необходимо ли Вам обновить вид таблицы на экране.

        Функция Refresh связана с функцией Open , в том смысле что она считывает данные, или некоторую часть данных, связанных с данной таблицей. Например, когда Вы открываете таблицу, Delphi считывает данные непосредственно из файла БД. Аналогично, когда Вы Регенерируете таблицу, Delphi считывает данные напрямую из таблицы. Поэтому Вы можете использовать эту функцию, чтобы перепрочитать таблицу, если Вы думаете что она могла измениться. Быстрее и эффективнее, вызывать Refresh , чем вызывать Close и затем Open.

        Имейте ввиду, однако, что обновление TTable может иногда привести к неожиданным результатам. Например, если a пользователь рассматривает запись, которая уже была удалена, то она исчезнет с экрана в тот момент, когда будет вызван Refresh. Аналогично, если некий другой пользователь редактировал данные, то вызов Refresh приведет к динамическому изменению данных. Конечно маловероятно, что один пользователь будет изменять или удалять запись в то время, как другой просматривает ее, но это возможно.

      1. Закладки (Bookmarks)
      2. Часто бывает полезно отметить текущее местоположение в таблице так, чтобы можно было быстро возвратиться к этому месту в дальнейшем. 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

        Следующий шаг должен связать таблицу ORDERS с таблицей CUSTOMER так, чтобы Вы видели только те заказы, которые связанные с текущей записью в таблице заказчиков. В первой таблице заказчик однозначно идентифицируется своим номером — поле CustNo. Во второй таблице принадлежность заказа определяется также номером заказчика в поле CustNo. Следовательно, таблицы нужно связывать по полю CustNo в обоих таблицах (поля могут иметь различное название, но должны быть совместимы по типу). Для этого, Вы должны сделать три шага, каждый из которых требует некоторого пояснения:

        1. Установить свойство Table2.MasterSource = DataSource1
        2. Установить свойство Table2.MasterField = CustNo
        3. Установить свойство Table2.IndexName = CustNo

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

        Свойство MasterSource в Table2 определяет DataSource от которого Table2 может получить информацию. То есть, оно позволяет таблице ORDERS знать, какая запись в настоящее время является текущей в таблице CUSTOMERS.

        Но тогда возникает вопрос: Какая еще информация нужна Table2 для того, чтобы должным образом отфильтровать содержимое таблицы ORDERS? Ответ состоит из двух частей:

        1. Требуется имя поля по которому связанны две таблицы.
        2. Требуется индекс по этому полю в таблице 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.

            1. Основные понятия о TDataSource
            2. Класс 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 объекте.

          1. Использование TDataSource для проверки состояния БД:
          2. TDataSource имеет три ключевых события, связанных с состоянием БД

            OnDataChange происходит всякий раз, когда Вы переходите на новую запись, или состояние DataSet сменилось с dsInactive на другое, или начато редактирование. Другими словами, если Вы вызываете Next, Previous, Insert, или любой другой запрос, который должен привести к изменению данных, связанных с текущей записью, то произойдет событие OnDataChange. Если в программе нужно определить момент, когда происходит переход на другую запись, то это можно сделать в обработчике события OnDataChange:

            procedure TForm1.DataSource1DataChange(Sender: TObject; Field: TField);

            if DataSource1.DataSet.State = dsBrowse then begin

            Событие OnStateChange событие происходит всякий раз, когда изменяется текущее состояние DataSet. DataSet всегда знает, в каком состоянии он находится. Если Вы вызываете Edit, Append или Insert, то TTable знает, что он теперь находится в режиме редактирования (dsEdit или dsInsert). Аналогично, после того, как Вы делаете Post, то TTable знает что данные больше не редактируется, и переключается обратно в режим просмотра (dsBrowse).

            Dataset имеет шесть различных возможных состояний, каждое из которых включено в следующем перечисляемом типе:

            TDataSetState = (dsInactive, dsBrowse, dsEdit, dsInsert,

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

            procedure TForm1.DataSource1StateChange(Sender: TObject);

            case Table1.State of

            dsInactive: S := ‘Inactive’;

            dsBrowse: S := ‘Browse’;

            dsInsert: S := ‘Insert’;

            dsSetKey: S := ‘SetKey’;

            dsCalcFields: S := ‘CalcFields’;

            OnUpdateData событие происходит перед тем, как данные в текущей записи будут обновлены. Например, OnUpdateEvent будет происходить между вызовом Post и фактическим обновлением информации на диске.

            События, генерируемые TDataSource могут быть очень полезны. Иллюстрацией этого служит следующий пример. Эта программа работает с таблицей COUNTRY, и включает TTable, TDataSource, пять TEdit, шесть TLlabel, восемь кнопок и панель. Действительное расположение элементов показано на рис.11. Обратите внимание, что шестой TLabel расположен на панели внизу главной формы.

            Рис .11: Программа STATE показывает, как отслеживать текущее состояние таблицы.

            Для всех кнопок напишите обработчики, вроде:

            procedure TForm1.FirstClick(Sender: TObject);

            В данной программе есть одна маленькая хитрость, которую Вы должны понять, если хотите узнать, как работает программа. Так как есть пять отдельных редакторов TEdit на главной форме, то хотелось бы иметь некоторый способ обращаться к ним быстро и легко. Один простой способ состоит в том, чтобы объявить массив редакторов:

            Edits: array[1..5] of TEdit;

            Чтобы заполнить массив, Вы можете в событии OnCreate главной формы написать:

            procedure TForm1.FormCreate(Sender: TObject);

            for i := 1 to 5 do

            Edits[i] := TEdit(FindComponent(‘Edit’ + IntToStr(i)));

            Код показанный здесь предполагает, что первый редактор, который Вы будете использовать назовем Edit1, второй Edit2, и т.д. Существование этого массива позволяет очень просто использовать событие OnDataChange, чтобы синхронизировать содержание объектов TEdit с содержимом текущей записи в DataSet:

            procedure TForm1.DataSource1DataChange(Sender: TObject;

            for i := 1 to 5 do

            Edits[i].Text := Table1.Fields[i — 1].AsString;

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

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

            procedure TForm1.DataSource1UpdateData(Sender: TObject);

            for i := 1 to 5 do

            Table1.Fields[i — 1].AsString := Edits[i].Text;

            Программа будет автоматически переключатся в режим редактирования каждый раз, когда Вы вводите что-либо в одном из редакторов. Это делается в обработчике события OnKeyDown (укажите этот обработчик ко всем редакторам):

            procedure TForm1.Edit1KeyDown(Sender: TObject;

            var Key: Word; Shift: TShiftState);

            if DataSource1.State <> dsEdit then

            Этот код показывает, как Вы можете использовать св-во State DataSource, чтобы определить текущий режим DataSet.

            Обновление метки в статусной панели происходит при изменении состояния таблицы:

            procedure TForm1.DataSource1StateChange(Sender: TObject);

            case DataSource1.State of

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

            В предыдущей части Вы узнали, как использовать TDataSource, чтобы узнать текущее состоянии TDataSet. Использование DataSource — это простой путь выполнения данной задачи. Однако, если Вы хотите отслеживать эти события без использования DataSource, то можете написать свои обработчики событий TTable и TQuery:

            Append — Процедура Delphi

            Теперь, когда нам известны все основные компоненты, необходимые для работы с СУБД — как обеспечивающие доступ к данным, так и их отображение, можно перейти к вопросам практического характера. Прежде всего, это вопросы, касающиеся полей данных, а так же состояния наборов данных, программная навигация, сортировка, поиск и фильтрация. Так же, разумеется, будут рассмотрены правка, добавление и удаление записей.

            Состояния и режимы набора данных

            Как мы уже знаем, одним из основных компонентов Delphi для доступа к данным является Table. Этот компонент происходит от общего для всех наборов данных класса — TDataSet — набор данных. Именно на уровне набора данных информация из БД представляются как совокупность строк и столбцов. В этом базовом классе так же сосредоточены все основные свойства и методы для работы с наборами данных, включая управление состоянием набора, поиск, фильтрацию, сортировку и изменение данных.

            ВНИМАНИЕ
            Набор данных в Delphi — это некая логическая таблица, с которой может работать приложение. При этом порядок следования записей в наборе данных может отличаться от имеющегося в реальной таблице, поскольку зависит от того, используется ли сортировка. Равно как и количество записей в наборе зависит от наличия и параметров фильтрации.

            Основными свойствами набора данных как такового являются Active и State, а применительно к записям — RecordCount и RecNo. В частности, свойство RecordCount указывает на текущее количество записей в наборе данных (которое может не совпадать с количеством записей в таблице БД благодаря возможной фильтрации), а свойство RecNo указывает на индекс активной записи.

            Что касается свойства Active, то мы уже рассматривали его в контексте компонента Table и знаем, что оно отвечает за непосредственное подключение к БД. Значение этого свойства может быть установлено как на этапе разработки приложения, так и во время выполнения. При этом следует учитывать, что попытка установить свойство Active в истину может вызвать исключительную ситуацию, если не указана, как минимум, физическая таблица с данными (при помощи свойства TableName):

            Table1.TableName=’..\DB1\bill.db’; // указываем физическую таблицу БД Table1.Active:=true; // делаем набор активным

            Если же поддерживать активное соединение по ходу дальнейшего выполнения программы нет необходимости, то можно вернуть состояние в неактивное, присвоив свойству Active значение ложь. Эту операцию необходимо сделать и в том случае, если требуется изменить исходный источник с данными:

            Table1.Active:=false; // отключаемся Table1.TableName=’..\DB1\customer.db’; // меняем таблицу БД Table1.Active:=true; // включаем внось

            Альтернативным методом изменения состояния набора данных является использование методов Open и Close:

            Table1.Close; // отключаемся Table1.TableName=’..\DB1\customer.db’; // меняем таблицу БД Table1.Open; // включаем внось

            Фактически, эти методы выполняют ту же работу, что и изменение свойства Active, т.е. обращение к методу Open устанавливает свойство Active в истину, а Close — в ложь. При написании программного кода принято использовать именно эти методы, а не изменять значение свойства Active.

            Другое свойство набора данных — State — показывает текущее состояние набора данных, или режим его работы. Оно имеет тип TDataSetState и доступно для чтения во время выполнения программы. Основными значениями State являются следующие:

            • dsInactive — набор данных закрыт;
            • dsBrowse — набор данных доступен для промотра, но не находится в состоянии изменения;
            • dsEdit — текущая запись может быть изменена;
            • dsInsert — добавлена, но еще не отправлена в таблицу новая запись;
            • dsSetKey — осуществляется поиск записи (только для Table);
            • dsCalcFields — осуществляется расчет полей;
            • dsFilter — производится фильтрация записей;
            • dsOpening — начат но еще не завершен процесс открытия набора данных.

            При помощи свойства State во время выполнения программы можно получить текущее состояние набора данных, а само изменение состояние инициализирует событие OnStateChange, которое происходит для связанного с источником данных компонента DataSource.

            Рассмотрим работу с источником данных на примере, для которого нам потребуются компоненты Table, DataSource, DBGrid и OpenDialog. При этом компоненты для баз данных должны быть связаны между собой, т.е. у DataSource1 в свойстве DataSet должно быть указано Table1, а у DBGrid1 свойство DataSource должно иметь значение DataSource1. Теперь разместим на форме кнопку (Button), которая будет вызывать диалог выбора файла и производить работу по подключению данных, для чего нам понадобится обработчик события OnClick. А для компонента DataSource1 мы сделает обработчик события OnStatusChange, который будет выводить текущее состояние набора данных в заголовок окна. Вариант кода приведен в листинге 20.1.

            Листинг 20.1. Подключение источника данных и слежение за его состоянием

            procedure TForm1.Button1Click(Sender: TObject); begin if not OpenDialog1.Execute then exit; Table1.Close; Table1.TableName:=OpenDialog1.FileName; Table1.Open; end; procedure TForm1.DataSource1StateChange(Sender: TObject); begin case DataSource1.State of dsInactive: Caption:=’Отключено’; dsBrowse: Caption:=’Просмотр данных’; dsEdit: Caption:=’Правка данных’; dsInsert: Caption:=’Вставка данных’; dsOpening: Caption:=’Открытие источника данных’; else Caption:=’Состояние неопределено’; end; end;

            Пример программы находится в каталоге Demo\Part4\DataSource.

            Поля и класс TField

            Если для данных в целом используют компоненты типа Table, т.е. потомки TDataSet, то для, полей, которые представляют собой отдельные столбцы данных в таком наборе, определен класс TField. В свою очередь, от этого класса происходят типизированные классы полей типа TIntegerField, TStringField и т.п.

            Для доступа к полям записей у набора данных имеется ряд специальных методов и свойств, доступных во время выполнения. Чаще всего используются метод FieldByName, позволяющий обратиться к полю по его имени. Альтернативным и зачастую менее надежным способом является использование массива Fields, обеспечивающего доступ к полю по его порядковому номеру. И в том и в другом случае мы получим объект типа TField. Основные свойства этого класса приведены в таблице 20.1.

            Таблица 20.1. Основные свойства TField

            Свойство Тип Описание
            Alignment TAlignment Определяет выравнивание при выводе значения поля в визуальном компоненте
            AsBCD TBcd Содержит значение поля в двоичном виде (BCD)
            AsBoolean Boolean Содержит значение поля в виде булева значения
            AsCurrency Currency Содержит значение поля в виде Currency
            AsDateTime TDateTime Содержит значение поля в виде даты и времени
            AsFloat Double Содержит значение поля в виде вещественного числа
            AsInteger Integer Содержит значение поля в виде целого
            AsString String Содержит значение поля в виде строки
            AsVariant Variant Содержит значение поля в виде вариантного типа
            Calculated Boolean Определяет, является ли поле вычисляемым
            CanModify Boolean Указывает, может или нет быть изменено значение в данном поле
            DataSet TDataSet Определяет набор данных, которому принадлежит данное поле
            DataType TFieldType Указывает на тип данных поля
            DisplayLabel String Определяет текст, выводимый в качестве заголовка столбца в таблице DBGrid
            DisplayWidth Integer Определяет число символов, необходимое для вывода значения поля в визуальном компоненте
            FieldName String Определяет имя поля в физической таблице БД
            Index Integer Определяет порядковый номер поля в наборе данных
            IsIndexField Boolean Указывает, является ли данное поле индексированным
            IsNull Boolean Возвращает истину, если текущее значение поля пустое
            ReadOnly Boolean Определяет, может ли поле быть изменено пользователем
            Value Variant Содержит текущее значение поля
            Visible Boolean Определяет, должно ли это поле быть видимым при отображении в таблице DBGrid

            Здесь, прежде всего, следует выделить группу из 8 свойств, начинающихся с As. Все они, по сути, являются аналогами функций приведения к типу, поскольку, объект TField универсален и может содержать данные любого типа из встречающихся в СУБД. Например, если речь идет о полях из нашей таблицы bills, то обратиться к полям этой таблицы для получения их значений в «естественном» виде следует использовать подобный код:

            В то же время, если значения этих полей понадобятся нам для вывода в строковом виде (например, в поле однострочного редактора или), то ничего не мешает нам сразу же использовать приведение к нужному типу. В данном случае (с редактором и меткой) — к строковому:

            Если же при работе с записью нам не требуется явно приводить тип, то вполне подойдет свойство Value, содержащее текущее значение поля в БД. Оно же является одним из наиболее часто используемых, благодаря универсальности этого типа данных. Например, мы можем проверить значение поля на соответствие какому-либо условию, просто написав:

            if Fields[1].Value > 100 then Fields[1].Value:=200;

            При этом, правда, следует не забывать про то, что не всякий тип может быть приведен к нужному (в данном случае — к целому или вещественному числу).

            Свойства DataSet и FieldName отвечают за привязку объекта к конкретным данным, а Calculated и IsIndexFiels позволяют определить параметры поля в СУБД. Большинство остальных свойств так или иначе влияют на параметры вывода информации из набора данных в визуальные компоненты наподобие DBGrid.

            Что касается методов, то тут следует отметить метод Clear, устанавливающий значение поля в Null, и, пожалуй, IsValidChar, при помощи которого можно проверить, является ли указанный в качестве аргумента символ допустимым для данного поля:

            В данном случае заголовок окна получит надпись «Yes!», поскольку символ «1» можно использовать для ввода в поле числового типа. Но если бы мы проверяли на допустимость ввода какой-либо буквы, то получили бы отрицательный ответ.

            Типы полей и типы данных

            Как уже было отмечено, тип Field является лишь предком для целого ряда (всего их около 30) типизированных полей. При этом каждый тип поля в Delphi соответствует определенному типу данных, используемому в той или иной СУБД. Чаще всего используются следующие классы:


            • TBLOBField — поле BLOB-объекта;
            • TMemoField — поле типа Memo;
            • TGraphicField — графическое поле;
            • TStringField — поле строкового значения;
            • TBCDField — поле BCD-значения;
            • TDateTimeField — поле даты и времени;
            • TFloatField — поле вещественного числа;
            • TCurrencyField — поле денежной суммы;
            • TIntegerField — поле целого числа;
            • TAutoIncField — поле автоинкрементного значения.

            Не все перечисленные классы происходят напрямую от TField — для некоторых из них существует «промежуточный» предок. Например, для всех числовых типов определен общий класс TNumericField.

            В то же время, имеющееся у класса TField свойство DataType, позволяет получить информацию о том, какого типа значение хранится в поле. Таким образом, обращаясь к методу базового класса, можно не задумываться над тем, с каким конкретным типом объекта мы имеем дело. В Delphi 7 оно определено следующим образом:

            type TFieldType = (ftUnknown, ftString, ftSmallint, ftInteger, ftWord, ftBoolean, ftFloat, ftCurrency, ftBCD, ftDate, ftTime, ftDateTime, ftBytes, ftVarBytes, ftAutoInc, ftBlob, ftMemo, ftGraphic, ftFmtMemo, ftParadoxOle, ftDBaseOle, ftTypedBinary, ftCursor, ftFixedChar, ftWideString, ftLargeint, ftADT, ftArray, ftReference, ftDataSet, ftOraBlob, ftOraClob, ftVariant, ftInterface, ftIDispatch, ftGuid, ftTimeStamp, ftFMTBcd);

            Очевидно, что названия типов образуются от префикса ft и названия типа данных. Соответственно, если нам надо получить информацию о том, принадлежит ли интересующее нас поле к тому или иному типу, скажем, к Currency, то достаточно написать выражение вида:

            if Table1.FieldByName(‘BILL_SUMM’).DataType = ftCurrency then caption:=’$’;

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

            var sum: Currency; . if Table1.FieldByName(‘BILL_SUMM’).DataType in [ftInteger, ftWord, ftFloat, ftCurrency] then sum:=Table1.FieldByName(‘BILL_SUMM’).AsCurrency;

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

            Для определения статических полей следует воспользоваться редактором полей, который можно вызвать двойным щелчком по компоненту Table. Внешне он напоминает редактор списка столбцов компонента DBGrid (см. рис. 19.2), что, впрочем, и не удивительно, так как в обоих случаях мы имеем дело с редактором коллекций. Выбрав их контекстного меню редактора полей пункт Add Fields, мы получим диалоговое окно со списком всех имеющихся в текущей таблице БД полей. Выбрав нужные поля, остается нажать OK и приступить к исследованию получившегося списка. В нем будут находиться выбранные поля (обозначенные по своим заголовкам в таблице БД), являющиеся объектами какого-либо из типов полей. Например, для таблицы клиентов это могут быть поля типа TAutoIncField (для CUST_ID) и TStringField (для CUST_NAME). Если щелкнуть по названию поля в списке, то в инспекторе объектов мы увидим все его опубликованные свойства. При этом автоматически будут созданы и помещены в объявление класса формы соответствующие переменные:

            type TForm1 = class(TForm) Table1: TTable; Table1CUST_ID: TAutoIncField; Table1CUST_NAME: TStringField; . end;

            Соответственно, в дальнейшем мы сможем оперировать именно этими переменными, а не обращаться к полям таблицы при помощи методов FieldByName и подобных способов:

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

            ПРИМЕЧАНИЕ
            Разумеется, можно определить параметры вывода и для динамических полей, но это менее удобно, поскольку на этапе разработки их свойства недоступны, в то время, как параметры статических полей можно определить визуально при помощи инспектора объектов.

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

            Сортировка

            Изначально порядок расположения записей в наборе данных бывает неопределенным. Так, для таблиц одних СУБД (например, dBASE) записи располагаются в порядке поступления в файл таблицы, а в других (например, в Paradox) они сортируются по первичному индексу. Однако очень часто требуется вывести записи в определенном порядке, для чего используется сортировка данных.

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

            Для набора данных типа Table сортировка выполняется автоматически по выбранному индексу. Соответственно, если изменить индекс, например, при помощи свойств IndexFieldNames или IndexName, то записи будут упорядочены заново. В качестве значения для IndexName указывают имя индекса, а для IndexFieldNames указывают имена полей, из которых индекс состоит. Здесь следует отметить, что поскольку в таблицах Paradox первичный индекс не имеет имени, то для сортировки по нему можно использовать только свойство IndexFieldNames.

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

            ПРИМЕЧАНИЕ
            Помимо компонента Table, для наборов данных имеется SQL-ориентированный компонент Query. В случае использования Query сортировка производится при помощи языка SQL и может проводиться по любым полям. Вопросы использования компонента Query и языка запросов SQL будут рассмотрены в следующей главе.

            Для примера возьмем таблицу счетов и попробуем отсортировать ее по всем возможным индексам. Для этого нам понадобятся компоненты Table, DataSource и DBGrid, а так же RadioGroup. Для Table установим значение свойства DatabaseName в DATA1, TableName — в bill.db, а Active — в истину. После этого свяжем DBGridс Table через компонент DataSource.

            Как видно, изначально данные отображаются точно так же, как хранятся в таблице — последовательно с 1 по 8-я запись (на самом деле, в данном случае это заслуга первичного индекса, построенного по полю BILL_ID). Теперь попробуем произвести сортировку по вторичным индексам — CUST_IDX и SECOND_IDX, определенных в структуре таблицы БД, для чего определим 3 варианта в группе переключателей, назвав их «по умолчанию», «индекс 1» и «индекс 2». Теперь в процедуре для события OnClick группы напишем следующий код:

            case RadioGroup1.ItemIndex of 0: Table1.IndexName:=»; 1: Table1.IndexName:=’CUST_ ; end;

            Если теперь запустить приложение и выбрать какой-либо вариант сортировки, то можно убедиться, что данные в таблице будут отсортированы по 1-му, 2-му, или 3-му столбцу, в зависимости от выбранного положения переключателя (рис. 20.1).

            Рис. 20.1. Сортировка по сумме счета (индекс SECOND_IDX)

            Недостатком использования свойства IndexName в данном случае состоит в том, что для таблиц Paradox, как и в данном случае, не удастся задействовать поле с первичным индексом. В таком случае можно воспользоваться свойством IndexFieldNames, для чего добавим еще одну группу, на этот раз — из 4 переключателей, а в обработчике для нее напишем следующий код:

            case RadioGroup2.ItemIndex of 0: Table1.IndexFieldNames:=»; 1: Table1.IndexFieldNames:=’BILL_CUST’; 2: Table1.IndexFieldNames:=’BILL_ ; end;

            Теперь в 2 случаях (варианты 2 и 3) можно производить сортировку по нескольким полям сразу. Так же следует отметить, что свойства IndexName и IndexFieldNames являются взаимоисключающими, т.е. если установить какое-либо значение для одного из этих свойств, значение другого автоматически сбрасывается. Исходный код этой программы находится в каталоге Demo\Part4\Sort.

            Что касается направления сортировки — по возрастанию или по убыванию, то за это отвечает флаг ixDescending свойства Options определения индекса. Эти определения, в свою очередь, задаются через свойство IndexDefs.

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

            Мы уже рассматривали навигацию при помощи специального компонента — DBNavigator. Теперь рассмотрим, как можно осуществлять ее программным способом, обращаясь непосредственно к методам набора данных.

            Для перемещения указателя текущей записи можно использовать следующие процедуры набора данных:

            • First — устанавливает указатель на первую запись;
            • Next — устанавливает указатель на запись, следующую за текущей;
            • Last — устанавливает указатель на последнюю запись;
            • Prior — устанавливает указатель на запись, предшествующую за текущей;

            Для перемещения на несколько записей сразу используют функцию MoveBy — она принимает в качестве аргумента число, на которое должно произойти перемещение, а возвращает число записей, на которое реально переместился курсор (разница может быть вызвана, например тем, что конец набора данных был достигнут «досрочно»). Перемещение при помощи этой функции можно производить в обоих направлениях:

            Table1.MoveBy(10); //перемещение указателя на 10 записей вперед Table1.MoveBy(-3); //перемещение указателя на 3 записи назад x:=Table1.MoveBy(y); //использование возвращаемого значения if x<>y then Caption:=’Перемещено только на ‘+IntToStr(x)+’ записей’;

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

            ВНИМАНИЕ
            Следует учитывать, что указатель привязывается именно к записи, а не к ее расположению в наборе данных. Соответственно, если после сортировки порядок записей изменится, указатель будет ссылаться на ту же запись, что и до сортировки. А вот после фильтрации указатель всегда указывает на первую запись в наборе данных.

            Для примера возьмем все ту же таблицу счетов, задействовав компоненты Table, DataSource и DBGrid. Еще нам понадобятся 3 кнопки. Первая будет перемещать курсор на следующую запись, а вторая — на предыдущую. Соответственно, код для первой кнопки будет выглядеть так:

            А у второй, соответственно:

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

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

            Листинг 20.2. Последовательный обход всех записей в источнике данных

            procedure TForm1.Button3Click(Sender: TObject); var i: integer; x: currency; begin Table1.First; // начинаем с 1-й записи x:=0; for i:=0 to Table1.RecordCount-1 do begin x:=x+Table1.FieldByName(‘BILL_SUMM’).AsCurrency; Table1.Next; // не забываем перевести курсор на следующую запись end; Caption:=CurrToStr(x); end;

            Для того чтобы пройтись по всем записям, здесь мы воспользовались таким свойством набора данных, как RecordCount. Это вполне логичный вариант, однако, он не совсем идеален с той точки зрения, что нам требуется дополнительная переменная. В то же время, у набора данных имеется такое свойство, как Eof, при помощи которого можно узнать, достигнут или нет конец. Соответственно, чтобы избавиться от лишней переменной в своем коде, мы можем использовать цикл while:

            while not Table1.Eof do begin x:=x+Table1.FieldByName(‘BILL_SUMM’).AsCurrency; Table1.Next; end;

            В любом случае, по завершении цикла мы получим нужный нам результат в переменной x. Исходный код приведен в примере в каталоге Demo/Part4/Navigate.

            Помимо свойства Eof, существует и свойство Bof, проверяющее, находится ли указатель в начале набора данных. Он может потребоваться, например, в том случае, если необходимо произвести подсчет в обратном порядке:

            Table1.Last; while not Table1.Bof do begin x:=x+Table1.FieldByName(‘BILL_SUMM’).AsCurrency; Table1.Prior; end;

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

            Фильтрация

            Фильтрацией называют определение ограничений для записей, отбираемых для набора данных. По умолчанию фильтрация отключена, однако всегда можно ею воспользоваться, определив параметры фильтра в свойстве Filter и установив другое свойство — Filtered — в истину.

            ПРИМЕЧАНИЕ
            При работе с запросами SQL посредством компонента Query или ему аналогичного, фильтрация набора данных действует поверх ограничений, заданных в самом SQL-запросе. Иначе говоря, если при помощи запроса уже был произведен «отсев» данных, то фильтрация дополнительно ограничит число выводимых записей.

            В качестве выражения, определяющего фильтр, используется строковое значение, составленное по определенным правилам. В состав фильтра могут входить такие элементы, как имена полей таблиц, литералы, скобки, операции сравнения, а так же арифметические и логические операции. Под литералом здесь понимается заданное явно значение — число, строка или символ. Из арифметических операций доступны такие, как сложение, вычитание, умножение и деление. В качестве логических операций можно использовать AND, OR и NOT. Ну а скобки используются, как обычно, для изменения порядка выполнения операций.

            Для примера рассмотрим несколько вариантов фильтров:

            BILL_SUMM > 150 BILL_CUST > 2 AND BILL_CUST

            В первом случае будут отобраны только те ряды, у которых в поле BILL_SUMM значение превышает 150, а во втором — ряды, поле BILL_CUST которых имеет значения больше 2 и меньше 5. Если в выражении фильтра используются строковые или символьные литералы, то они должны быть заключены в одинарные кавычки:

            CUST_NAME = ‘OOO «Alpha»‘

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

            Если же сам фильтр вводится в код программы как литерал, то можно заранее продублировать кавычки:

            Table1.Filter:=’CUST_NAME = »OOO «Gamma»»;

            Теперь создадим небольшое приложение, которое будет осуществлять фильтрацию записей в наборе данных. В качестве набора вновь используем таблицу (Table), так же нам понадобятся компоненты DBGrid и DataSource. Для управления фильтром поместим на форму компонент-редактор (Edit) и 2 кнопки (Button). Первая кнопка, назовем ее «Применить» будет включать фильтрацию путем присвоения значения, определенного в редакторе, свойству Filter таблицы и назначения значения истины свойству Filtered:

            Вторая же кнопка — «Сброс» — будет просто отключать фильтрацию:

            Теперь остается запустить приложение, ввести какое-либо подходящее выражение в строку фильтра и нажать кнопку «Применить».

            Исходный код примера находится в каталоге Demo\Part4\Filter.

            Еще одним свойством набора данных, имеющим отношение к фильтрации, является FilterOptions. Оно представляет собой набор из 2 флагов — foCaseInsensitive и foNoPartialCompare. Первый из них, будучи установленным, делает фильтрацию в строках регистронезависимой. Второй же заставит интерпретировать знак * (звездочку) как символ, в противном случае звездочка будет выполнять роль шаблона подстановки. Например, можно определить фильтр таким образом:

            В том случае, если флаг foNoPartialCompare выключен, то будут отобраны все записи, начинающиеся с «ООО «. Если же этот флаг включен, то подойдет только запись, имеющая именно такое значение, т.е. «ООО *».

            Поиск

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

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

            Функция Locate производит поиск записи с удовлетворяющими условия поиска значениями полей. Если такая запись будет найдена, то курсор будет переведен на нее, а функция возвратит истину. Если же совпадений не найдется, то функция возвратит ложь. Определена эта функция следующим образом:

            function Locate(const KeyFields: string; const KeyValues: Variant; Options: TLocateOptions): Boolean;

            Здесь KeyFields определяет поля, по которым производится поиск, KeyValues — выражение для поиска, а Options задает такие параметры, как распознавание регистра в строках и возможность совпадения по части слова.

            Для примера создадим приложение, которое сможет осуществлять поиск по названиям клиентов в нашей таблице Customers. Как обычно, понадобятся связка из компонентов Table, DataSource и DBGrid. Кроме них, задействуем однострочный редактор, кнопку и 2 переключателя типа CheckBox. Первый переключатель подпишем как «Без учета регистра», а второй — «Совпадение по части». Теперь остается написать код для процедуры обработки нажатия на кнопку, подобно тому, что приведен в листинге 20.3.

            Листинг 20.3. Поиск с опциями

            procedure TForm1.Button1Click(Sender: TObject); var lo: TLocateOptions; begin lo:=[]; if CheckBox1.Checked then lo:=lo+[loCaseInsensitive]; if CheckBox2.Checked then lo:=lo+[loPartialKey]; Table1.Locate(‘CUST_NAME’,Edit1.Text,lo); end;

            Данный пример иллюстрирует типичное применение поиска по одному полю (см. также пример в Demo\Part4\Search). В том же случае, если поиск необходимо провести по нескольким полям, то сами поля перечисляют через точку с запятой для параметра KeyFields и определяют массив значений для KeyValues:

            Здесь производится поиск по полям CUST_NAME и CUST_ID, при этом совпавшими будут считаться записи, у которых в поле CUST_NAME будет значение «OOO «Beta»», а в поле CUST_ID — число 2. Что касается функции VarArrayOf, то она используется для одновременного создания и заполнения массива типа Variant.

            Второй метод нахождения записей — Lookup — предназначен для нахождения искомого в источнике данных и возвращения нужных данных. Объявлена она следующим образом:

            function Lookup(const KeyFields: string; const KeyValues: Variant; const ResultFields: string): Variant;

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

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

            Функции Locate и Lookup хороши тем, что могут использоваться для поиска по любым полям набора данных. Если же поиск требуется осуществлять по индексированным полям, то выбор методов существенно расширяется. В частности, для поиска одной записи можно использовать методы GotoKey и FindKey, производящие поиск по точному соответствию. А методы GotoNearest и FindNearest производят поиск по частичному соответствию. При этом перед вызовом методов GotoKey и GotoNearest необходимо вызывать метод EditKey или SetKey, чтобы перевести компонент Table в режим редактирования ключа поиска:

            Table1.EditKey; Table1.FieldByName(‘CUST_NAME’).AsString := Edit1.Text; if not Table1.GotoKey then ShowMessage(‘Совпадения не найдено!’);

            Здесь подразумевается, что поле CUST_NAME является индексированным.

            Еще одна группа методов, работающих с индексированными полями, позволяет работать с диапазоном записей. К ним относятся: SetRangeStart, SetRangeEnd, EditRangeStart, EditRangeEnd, ApplyRange и CancelRange. Для использования поиска диапазона записей необходимо установить начало и конец диапазона вызовом функций SetRangeStart и SetRangeEnd, или EditRangeStart и EditRangeEnd, указывая при этом граничные значения полей. Затем, вызвав метод ApplyRange, указанный диапазон применяется к набору данных:

            with Table1 do begin SetRangeStart; //включаем режим установки начала диапазона FieldByName(‘CUST_ ).AsString:=Edit2.Text; //определяем конец диапазона ApplyRange; //Применяем диапазон end;

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

            Редактирование

            Помимо поиска, фильтрации и навигации, существует другая не менее важная группа действий, выполняемых с наборами данных. Это редактирование (изменение), удаление и добавление записей. Все эти операции доступны только в том случае, если источник данных, во-первых, поддерживает модификацию данных как таковую, а во-вторых, если она разрешена в данный момент времени. В частности, компонент Table поддерживает модификацию, а за возможность ее производить отвечает свойство ReadOnly: если оно установлено в истину, то править данные нельзя, а если в ложь (что принято по умолчанию) — то можно. Проверить, допустимо ли изменение данных, можно при помощи свойства CanModify, доступное во время выполнения. И если оно содержит значение истины, то можно смело приступать к правке.

            ПРИМЕЧАНИЕ
            Следует отметить, что предоставить пользователю возможность правки данных возможно при помощи визуальных компонент — без написания какого-либо кода вообще, для чего достаточно воспользоваться компонентами DBGrid и DBNavigator.

            Редактирование записей заключается в изменении значений их полей. Отредактирована в один момент времени может только одна запись, причем только текущая (та, на которую ссылается курсор). Поэтому перед редактированием следует выбрать запись, например, при помощи метода Locate. После этого последовательно выполняются следующие операции:

            • перевод набора данных в режим редактирования (вызов метода Edit);
            • собственно изменение значений полей записи;
            • подтверждение изменений (метод Post) или отказ от них (метод Cancel).

            В виде программного кода это реализуется следующим образом (см. пример в каталоге Demo\Part4\Edit):

            Table1.Edit; Table1.FieldByName(‘CUST_NAME’).AsString:=Edit1.Text; Table1.Post;

            Здесь мы меняем значение поля CUST_NAME в текущей записи на содержимое однострочного редактора. Если бы требовалось изменить значение, скажем, числового поля, то следовало бы либо преобразовать значение Edit1.Text в число, либо использовать другой компонент, например, UpDown:

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

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

            with Table1 do begin First; while not Table1.Eof do begin Edit; FieldByName(‘BILL_SUMM’).AsCurrency:=FieldByName(‘BILL_SUMM’).AsCurrency*1.2; Post; Next; end; end;

            Обратите внимание, что метод Edit вызывается при каждой итерации цикла, поскольку после обработки метода Post таблица возвращается к режиму чтения. Так же полезно знать, что метод Next, в случае необходимости, может сам вызывать метод Post, однако если вместо Next использовать, скажем, FindNext, то Post вызван не будет. Поэтому, во избежание возможных ошибок в процессе модернизации кода, желательно вызывать метод Post явно.

            Добавление и удаление

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

            • перевести набор данных в режим вставки записи;
            • определить значения полей новой записи;
            • подтвердить сделанные изменения или отказаться от них.

            Очевидно, что последовательность действий практически полностью аналогично тому, что мы уже видели при редактировании, с той лишь разницей, что вместо перехода к режиму редактирования используется переход в режим вставки. Для этих целей предусмотрено 4 метода — Insert, Append, InsertRecord и AppendRecord.

            Методы Insert и Append переводят набор данных в режим вставки, и добавляют к нему новую пустую запись. Различие между этими методами состоит лишь в том, что если метод Append всегда добавляет запись в конец набора данных, то метод Insert — в позицию, на которой находится курсор ввода. Поэтому перед обращением к методу Insert обычно предварительно перемещают указатель в требуемую позицию набора данных. Например, для вставки нового ряда в самое начало набора, можно использовать следующий код:

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

            Table1.FieldByName(‘BILL_CUST’).AsInteger:=’5′; Table1.FieldByName(‘BILL_SUMM’).AsCurrency:=’155.99′; Table1.Post;

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

            Более того, некоторые поля могут являться заполняемыми автоматически, например, поле BILL_ID в таблице счетов имеет автоинкрементный тип и получит нужное значение путем встроенных механизмов СУБД. Правда, произойдет это не сразу, а лишь при выполнении последней стадии редактирования — подтверждения, что, как и при обычной правке делается методом Post. Если же отказаться от внесенной правки (метод Cancel), то не будут сохранены не только введенные значения, но и сам добавленный ряд тоже.

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

            Table1.SetFields([Nil, 5, 155.99]);

            Здесь в качестве значения для первого поля указано значение Nil, поскольку подразумевается, что это поле автоинкрементного типа и значение для него будет назначено автоматически во время подтверждения. Отметим так же, что этого самого подтверждения (использования метода Post) в данном случае явно указывать не надо, поскольку метод SetFields вызывает его самостоятельно. Метод SetFields можно использовать не только при добавлении, но и при редактировании записей. В таком случае для тех полей, значения которых менять не требуются, так же указывают Nil.

            Наконец, наиболее комплексным подходом к вставке записей является использование методов InsertRecord и AppendRecord. Метод InsertRecord объединяет в себе методы Insert и SetFields. Соответственно, чтобы добавить новую запись с его помощью, достаточно написать:

            Table1.InsertRecord([Nil, 5, 155.99]);

            Аналогично, метод AppendRecord, объединяет в себе методы Append и SetFields, т.е. добавляет и заполняет значениями запись в конце набора данных.

            Что касается удаления записей, то для этих целей используется метод Delete. Он удаляет текущую запись из набора данных и перемещает указатель текущей записи на следующую запись. При этом если запись в момент вызова этого метода находилась в режиме вставки, то вызов Delete, по сути, будет аналогичен обращению к методу Cancel. Если же набор данных не содержит ни одной записи, то вызов метода Delete инициирует исключение.

            Тот факт, что при удалении записи указатель перемещается автоматически, делает ненужным перемещение указателя методами типа Next. Например, чтобы удалить все записи из набора достаточно написать такой цикл:

            while not Table1.Eof do Table1.Delete;

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

            Простое приложение БД

            На основе полученных знаний в области баз данных, создадим приложение, которое будет работать с нашей базой данных. Оно должно будет производить такие действия, как вывод списка клиентов с возможностью поиска по нему и выводить сведения обо всех счетах на отдельно взятого клиента. Так же предусмотрим возможность создания новых клиентов и счетов путем заполнения специальных форм. Назовем мы создаваемую программу «База 1.0».

            Чтобы не отвлекаться на особенности визуального программирования, при разработке этого приложения мы постараемся максимально использовать программный код для выполнения всех действий, относящихся к взаимодействию с СУБД. Окончательный вариант того, что должно будет получиться, можно найти в каталоге Demo\Part4\DBApp.

            Прежде чем приступить собственно к написанию приложения, немного модифицируем тестовую базу данных, для чего в таблицу счетов добавим поле даты счета (тип Date), а в таблицу клиентов — строковое поле адреса (модифицированный вариант БД находится в подкаталоге data, вложенном в указанный выше каталог с рассматриваемым приложением).

            Теперь приступим к разработке самой «Базы 1.0». Для начала назовем главное окно приложения MainFrm и сохраним его под именем «main.pas», а сам проект сохраним под названием «mydb». Затем поместим на главной форме компонент DBGrid, отведя для него основное пространство окна, а под ним в ряд расположим однострочный текстовый редактор и 4 кнопки — «Баланс», «Новый счет», «Новый клиент» и «Выход» (рис. 20.2).

            Рис. 20.2. Форма главного окна приложения базы даных

            Чтобы в дальнейшем, при написании кода, не возникало вопросов, какой элемент интерфейса за что отвечает, присвоим всем компонентам более осмысленные имена. Так, таблицу назовем CustGrd, редактор — NameEd, а кнопки — BalanceBtn, NewBillBtn, NewCustBtn и CloseBtn. Учитывая тот факт, что для ввода новых клиентов мы собираемся предусмотреть отдельное окно, то чтобы придать приложению более «профессиональный» вид, в свойстве Options таблицы включим флаг dgRowSelect, в результате чего записи будут выделяться целиком. В свойствах самой формы отключим кнопку разворачивания на полный экран путем выключения флага biMaximize у свойства BorderIcons, а рамку сделаем неизменяемой, установив свойство BorderStyle в bsSingle. Наконец, для свойства Position выберем значение poScreenCenter, а в Caption напишем название программы — «База 1.0».

            Дабы не загромождать главную форму приложения компонентами доступа к данным, используем форму данных (File ‘ New ‘ Data Module), на которую, в свою очередь, поместим компонент Database и 2 пары компонентов Table и DataSource. Компонент Database назовем MainDB, это же значение используем как название базы данных (свойство DatabaseName), а для свойства DriverName выберем Standard. Компоненты-таблицы назовем CustTbl и BillTbl, а источники данных — CustBs и BillDs, после чего привяжем их к таблицам при помощи свойства DataSet. Саму форму данных назовем data, и сохраним файл под именем dm.pas.

            Прежде, чем браться за дальнейшую работу, определим программный код, который будет выполнять собственно подключение к данным. Для этого нам потребуется определить параметры компонента Database (MainDB) и обеих таблиц в момент создания модуля. Сделать это можно, написав для события OnCreate формы data следующий код:

            MainDB.Params.Add(‘path=’+ExtractFilePath(paramstr(0))+’data’); MainDB.Connected:=true; CustTbl.DatabaseName:=’MainDB’; CustTbl.TableName:=’customer.DB’; CustTbl.Active:=true; BillTbl.DatabaseName:=’MainDB’; BillTbl.TableName:=’bill.DB’; BillTbl.Active:=true;

            Теперь можно вернуться к главному окну приложения и после ключевого слова uses в части interface модуля добавить модуль dm. Это позволит нам уже на данном этапе указать в свойстве DataSource таблицы CustGrd нужное нам значение — data.CustDS.

            Следующим этапом будет разработка формы отчета по счетам. Для этого нам понадобится еще одна форма (File ‘ New ‘ Form), на которой мы разместим таблицу DBGrid и одну единственную кнопку. Как и в главной форме, в части interface, в списке модулей после ключевого слова uses добавим dm. Теперь для свойства DataSource таблицы укажем значение data.BillDS, а саму таблицу назовем BillsGrd. Кроме того, подобно таблице в главном окне, включим флаг dgRowSelect в свойстве Options.

            Единственную имеющуюся на этой форме кнопку назовем CloseBtn и сразу же напишем код для обработчика события OnClick, который будет состоять из единственного выражения: close. Наконец, свойство BorderStyle установим в bsDialog, а для свойства Position выберем значение poMainFormCenter.

            Вновь перейдем к главному окну и напишем обработчик события для кнопки «Баланс», т.к. именно при помощи этой кнопки будет открываться окно счетов. Для того чтобы вывести все счета для выбранного клиента, нам понадобится фильтрация в таблице счетов по полю BILL_CUST. А для того, чтобы подсчитать сумму всех счетов, надо будет пройтись по всем отфильтрованным записям и просуммировать значение поля BILL_SUMM. Наконец, не помешает вывести в заголовок окна имя клиента. В результате мы получим код, приведенный в листинге 20.4.

            Листинг 20.4. Вывод информации по счетам с подсчетом общей суммы

            procedure TMainFrm.BalanceBtnClick(Sender: TObject); var summ: Currency; begin Data.BillTbl.Filter:= ‘BILL_CUST=’+Data.CustTbl.FieldByName(‘CUST_ +CurrToStr(summ); BillsFrm.ShowModal; end;

            Здесь сначала производится фильтрация таблицы счетов по текущему значению поля CUST_ID в таблице клиентов, после чего производится суммирование значений поля BILL_SUMM при помощи заранее объявленной переменной summ. После этого составляется заголовок окна, и оно показывается в качестве модального диалога. Если на данном этапе запустить приложение, то можно будет убедиться, что в главную форму выводится список клиентов, а при нажатии на кнопку «Баланс» открывается окно со счетами. Для упрощения навигации по списку клиентов (предположим, что их там у нас не несколько штук, а несколько десятков или сотен), реализуем поиск по вводу. Разумеется, подобный поиск должен быть регистро-независимым и осуществляться по части слова. Для этого в обработчике события OnChange текстового редактора NameEd следует предусмотреть соответствующий код:

            Теперь займемся интерфейсом для создания новых счетов и новых клиентов. Для этого нам понадобятся еще 2 формы, назовем их BillFrm и CustFrm.На первой нам понадобятся компонент-редактор для ввода суммы (назовем его SummEd) и компонент DateTimePicker для указания даты (DatePick). На второй — 2 редактора: один — для ввода имени клиента (NameEd), другой — для адреса (AddrEd). Так же на обоих формах нам потребуются кнопки «ОК» и «Отмена». Поскольку оба этих окна, подобно окну со счетами, будут модальными диалогами, то установим свойства BorderStyle в bsDialog, и Position — в poMainFormCenter. Для кнопок отмены в обоих случаях код будет выглядеть следующим образом:

            Это позволит определить дальнейшие действия программы после того, как окно закроется и выполнение должно будет быть продолжено в том месте, откуда данное окно было вызвано. Что касается кнопок ОК, то для формы нового счета она должна будет проверять на корректность значение, введенное в поле суммы. Реализовать такую проверку можно так:

            if StrToCurrDef(SummEd.Text,0)<>0 then ModalResult:=mrOk else MessageDlg(‘Сумма накладной не может быть нулевой, либо число введено неверно’,mtError,[mbOk],0);

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

            Аналогичным образом следует организовать проверку и в окне клиента, с той лишь разницей, что проверять в нем следует содержимое строки с названием — оно не должно быть пустым:


            if NameEd.Text<>» then ModalResult:=mrOk else MessageDlg(‘Не указано имя клиента’,mtError,[mbOk],0);

            Собственно, остается только написать код, который и будет вызывать эти окна из главной формы, и производить нужные манипуляции над данными. В частности, для создания нового счета нам надо будет показать окно-форму счета, не забыв при этом указать в заголовке окна имя клиента, затем, если форма возвратит результат mrOk, то выполнить вставку данных, введенных пользователем в окне счета, в таблицу bill. Такая процедура, написанная с использованием метода Append, представлена в листинге 20.5. В нем же представлен вариант добавления записи в таблицу клиентов, но с использованием комбинированного метода AppendRecord.

            Листинг 20.5. Добавление новых записей в таблицу счетов и в таблицу клиентов

            procedure TMainFrm.NewBillBtnClick(Sender: TObject); begin with BillFrm, Data.BillTbl do begin Caption:=’Счет для ‘+Data.CustTbl.FieldByName(‘CUST_NAME’).AsString; ShowModal; if ModalResult<>mrOk then exit; Append; FieldByName(‘BILL_CUST’).AsInteger:= Data.CustTbl.FieldByName(‘CUST_ ).AsDateTime:=DatePick.Date; Post; end; end; procedure TMainFrm.NewCustBtnClick(Sender: TObject); begin with CustFrm do begin ShowModal; if ModalResult<>mrOk then exit; Data.CustTbl.AppendRecord([nil,NameEd.Text,AddrEd.Text]); Data.CustTbl.Post; end; end;

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

            with CustFrm do begin NameEd.Text:=Data.CustTbl.FieldByName(‘CUST_NAME’).AsString; AddrEd.Text:=Data.CustTbl.FieldByName(‘CUST_ADDRESS’).AsString; ShowModal; if ModalResult<>mrOk then exit; Data.CustTbl.Edit; Data.CustTbl.FieldByName(‘CUST_NAME’).AsString:=NameEd.Text; Data.CustTbl.FieldByName(‘CUST_ADDRESS’).AsString:=AddrEd.Text; Data.CustTbl.Post; end;

            Что касается изменения счетов, то в форме списка счетов (BillsFrm) определим вызов формы составления счета (BillFrm) так же по двойному щелчку, по таблице BillsGrd. При этом следует не забыть указать модуль формы счета в списке uses части implementation. Полный код этой части модуля приведен в листинге 20.6.

            Листинг 20.6. Часть implementation модуля bills

            implementation uses bill; <$R *.dfm>procedure TBillsFrm.CloseBtnClick(Sender: TObject); begin close; end; procedure TBillsFrm.BillsGrdDblClick(Sender: TObject); begin with BillFrm, Data.BillTbl do begin Caption:=’Счет для ‘+Data.CustTbl.FieldByName(‘CUST_NAME’).AsString; SummEd.Text:=FieldByName(‘BILL_SUMM’).AsString; DatePick.Date:=FieldByName(‘BILL_DATE’).AsDateTime; ShowModal; if ModalResult<>mrOk then exit; Edit; FieldByName(‘BILL_SUMM’).AsString:=SummEd.Text; FieldByName(‘BILL_DATE’).AsDateTime:=DatePick.Date; Post; end; end; end.

            Теперь наша программа умеет даже несколько больше, чем планировалось ранее. А именно, помимо обеспечения удобной навигации по списку клиентов (под удобством мы здесь понимаем возможность поиска по первым введенным буквам), просмотра счетов для каждого клиента, и созданию новых записей клиентов и счетов, мы так же получили возможность изменять все эти записи. Теоретически, подобных функциональных возможностей мы могли бы добиться, просто поместив на форму 2 связанных по принципу «один ко многим» таблицы, как это уже было продемонстрировано в самом начале этой части книги. Однако для конечного пользователя тот вариант был бы гораздо менее понятным и удобным. Кроме того, здесь мы реализовали базовую проверку вводимых значений на допустимость, что на практике позволяет минимизировать возможные ошибки при вводе данных.

            Использование процедур и функций в Delphi

            Скобки

            Добавление скобок при вызове процедур и функций без параметров уже давно не является новинкой в Delphi, тем не менее, эта возможность мало известна. Эту возможность оценят по достоинству те программисты, которым приходится работать на двух языках (C++ и Delphi), так как им не нужно будет постоянно помнить о разнице в синтаксисе при вызове процедур и функций в разных языках. В Delphi оба варианта, приведенные ниже, считаются корректными.

            Возможность перегрузки

            Впервые концепция перегрузки процедур и функций была реализована в Delphi 4. Она позволяет иметь несколько различных процедур и функций с одинаковыми именами, но с разными списками параметров. Такие процедуры и функции должны быть описаны с применением директивы overload.

            procedure Test (I: integer); overload;
            procedure Test (S: string); overload;
            procedure Test (D: double); overload;

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

            Передача параметров

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

            Передача параметров по значению

            Этот режим передачи параметров применяется по умолчанию. Если параметр передается по значению, создается локальная копия данной переменной, которая и предоставляется для обработки в процедуру или функцию. Посмотрите на следующий пример:

            procedure Test(s: string);

            При вызове указанной процедуры будет создана копия передаваемой ей в качестве параметра строки s, с которой и будет работать процедура Test. При этом все внесенные в строку изменения никак не отразятся на исходной переменной s.

            Однако это не относится к объектам. Например, если в функцию передается переменная (а точнее экземпляр объекта) TStringList, то в данном случае произойдет передача по ссылке (даже если это не указано явно). Этот способ передачи является у большинства самым излюбленным, но в тоже время является и самым не практичным, т.к. для выполнения метода выделяется дополнительная память для создания точной копией передаваемой переменой. Для решения этой проблемы следует использовать один из способов описанных ниже.

            Передача параметров по ссылке

            Pascal позволяет также передавать параметры в функции или процедуры по ссылке — такие параметры называются параметрами-переменными. Передача параметра по ссылке означает, что функция или процедура сможет изменить полученные значения параметров. Для передачи параметров по ссылке используется ключевое слово var, помещаемое в список параметров вызываемой процедуры или функции.

            procedure ChangeMe(var x: longint);
            begin
            x := 2; // Параметр х изменен вызванной процедурой
            end;

            Вместо создания копии переменной x, ключевое слово var требует передачи адреса самой переменной x, что позволяет процедуре непосредственно изменять ее значение.

            Передача параметров констант

            Если нет необходимости изменять передаваемые функции или процедуре данные, можно описать параметр как константу. Ключевое слово const не только защищает параметр от изменения, но и позволяет компилятору сгенерировать более оптимальный код передачи строк и записей. Вот пример объявления параметра-константы:

            procedure Test(const s: string );

            Передача открытых массивов

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

            function AddEmUp(A: array of integer): integer;

            В открытом массиве можно передавать переменные, константы или выражения из констант.

            Для получения информации о фактически передаваемом массиве параметров в функции или процедуре могут использоваться функции High, Low и SizeOf.

            Object Pascal также поддерживает тип array of const, который позволяет передавать в одном массиве данные различных типов. Синтаксис объявления функций или процедур, использующих такой массив для получения параметров, следующий:

            procedure WhatHaveIGot( A: array of const );

            Вызвать объявленную выше функцию можно, например, с помощью такого оператора:

            procedure WhatHaveIGot( [‘Text’, 10, 5.5, @WhatHaveIGot, 3.14, true, ‘c’] );

            При передаче функции или процедуре массива констант все передаваемые параметры компилятор неявно конвертирует в тип TVarRec. Тип данных TVarRec объявлен в модуле System следующим образом:

            PVarRec = ^TVarRec;
            TVarRec = record
            case Byte of
            vtInteger: (VInteger: Integer; VType: Byte);
            vtBoolean: (VBoolean: Boolean);
            vtChar: (VChar: Char);
            vtExtended: (VExtended: PExtended);
            vtString: (VString: PShortString);
            vtPointer: (VPointer: Pointer);
            vtPChar: (VPChar: PChar);
            vtObject: (VObject: TObject);
            vtClass: (VClass: TClass);
            vtWideChar: (VWideChar: WideChar);
            vtPWideChar: (VPWideChar: PWideChar);
            vtAnsiString: (VAnsiString: Pointer);
            vtCurrency: (VCurrency: PCurrency);
            vtVariant: (VVariant: PVariant);
            vtInterface: (VInterface: Pointer);
            vtWideString: (VWideString: Pointer);
            vtInt64: (VInt64: PInt64);
            end;

            Поле VType определяет тип содержащихся в данном экземпляре записи TVarRec данных и может принимать одно приведенных значений.

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

            procedure WhatHaveIGot( A: array of const );
            var
            i: integer;
            TypeStr: string;
            begin
            for i := Low(A) to High(A) do
            begin
            case A[i].VType of
            vtInteger : TypeStr := ‘Integer’;
            vtBoolean : TypeStr := ‘Boolean’;
            vtChar : TypeStr := ‘Char’;
            vtExtended : TypeStr := ‘Extended’;
            vtString : TypeStr := ‘String’;
            vtPointer : TypeStr := ‘Pointer’;
            vtPChar : TypeStr := ‘PChar’;
            vtObject : TypeStr := ‘Object’;
            vt ;
            vtW ;
            vtPW ;
            vtAnsiString : TypeStr := ‘AnsiString’;
            vtCurrency : TypeStr := ‘Currency’;
            vtVariant : TypeStr := ‘Variant’;
            vtInterface : TypeStr := ‘Interface’;
            vtW ;
            vtInt64 : TypeStr := ‘Int64’;
            end;
            ShowMessage( Format( ‘Array item %d is a %s’, [i, TypeStr] ) );
            end;
            end;

            Значения параметров по умолчанию

            В Delphi есть одна очень полезная возможность — использование значений параметров по умолчанию. Она позволяет установить принимаемое по умолчанию значение параметра процедуры или функции. Это значение будет использоваться в тех случаях, когда вызов процедуры или функции производится без указания значения данного параметра. В объявлении процедуры или функции принимаемое по умолчанию значение параметра указывается после знака равенства, следующего после его имени. Поясним это на следующем примере:

            procedure HasDefVal( s: string; i: integer = 0 );

            Подобное объявление означает, что процедура HasDefVal может быть вызвана двумя путями. В первом случае — как обычно, с указанием обоих параметров:

            procedure HasDefVal( ‘Hello’, 26 );

            Во втором случае можно задать только значение параметра s, а для параметра i использовать значение, установленное по умолчанию:

            procedure HasDefVal( ‘Hello’ );

            При использовании значении параметров по умолчанию следует помнить о нескольких приведенных ниже правилах:

            • Параметры, имеющие значения по умолчанию, должны располагаться в конце списка параметров. Параметр без значения по умолчанию не должен встречаться в списке после параметра, имеющего значение по умолчанию.
            • Значения по умолчанию могут присваиваться только параметрам обычных типов, указателям или множествам.
            • Значение по умолчанию может передаваться только по значению либо с модификатором const. Оно не может быть ссылкой или нетипизированным параметром.

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

            function Add( I1, I2: integer ): integer;
            begin
            Result := I1 + I2;
            end;

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

            function Add( I1, I2: integer; I3: integer = 0 ): integer;
            begin
            Result := I1 + I2 + I3;
            end;

            Директива

            Директива <$X->запрещает вызов функций как процедур (с игнорированием возвращаемого результата). По умолчанию этот режим включен (<$X+>). Так вот, запомните, использование переменной Result недопустимо при сброшенном флажке опции Extended Syntax, расположенном во вкладке Compiler диалогового окна Project Options, или при указании директивы компилятора <$X->.

            В каждой функции языка Objecl Pascal существует локальная переменная с именем Result, предназначенная для размещения возвращаемого значения. Кроме того, вернуть значение из функции можно также путем присвоения значения переменной, имеющей то же имя, что и данная функция. Это стандартный синтаксис языка Pascal, сохранившийся от его предыдущих версий. При использовании в теле функции переменной с ее именем не забывайте, что существуют большие отличия в обработке этого имени — все зависит от того, где она расположена — в левой части оператора присвоения или же в любом другом месте текста функции. Если имя функции указано в левой части оператора присвоения, то предполагается, что назначается возвращаемое функцией значение. Во всех других случаях предполагается, что осуществляется рекурсивный вызов этой функции.

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

            Разборка и чистка ноутбука Lenovo IdeaPad Yoga 13

            Решили почистить ультрабук леново йогу от пыли. В плане разборки ноутбук интересный, но без специальной отвёртки его разобрать очень сложно (если вдруг кто захочет маленькой плоской отвёрткой открутить винтики с шляпкой под звёздочку).

            Разборка и чистка ноутбука HP dv6-3106er

            Принесли мне ноутбук для чистки от пыли, так как он перегревался.

            Разборка планшета irbis TX68 и замена тачскрина

            Попался мне в руки планшет у которого был разбит экран и была необходимость его заменить.

            Как снять клавиатуру с ноутбука ASUS K52J

            Снимаем клавиатуру с ноутбука, чтобы её заменить. Если нет денег на замену клавиатуры, то её можно просто отключить и купить стандартную USB клавиатуру, обойдётся она раз в 5 дешевле.

            Разборка и чистка ноутбука HP G6 2254sr

            Недавно я уже разбирал ноутбук HP G6, но там была модель по старше, этот же ноутбук более новее и его разборка отличается от предыдущей моделей, особенно интересно здесь снимается клавиатура.

            Разборка ноутбука Asus Eee pc1011cx и замена винчестера

            Операционная система в этом ноутбуке перестала загружаться, вместо этого срабатывала встроенная в ОС windows 7 диагностика по восстановлению системы. В итоге оказалось, что на винчестере много битых секторов (бэд блоков), поэтому пришлось заменить винчестер.

            Разборка и чистка ноутбука Acer aspire 3690

            Ноутбуку порядка 9 лет и его, по словам владелицы, ни разу не разбирали, чтобы почистить или заменить термопасту.

            Разборка и чистка ноутбука Samsung R540

            Цель разборки ноутбука была одна — почистить его от пыли, так как он грелся.

            Разборка и чистка ноутбука SONY vaio VPCEH2J1R (PCG-71812V)

            В этот раз ко мне в руки попал ноутбук SONY VAIO, если я не ошибаюсь, то первый опыт по разборке ноутбуков у меня был как раз связан с sony vaio (хотя может это был и второй ноутбук), правда другой модели. Ну ладно, ближе к делу.

            Разборка видеокарты XFX GeForce GTX 260

            Появилась необходимость почистить видеокарту от пыли, но как оказалось не совсем просто было её разобрать, по этому было решено написать статью о разборке видеокарты фирмы XFX модели GeForce 260 GTX.

            Append — Процедура Delphi

            Making sure you have your e111 for your
            holiday is absolutely essential

            A new book on thinking . now on Amazon

            Delphi Basics
            Delphi Basics supplies reference up to release XE1
            This web site provides help and reference for the fundamentals of the Delphi© language. It gives an introduction to the Delphi Object Oriented Language for newcomers, and provides a ready reference for experienced programmers.

            It limits itself to a broad base of the language in order to keep things simple and manageable. The approach taken is to present the most useable basics of Delphi Programming in as accessible a manner as possible.

            Feel free to contact the author about any aspects of the site. Every effort will be made to respond to all emails received.

            New : An independent review of «Learn to program in Pascal», an online course for those starting out on Delphi programming.

            Currently at a 60% discount

            Learn Javascript course

            Currently at a 70% discount

            Delphi Basics as a downloadable Windows program Download the Windows program : now only Ј5

            A complete version of the web site has been converted into a Windows executable.

            It looks and behaves like the site, but with the added benefits of :

            • No adverts
            • Search facility for finding Run Time Library entries and .Net Methods.
            • Fast access to 1,000+ pages of tutorial/reference pages — the full site and more
            • System.Drawing.Graphics .Net >Download the Windows program : now only Ј5

            Learn Delphi TV
            Learn Delphi with easy tutorial videos.

            DelphiComponent.ru — бесплатно видеоуроки по Delphi, статьи, исходники

            Файлы в Delphi: Открытие файла для вывода.

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

            Возможны следующие режимы открытия файла для записи в него данных:

            • перезапись (запись нового файла поверх существующего или создание нового файла);
            • добавление в существующий файл.

            Для того чтобы открыть файл в режиме создания нового файла или замены существующего, необходимо вызвать процедуру Rewrite (f], где f — файловая переменная типа textFiie.

            Для того чтобы открыть файл в режиме добавления к уже существующим данным, находящимся в этом файле, нужно вызвать процедуру Append ( f ) , где f — файловая переменная типа TextFiie.

            На рис. 7.1 приведено диалоговое окно программы, которая выполняет запись или добавление в текстовый файл.

            В листинге 7.1 приведена процедура, которая запускается нажатием командной кнопки Записать. Она открывает файл в режиме создания нового или замещения существующего файла и записывает текст, находящийся в поле компонента memo1.

            Имя файла нужно ввести во время работы в поле Edit1. Можно задать предопределенное имя файла во время разработки формы приложения. Для этого надо присвоить значение, например test.txt, свойству Editl. Text.

            Листинг 7.1. Создание нового или замещение существующего файла

            Файловые функции DELPHI. Файловые операции средствами ShellApi.

            В Delphi существует понятие — подпрограммы управления файлами ( category File management routines). Процедуры и функции входящие в эту категорию находятся в модулях System, SysUtils (каталог Source\Rtl\Sys) и FileCtrl (каталог Source\Vcl). Модуль FileCtrl содержит только две функции из категории подпрограмм управления файлами — это DirectoryExists и ForceDirectories. Местонахождение остальных процедур и функций определяется следующим образом. Если в подпрограмме используется файловая переменная, то она входит в модуль System. Если дескриптор или имя файла в виде строки, то в модуль SysUtils. Правда есть исключения (интуитивно понятные) ChDir входит в System. Также в System входят MkDir, RmDir из категории ввода/вывода ( I/O routines). Надо отметить, что все подпрограммы, отнесенные к категориям ввода/вывода и текстовых файлов ( Text file routines) находятся в модуле System (исключая процедуру AssignPrn входящую в модуль Printers каталог Source\Vcl). Вот список подпрограмм отсортирован по категориям и по алфавиту.

            File management routines — подпрограммы управления файлами

            S ystem procedure AssignFile(var F; FileName: string); Связывает файловую переменную с именем файла
            System procedure ChDir(S: string); Изменяет текущий каталог
            System procedure CloseFile(var F); Закрывает файл по файловой переменной
            SysUtils function CreateDir(const Dir: string): Boolean; Создает новый каталог
            SysUtils function DeleteFile(const FileName: string): Boolean; Удаляет файл
            FileCtrl function DirectoryExists(Name: string): Boolean; Проверяет наличие каталога
            SysUtils function DiskFree(Drive: Byte): Int64; Определяет свободное пространство на диске
            SysUtils function DiskSize(Drive: Byte): Int64; Определяет полный размер диска
            SysUtils function FileAge(const FileName: string): Integer; Определяет время последнего обновления
            SysUtils procedure FileClose(Handle: Integer); Закрывает файл по дескриптору
            SysUtils function FileDateToDateTime(FileDate: Integer): TDateTime; Преобразует DOS-дату в Delphi-дату
            SysUtils function FileExists(const FileName: string): Boolean; Проверяет наличие файла
            SysUtils function FileGetAttr(const FileName: string): Integer; Определяет атрибуты файла
            SysUtils function FileGetDate(Handle: Integer): Integer; Определяет время последнего обновления
            SysUtils function FileOpen(const FileName: string; Mode: LongWord): Integer; Открывает существующий файл
            SysUtils function FileRead(Handle: Integer; var Buffer; Count: Integer): Integer; Читает из файла
            SysUtils function FileSearch(const Name, DirList: string): string; Ищет файл в списке каталогов
            SysUtils function FileSeek(Handle, Offset, Origin: Integer): Integer; Меняет позицию указателя
            SysUtils function FileSetAttr(const FileName: string; Attr: Integer): Integer; Устанавливает атрибуты файла
            SysUtils function FileSetDate(Handle: Integer; Age: Integer): Integer; Устанавливает время последнего обновления
            SysUtils function FileWrite(Handle: Integer; const Buffer; Count: Integer): Integer; Записывает в файл
            SysUtils procedure FindClose(var F: TSearchRec); Прекращает поиск файлов
            SysUtils function FindFirst(const Path: string; Attr: Integer; var F: TSearchRec): Integer; Начинает поиск файлов
            SysUtils function FindNext(var F: TSearchRec): Integer; Продолжает поиск файлов
            FileCtrl function ForceDirectories(Dir: string): Boolean; Создает все каталоги пути
            SysUtils function GetCurrentDir: string; Определяет текущий каталог
            System procedure GetDir(D: Byte; var S: string); Определяет текущий каталог
            SysUtils function RemoveDir(const Dir: string): Boolean; Удаляет каталог
            SysUtils function RenameFile(const OldName, NewName: string): Boolean; Переименовывает файл
            SysUtils function SetCurrentDir(const Dir: string): Boolean; Устанавливает текущий каталог

            I/O routines — подпрограммы ввода/вывода

            Модуль Подпрограмма
            System procedure Append(var F: Text); Добавляет текст в конец файла
            System procedure BlockRead(var F: File; var Buf; Count: Integer [; var AmtTransferred: Integer]); Читает блок из файла
            System procedure BlockWrite(var f: File; var Buf; Count: Integer [; var AmtTransferred: Integer]); Записывает блок в файл
            System function Eof(var F): Boolean; Определяет конец файла
            System function FilePos(var F): Longint; Определяет позицию указателя
            System function FileSize(var F): Integer; Определяет размер файла
            System function IOResult: Integer; Определяет ошибки предыдущего ввода/вывода
            System procedure MkDir(S: string); Создает каталог
            System procedure Rename(var F; Newname:string); Переименовывает файл
            System procedure Reset(var F [: File; RecSize: Word ] ); Открывает файл
            System procedure Rewrite(var F: File [; Recsize: Word ] ); Создает и открывает новый файл
            System procedure RmDir(S: string); Удаляет каталог
            System procedure Seek(var F; N: Longint); Устанавливает позицию указателя
            System procedure Truncate(var F); Усекает файл до текущей позиции указателя

            Text file routines — подпрограммы текстовых файлов

            Модуль Подпрограмма
            Printers procedure AssignPrn(var F: Text); Связывает файловую переменную с принтером
            System function Eoln [(var F: Text) ]: Boolean; Определяет конец строки
            System procedure Erase(var F); Удаляет файл
            System procedure Flush(var F: Text); Переписывает данные в файл из его буфера
            System procedure Read(F , V1 [, V2. Vn ] ); Читает из файла
            System procedure Readln([ var F: Text; ] V1 [, V2, . Vn ]); Читает из файла до конца строки
            System function SeekEof [ (var F: Text) ]: Boolean; Определяет конец файла
            System function SeekEoln [ (var F: Text) ]: Boolean; Определяет конец строки
            System procedure SetTextBuf(var F: Text; var Buf [ ; Size: Integer] ); Устанавливает новый буфер
            System procedure Write(F, V1 [, V2. Vn ] ); Записывает в файл
            System procedure Writeln([ var F: Text; ] V1 [, V2, . Vn ] ); Записывает в файл с концом строки

            Проверяем наличие файла и записываем его
            type
            TFileData=record
            Name:String[10];
            ExtDat:Extended;
            end;
            var
            Cals: File of TFileData;
            CalsData: TFileData;

            procedure NAME;
            //Описание процедуры
            var p: Real;
            u: Byte;
            begin
            Road:=’<файл>.dat’;
            Dest:=’<каталог>‘+Road;
            try
            AssignFile(Cals,Dest);
            // Если файл существует открываем на чтение, иначе создаем новый
            If FileExists(Cals) then Reset(cals) else Rewrite(cals);
            // установим позицию чтения в конец файла
            seek (cals,filesize(cals));
            CalsData.Name := ‘название параметра’;
            CalsData.ExtDat := <сами данные>;
            Write(Cals,CalsData);
            except
            on E: EInOutError do
            ShowMessage(‘При выполнении файловой операции возникла ошибка’+
            ‘ № ‘+ IntToStr(E. ErrorCode)+’: ‘+SysErrorMessage(GetLastError));
            on E: EAccessViolation do
            ShowMessage(‘Ошибка!: ‘+SysErrorMessage(GetLastError));
            end;
            CloseFile(cals); //Независимо от того что произошло выше закрываем открытый файл
            end;

            Перепишем файл a.dat в файл b.dat, удалив признаки конца файла:

            Proedure MyWrite;
            var
            f1,f2 :file of Byte;
            a :Byte;
            i :Longint;
            begin
            <$I->
            AssignFile(f1, ‘a.dat’);
            AssignFile(f2, ‘b.dat’);
            Reset(f1);
            Rewrite(f2);
            for i := 1 to FileSize(f1) do
            begin
            Read(f1, a);
            if a <> 26 then Write(f2, a);
            end;
            CloseFile(f1);
            CloseFile(f2);
            end.

            Файл записей. Пишем и читаем любую:

            Procedure MyBook;
            type TR=Record
            Name:string[100];
            Age:Byte;
            Income:Real;
            end;
            var f:file of TR;
            r:TR;
            begin
            //assign file
            assignFile(f, ‘MyFileName’);
            //open file
            if FileExists(‘MyFileName’) then
            reset(f)
            else
            rewrite(f);
            //чтение 10й записи
            seek(f,10);
            read(f,r);
            //запись 20й записи
            seek(f, 20);
            write(f,r);
            closefile(f);
            end;

            Файловые операции средствами ShellAPI.


            Автор: Владимир Татарчевский

            Рассмотрим применение функции SHFileOperation.
            function SHFileOperation(const lpFileOp: TSHFileOpStruct): Integer; stdcall;
            Функция позволяет производить копирование, перемещение, переименование и удаление (в том числе и в Recycle Bin) объектов файловой системы.
            Функция возвращает 0, если операция выполнена успешно, и ненулевое значение в противном случае.

            Функция имеет единственный аргумент — структуру типа TSHFileOpStruct, в которой и передаются все необходимые данные. Эта структура выглядит следующим образом:

            _SHFILEOPSTRUCTA = packed record
            Wnd: HWND;
            wFunc: UINT;
            pFrom: PAnsiChar;
            pTo: PAnsiChar;
            fFlags: FILEOP_FLAGS;
            fAnyOperationsAborted: BOOL;
            hNameMappings: Pointer;
            lpszProgressTitle: PAnsiChar; < используется только при установленном флаге FOF_SIMPLEPROGRESS >
            end;

            Поля структуры имеют следующее назначение:
            hwnd Хэндл окна, на которое будут выводиться диалоговые окна о ходе операции.
            wFunc Требуемая операция. Может принимать одно из значений:

            FO_COPY Копирует файлы, указанные в pFrom в папку, указанную в pTo.
            FO_DELETE Удаляет файлы, указанные pFrom (pTo игнорируется).
            FO_MOVE Перемещает файлы, указанные в pFrom в папку, указанную в pTo.
            FO_RENAME Переименовывает файлы, указанные в pFrom.
            pFrom — указатель на буфер, содержащий пути к одному или нескольким файлам. Если файлов несколько, между путями ставится нулевой байт. Список должен
            заканчиваться двумя нулевыми байтами.

            pTo — аналогично pFrom, но содержит путь к директории — адресату, в которую производится копирование или перемещение файлов. Также может
            содержать несколько путей. При этом нужно установить флаг FOF_MULTIDESTFILES.

            fFlags — управляющие флаги.
            FOF_ALLOWUNDO Если возможно, сохраняет информацию для возможности UnDo.
            FOF_CONFIRMMOUSE Не реализовано.
            FOF_FILESONLY Если в поле pFrom установлено *.*, то операция будет производиться только с файлами.
            FOF_MULTIDESTFILES Указывает, что для каждого исходного файла в поле pFrom указана своя директория — адресат.
            FOF_NOCONFIRMATION Отвечает «yes to all» на все запросы в ходе опеации.
            FOF_NOCONFIRMMKDIR Не подтверждает создание нового каталога, если операция требует, чтобы он был создан.
            FOF_RENAMEONCOLLISION В случае, если уже существует файл с данным именем, создается файл с именем «Copy #N of. »
            FOF_SILENT Не показывать диалог с индикатором прогресса.
            FOF_SIMPLEPROGRESS Показывать диалог с индикатором прогресса, но не показывать имен файлов.
            FOF_WANTMAPPINGHANDLE Вносит hNameMappings элемент. Дескриптор должен быть освобожден функцией SHFreeNameMappings.
            fAnyOperationsAborted
            Принимает значение TRUE если пользователь прервал любую файловую операцию до ее завершения и FALSE в ином случае.

            hNameMappings — дескриптор объекта отображения имени файла, который содержит массив структур SHNAMEMAPPING. Каждая структура содержит старые и новые имена пути для каждого файла, который перемещался, был скопирован, или переименован. Этот элемент используется только, если установлен флаг FOF_WANTMAPPINGHANDLE.

            lpszProgressTitle — указатель на строку, используемую как заголовок для диалогового окна прогресса. Этот элемент используется только, если установлен флаг FOF_SIMPLEPROGRESS.

            Примечание. Если pFrom или pTo не указаны, берутся файлы из текущей директории. Текущую директорию можно установить с помощью функции
            SetCurrentDirectory и получить функцией GetCurrentDirectory.

            Добавьте в секцию uses модуль ShellAPI, в котором определена функция SHFileOperation.

            procedure TForm1.Button1Click(Sender: TObject);
            var
            SHFileOpStruct : TSHFileOpStruct;
            From : array [0..255] of Char;
            begin
            SetCurrentDirectory( PChar( ‘C:\’ ) );
            From := ‘Test1.tst’ + #0 + ‘Test2.tst’ + #0 + #0;
            with SHFileOpStruct do
            begin
            Wnd := Handle;
            wFunc := FO_DELETE;
            pFrom := @From;
            pTo := nil;
            fFlags := 0;
            fAnyOperationsAborted := False;
            hNameMappings := nil;
            lpszProgressTitle := nil;
            end;
            SHFileOperation( SHFileOpStruct );
            end;

            Ни один из флагов не установлен. Если вы хотите не просто удалить файлы, а переместить их в корзину, должен быть
            установлен флаг FOF_ALLOWUNDO.

            Напишем функцию, создающую из массива строк буфер для передачи его в качестве параметра pFrom. После
            каждой строки в буфер вставляется нулевой байт, в конце списка — два нулевых байта.
            type TBuffer = array of Char;

            procedure CreateBuffer( Names : array of string; var P : TBuffer );
            var I, J, L : Integer;
            begin
            for I := Low( Names ) to High( Names ) do
            begin
            L := Length( P );
            SetLength( P, L + Length( Names[ I ] ) + 1 );
            for J := 0 to Length( Names[ I ] ) — 1 do
            P[ L + J ] := Names[ I, J + 1 ];
            P[ L + J ] := #0;
            end;
            SetLength( P, Length( P ) + 1 );
            P[ Length( P ) ] := #0;
            end;

            Функция, удаляющая файлы, переданные ей в списке Names. Параметр ToRecycle определяет, будут ли файлы перемещены в корзину или удалены. Функция возвращает 0, если операция выполнена успешно, и ненулевое значение, если функции переданы имена несуществующих файлов.
            function DeleteFiles( Handle : HWnd; Names : array of string; ToRecycle : Boolean ) : Integer;
            var
            SHFileOpStruct : TSHFileOpStruct;
            Src : TBuffer;
            begin
            CreateBuffer( Names, Src );
            with SHFileOpStruct do
            begin
            Wnd := Handle;
            wFunc := FO_DELETE;
            pFrom := Pointer( Src );
            pTo := nil;
            fFlags := 0;
            if ToRecycle then fFlags := FOF_ALLOWUNDO;
            fAnyOperationsAborted := False;
            hNameMappings := nil;
            lpszProgressTitle := nil;
            end;
            Result := SHFileOperation( SHFileOpStruct );
            Src := nil;
            end;

            Освобождаем буфер Src простым присваиванием значения nil. Потери памяти при этом не происходит, происходит корректное уничтожение динамического массива.

            Проверяем :
            procedure TForm1.Button1Click(Sender: TObject);
            begin
            DeleteFiles( Handle, [ ‘C:\Test1’, ‘C:\Test2’ ], True );
            end;

            Файлы ‘Test1’ и ‘Test2’ удаляются совсем, без помещения в корзину, несмотря на установленный флаг FOF_ALLOWUNDO. При использовании функции SHFileOperation используйте полные пути, когда это возможно.

            Копирование и перемещение.

            Функция перемещает файлы указанные в списке Src в директорию Dest. Параметр Move определяет, будут ли файлы перемещаться или копироваться. Параметр AutoRename указывает, переименовывать ли файлы в случае конфликта имен.
            function CopyFiles( Handle : Hwnd; Src : array of string; Dest : string; Move : Boolean; AutoRename : Boolean ) : Integer;
            var
            SHFileOpStruct : TSHFileOpStruct;
            SrcBuf : TBuffer;
            begin
            CreateBuffer( Src, SrcBuf );
            with SHFileOpStruct do
            begin
            Wnd := Handle;
            wFunc := FO_COPY;
            if Move then wFunc := FO_MOVE;
            pFrom := Pointer( SrcBuf );
            pTo := PChar( Dest );
            fFlags := 0;
            if AutoRename then fFlags := FOF_RENAMEONCOLLISION;
            fAnyOperationsAborted := False;
            hNameMappings := nil;
            lpszProgressTitle := nil;
            end;
            Result := SHFileOperation( SHFileOpStruct );
            SrcBuf := nil;
            end;

            Выполнение:
            procedure TForm1.Button1Click(Sender: TObject);
            begin
            CopyFiles( Handle, [ ‘C:\Test1’, ‘C:\Test2’ ], ‘C:\Temp’, True, True );
            end;

            Переименование.
            function RenameFiles( Handle : HWnd; Src : string; New : string; AutoRename : Boolean ) : Integer;
            var SHFileOpStruct : TSHFileOpStruct;
            begin
            with SHFileOpStruct do
            begin
            Wnd := Handle;
            wFunc := FO_RENAME;
            pFrom := PChar( Src );
            pTo := PChar( New );
            fFlags := 0;
            if AutoRename then fFlags := FOF_RENAMEONCOLLISION;
            fAnyOperationsAborted := False;
            hNameMappings := nil;
            lpszProgressTitle := nil;
            end;
            Result := SHFileOperation( SHFileOpStruct );
            end;

            procedure TForm1.Button1Click(Sender: TObject);
            begin
            RenameFiles( Handle, ‘C:\Test1’ , ‘C:\Test3’ , False );
            end;

            Илон Маск рекомендует:  Sqlглава 5 о предложениях определения данных и оптимизации запросов
            Понравилась статья? Поделиться с друзьями:
            Кодинг, CSS и SQL
            Run Time Library Reference
            By first letter
            A B C D E F G H I J K L M N O P Q R S T U V W X Y Z $
            By Functionality
            Data types
            Integers, ordinals
            Floating point types
            String, char types
            Pointer types
            Other types

            Dates and Times
            Calculations
            Conversions from
            Conversions to
            Values
            Displaying
            By Unit
            System
            SysUtils
            StrUtils
            DateUtils
            FileCtrl
            ConvUtils
            StdConvs
            Math
            Classes
            Dialogs
            Types
            Variants
            By Category
            Compiler directives
            Directives
            Keywords
            Types
            Variables Constants
            Functions
            Procedures