8. Работа с компонентом ТТаblе
8.1.1. Получение информации об индексах ТБД
Компонент
TindexDefs содержит информацию обо всех индексах таблицы базы данных, объявленных в ней в текущий момент. У TTable имеется свойство property IndexDefs: TIndexDefs; которое содержит ссылку на объект класса TIndexDefs. Поэтому для каждого компонента TTable всегда можно получить информацию об индексах данной ТБД через свойства и методы TIndexDefs. Рассмотрим эти методы и свойства.Свойства:
property Count: Integer; -
возвращает число индексов;property Items[Index: Integer|: TIndexDef; -
коллекция объектов типа TIndexDef, каждый из которых содержит информацию о конкретном индексе. Index должен принадлежать диапазону [0..Count-1].Экземпляр типа
TIndexDef имеет следующие свойства (информацию о методах объекта данного класса можно получить в системе помощи Delphi):property Name: string; -
возвращает имя индекса;Пример.
Записать в ListBox1 имена всех индексов ТБД, ассоциированной с Table 1:ListBoxl.Clear;
Table1.IndexDefs.Update;
FOR i := 0 TO Tablel.IndexDefs.Count - 1 do ListBoxl.Items.Add(Tablel.IndexDefs[i].Name) ;
Перед считыванием значения свойства
Name необходимо выполнить метод Tablel IndexDefs.Update (см. ниже) для обновления информации обо всех имеющихся индексах.Отметим, что для Paradox-таблиц имя первичного индекса не выдается; вместо этого выдается пустая строка, поскольку первичный индекс для Paradox-таблиц не имеет имени (рис. 8.1):
Пустую строку имени первичного индекса можно заменять, например, словом '
Primary':var i : Integer;
Ima : String;
begin
ListBox1:Clear;
Table1.IndexDefs.Update;
FOR i := 0 TO Table1.IndexDefs.Count - 1 do begin
Ima := Table1.IndexDefs[i].Name;
IF Ima = '' THEN Ima := 'Primary';
ListBoxl.Items.Add(Ima) ;
END; //FOR
end;
property Fields: string, -
возвращает список полей, по которым построен данный индекс Поля в строке разделены точкой с запятой Именно в таком виде строку можно указывать в свойстве IndexFie!dNames (компонент TTable)property Options: TIndexOptions -
возвращает характеристики индекса в виде множества TIndexOptions = set of (ixPrimary ixUnique, ixDescending, ixNonMaintatned, ixCaselnsensitne). Таким образом, возвращаемое значение может состоять максимум из 6 элементовПример Показать в
Edit1 Text список полей индекса, чье имя является текущим в ListBox1 (результат на рис 82)// обработчик события выбора элемента в ListBoxl:
procedure TForm1.ListBox1Click(Sender: TObject);
var Teklndex : Integer;
begin
Teklndex := ListBox1.Itemlndex; {
индекс текущего элемента в ListBoxl}Edit1.Text := Tablel.IndexDefs[Teklndex].Fields;
Label2.Caption := '
Поля индекса ' + ListBoxl.Items[Teklndex] + ':' ;end;
Методы
procedure Add(const Name, Fields: string; Options: TIndexOptions);
Метод
Add создает новый объект TIndexDef и помещает его в коллекцию TIndexDefs Items На момент создания должны быть определены такие параметры нового объекта TIndexDef, как Name, Fields, Options Их назначение совпадает с назначением аналогичных свойств компонента TindexDef . Этот метод в основном используется при создании новых таблицprocedure Update;
обновляет элементы коллекции TIndexDefs Items текущей информацией из НД, причем обновление может производиться и без открытия набора данныхprocedure Clear;
очищает элементы коллекции TIndexDefs Itemsfunction Index0f(const Name: string): Integer; возвращает из коллекции TIndexDefs Items индекс элемента, у которого свойство Name совпадает с параметром Name данного метода
function FindIndexForFields(const Fields: string): TIndexDef;
Отыскивает индекс по списку его полей, которые содержатся в строке Fields Возвращает указатель на объект TIndexDefв коллекции TIndexDefs Items, в котором содержимое свойства Fields совпадает с параметром Fields индексаМетод
GetIndexNamesprocedure GetIndexNames(List: Tstrings) -
возвращает в параметре List список имен индексов Заметим, что для Paradox-таблиц имя главного индекса не выдаетсяПример. Выдать список индексов ТБД, ассоциированной с
Table1 (рис.8.3):ListBox2.Clear;
Tablel.GetIndexNames(ListBox2.Items) ;
Свойства
IndexFieldCount и IndexFieldsСвойство
IndexFieldCount: Integer; - возвращает число полей в текущем индексе НД. Номер первого поля 0.Свойство
IndexFields[Index: Integer]: TField; содержит набор полей текущего индекса; обращение IndexFields [Index] возвращает информацию о поле, определенном в текущем индексе под номером Index (нумерация полей начинается с 0). Для n определенных полей Index лежит в диапазоне 0.. (n-1). Например, чтобы записать в ListBox1 имена всех полей текущего индекса, можно использовать такой фрагмент программы:var i : Integer;
ListBox1.Clear;
For i:= 0 TO Tablel.IndexFieldCount - 1 do ListBoxl.Items.Add(Tablel.IndexFields[I].FieldName) ;
Поскольку при обращении к
Index Fields [Index] возвращается указатель на объект типа TField, для данного поля текущего индекса можно пользоваться всеми свойствами и методами типа TField.8.1.2. Установка текущего индекса ТТаЫе
То, какой индекс является текущим для данного НД (компонент
TTable), в ряде случаев имеет важное значение.Во-первых, текущий индекс определяет поля, по которым будет отсортирован данный НД. Это важно как с точки зрения доступа к данным, так и с точки зрения их визуализации: представление данных должно быть максимально информативным. Плюс к этому пользователь должен быстро находить нужную ему информацию и видеть группировки записей внутри НД.
Во-вторых, многие методы и свойства
TTable работают напрямую с текущим индексом. Это, например, метод SetRange для фильтрации записей в TTable и связанные с ним; методы для поиска записи, удовлетворяющей условию - FindKey, FindNearest и другие.Для указания индекса, по которому будет производиться сортировка в НД, связанном с данным компонентом
TTable, имеются два взаимоисключающих способа.1) Путем занесения имени индекса в свойство
property IndexName: string; например:Tablel.IndexName := 'INDEX_BY_FIO';
Пример.
Для описанного выше примера заполнения ListBox именами доступных индексов сделаем для Table текущим индекс, имя которого является текущим в ListBoxl:Tablel.IndexName := ListBoxl.Items[ListBox1.Itemlndex];
В силу того, что имя главного (первичного) индекса для Paradox-таблиц не выводится, данный фрагмент кода может сделать текущим только вторичный индекс.
2) Путем занесения списка индексных полей в свойство
property IndexFieldNames: string;В случае указания нескольких полей их имена разделяются точкой с запятой. Пример
:Table1.IndexFieldNames := 'FIO; Doljnost';
Индекс с указанными полями должен физически существовать. Попытка присвоить свойству
IndexFieldNames список полей, не являющихся полями какого-либо индекса, вызовет исключительную ситуацию. Данное свойство особенно ценно для Paradox-таблиц, т.к. позволяет сделать текущим главный (первичный) индекс путем перечисления входящих в него полей.8.1.3. Добавление нового индекса
Добавление нового индекса происходит в режиме исключительного доступа к ТБД (свойство
Exclusive = True) и осуществляется методом procedure Addlndex(const Name, Fields: string; Options: TIndexOptions); где параметр Name определяет имя индекса, а параметр Fields - список индексных полей. В случае нескольких полей их имена должны разделяться точкой с запятой. Должны указываться только поля, объявленные в структуре ТБД. В противном случае будет возбуждена исключительная ситуация и создание индекса будет блокировано. Параметр Options является множеством, которое содержит значения, определяющие свойства индекса:TIndexOptions = set of (ixPrimary, ixUnique, ixDescending,ixExpression, ixCaseInsensitive) ;
ixPrimary -
определяет первичный индекс;ixUnique -
определяет уникальный индекс;ixDescending -
определяет индекс, построенный по убыванию значений ключевых полей (по умолчанию строится индекс по возрастанию значений ключевых полей);ixCaseInsensitive -
определяет индекс, нечувствительный к высоте букв. Так, например, если для индекса установлен этот режим, значения "КАРТОФЕЛЬ", "Картофель" и "картофель" будут сочтены идентичными.Например, определить новый индекс с именем
WWW, построенный по полям 'NN; DatePrih', нечувствительный к высоте букв:Table1.Close;
Table1.Exclusive := True;
Table1.Open;
Table1.Addlndex('WWW, 'NN; DatePrih' , [ixCaseInsensitive]);
Table1.Close;
Table1.Exclusive := False;
Table1.Open;
8.1.4. Удаление существующего индекса
Удаление существующего индекса происходит в режиме исключительного доступа к ТБД (свойство
Exclusive = True) и осуществляется методом procedure Deletelndex(const Name: string); где параметр Name определяет имя удаляемого индекса. При попытке удаления несуществующего индекса возбуждается исключительная ситуация и удаление блокируется.Пример. Удалить индекс с именем
WWW:Table1.Deletelndex('WWW) ;
8.2. Исключительный доступ к НД
8.2.1. Установка приоритетного доступа при многопользовательском режиме
Свойство
Exclusive дает пользователю исключительный доступ к НД (значение True). Это означает, что никто иной не только не может вносить изменения в НД, но вообще не имеет доступа к НД. Установить исключительный доступ можно, лишь когда ни один пользователь не имеет доступа к НД и тот не открыт.Для SQL-таблиц исключительный доступ может означать запрет изменения НД другими пользователями. Однако последние могут просматривать содержимое НД.
ЗАМЕЧАНИЕ.
Delphi тоже считается в данном случае пользователем. Поэтому, если в программном коде делается попытка получения прав исключительного доступа к ТБД Table1.Exclusive := True; и программа запущена из Delphi, попытка получения исключительных прав будет блокирована, поскольку на Вашей машине имеется два пользователя, осуществляющих доступ к этой ТБД: выполняющееся приложение и Delphi.То же произойдет, если на момент попытки получения исключительных прав из работающего приложения в
Database Desktop будет открыта данная ТБД.Метод
procedure EmptyTable; уничтожает все записи в ТБД, связанной с данным НД. После этой операции ТБД будет пустой. Метод применим только к закрытым НД, и только для случая исключительного доступа (см. выше). В противном случае возбуждается исключение и очистка ТБД блокируется.Пример. Очистить от записей ТБД, ассоциированную с НД
Tablel:Table1.Close;
Table1.Exclusive := True;
Table1.EmptyTable;
Table1.Exclusive := False;
Table1.Open;
Еще раз напомню, что таблица не будет очищена, если этот фрагмент выполняется из среды
Delphi.procedure DeleteTable;
физически удаляет ТБД. Метод применим только к закрытым НД.procedure CreateTable;
создает новую пустую таблицу. Перед созданием таблицы для данного компонента TTable нужно указать:• имя БД - в свойстве
DatabaseName;•
имя таблицы - в свойстве TableName;•
тип таблицы - в свойстве ТаblеТуре. Возможно указание следующих типов:ttASCII
: многоколончатый текстовый файл, используемый для чтения как ТБД;ttDBase
: таблица dBASE;ttParadox:
таблица Paradox;описания полей - в свойстве
FieldDefs;описания индексов - в свойстве
IndexDefs.Для добавления описаний полей в свойство
FieldDefs оно сначала очищается, а затем для каждого поля информация в FieldDefs заносится методомprocedure Add(const Name: string; DataType: TFieldType; Size: Word; Required: Boolean);
где: параметр
Name определяет имя поля; параметр DataType определяет тип поля:TFieldType = (ftUnknown, ftString, ftSmallint, ftlnteger, ftWord, ftBoolean, ftFloat, ftCurrency, ftBCD, ftDate, ftTime, ftDateTime, ftBytes, ftVarBytes, ftAutoInc, ftBlob, ftMemo, ftGraphic, ftFmtMemo, ftParadoxOle, ftDBaseOle, ftTypedBinary) ;
параметр
Size определяет размер поля (если его указание необходимо) или 0 (если тип поля подразумевает его размер, например ftlnteger);параметр
Required определяет, должно ли поле в обязательном порядке содержать значение.Для добавления описаний индексов в свойство
IndexDefs оно сначала очищается, а затем для каждого поля информация в IndexDefs заносится методом procedure Add(const Name, Fields: string; Options: TIndexOptions); где параметр Name определяет название индекса; параметр Fields определяет список индексных полей. В случае нескольких полей их имена разделяются точкой с запятой. Могут быть указаны только те поля, описания которых перед этим добавлены в свойство FieldDefs; параметр Options определяет свойства индекса (см. описание метода Addlndex).Пример.
Создать новую ТБД с именем 'NewT' в БД 'book1', состоящую из двух полей - символьного поля FIO и целочисленного Oklad, с одним первичным индексом по полю FIO:WITH Table1 do begin
// укажем БД, имя новой ТБД, тип ТБД
Active := False;
DatabaseName := 'book';
TableName := Editl.Text;
TableType := ttParadox;
// опишем поля создаваемой ТБД
WITH FieldDefs do begin
Clear;
Add('FIO', ftString, 30, False);
Add('Oklad', ftlnteger, 0, False);
END;//with FieldDefs
// опишем индексы создаваемой ТБД
WITH IndexDefs do begin
Clear;
Add('Index_FIO', 'FIO', [ixPrimary, ixUnique]) ;
END;//with IndexDefs
CreateTable; //
создадим ТБДActive := True; //
откроем ееEND;//WITH Tablel
Заметим, что ТБД может быть создана также и при помощи SQL-оператора
CREA ТЕ TABLE. Для "персональных" СУБД типа Paradox и dBase это альтернативный методу CreateTable способ; для "промышленных" СУБД это единственный способ динамического создания ТБД из работающего приложения, реализованного с помощью Delphi.Для поиска записей в НД в компоненте
TTable применяются следующие методы:function FindKey
([список значений]): Boolean - ищет запись, точно удовлетворяющую условиям в списке значений; существует также дублирующий его метод GoToKey;procedure FindNearest([список значений]) - ищет запись, приблизительно Удовлетворяющую условиям в списке значений; существует также дублирующий его метод GoToNearest;
Помимо этого, у
TTable имеются методы, унаследованные от родительского класса TDBDataSet:function Locate
([список полей], [список значений]) : Boolean - устанавливает указатель на запись, у которой список полей точно или неточно содержит значения из списка значении.function Lookup
([список поисковых полей], [список значений], [список результирующих полей]) : Variant; - возвращает значения полей из списка результирующих полей для записи в НД, у которой поля из списка поисковых полей точно содержат значения из списка значений. Указатель текущей записи не меняется.Эти методы рассмотрены в разделе, описывающем общие свойства и методы наборов данных.
8.3.2. Установка значений для поиска
Поиск может осуществляться только по индексным полям. Состав полей, используемых для идентификации нужной записи при поиске в НД, определяется текущим индексом. Поэтому, если поля, по которым необходимо осуществить поиск, не входят в индекс, являющийся текущим в данный момент, в качестве текущего индекса нужно установить индекс, построенный по полям, по значениям которых и планируется осуществить поиск. Напомним, что для установки текущего индекса используют свойства
IndexFieldNames или IndexName.Для индексов, в состав которых входит более одного поля, должны указываться значения всех полей, входящих в индекс, или значения полей старших уровней вложенности. Более подробно см. ниже, подраздел "Поиск по части текущего индекса ".
Для точного поиска (поиска на точное соответствие) применяется метод
function FindKey( [список значений]): Boolean;При поиске на точное соответствие предпринимается попытка отыскать в НД запись, у которой индексные поля соответствуют значениям, указанным в списке значений. Если такая запись найдена, метод
FindKey возвращает True и указатель текущей записи в НД (курсор НД) устанавливается на эту запись, т.е. она делается текущей. Если найдена группа записей, отвечающая условию, текущей становится логически первая из них. Если запись не найдена, курсор НД не перемещается и метод FindKey возвращает False. Пример. Пусть имеется НД с полями GrNum (номер группы), NN (номенклатурный номер товара), Tovar (наименование товара):Предположим, что поисковое значение GrNum вводится в
Edit1, a NN - в Edit2 (рис.8.4). Тогда обработчик нажатия клавиши поиска FindButton может выглядеть так (для простоты в обработчике не контролируется правильность ввода в компонентах Edit1, Edit2):procedure TForm1.FindButtonClick(Sender: T0b]ect);
var GrTmp, NNTmp : Longint;
begin
GrTmp := StrToInt(Edit1.Text);
NNTmp := StrToInt(Edit2.Text);
//
поиск записиIF not Table1.FindKey([GrTmp,NNTmp]) then ShowMessage ('
Нет товара с такой группой и номером!');end;
Пусть в Edit1 введено "12" и в Edit2 - "4". Тогда указатель переместится на запись, у которой значение GrNum равно 12 и значение поля NN равно 4 (рис. 8.5):Пусть в Editi введено "12" и в Edit2 введено "О". Тогда указатель не переместится с текущей записи и будет выдано сообщение 'Нет товара с такой группой и номером!'.
Для неточного поиска в
Delphi имеется группа методов SetKey, EditKey, GotoKey, которые должны выполняться вместе и которые по функциональности аналогичны методу FindKey. Использование их является менее удобным. Сначала нужно перевести НД в состояние dsSetKey (методом SetKey или, если он уже применялся для данного индекса, EditKey), затем присвоить поисковые значения полям и выполнить метод GoToKey. После этого НД переходит в состояние dsBrowse. Результат выполнения аналогичен результату, возвращаемому методом FindKey. Например, кодIF not Table1.FindKey([GrTmp,NNTmp]) then ShowMessage
('Нет товара с такой группой и номером!');эквивалентен более громоздкому коду с применением
GotoKey.Table1.SetKey;
Table1GrNum.Value := GrTmp;
Table1NN.Value := NNTmp;
IF not Table1.GoToKey then
ShowMessage('Нет товара с такой группой и номером! ');
Неточный поиск (поиск на неточное, приблизительное соответствие) осуществляется методом
procedure FindNearest( [
список параметров]);При поиске на неточное соответствие предпринимается попытка отыскать в НД запись, у которой индексные поля соответствуют значениям, указанным в списке значений. Если такая запись найдена, указатель текущей записи в НД перемещается на нее или на следующую за ней запись, в зависимости от значения свойства
property KeyExclusive: Boolean. Если KeyExclusive = False (пo умолчанию), указатель текущей записи перемещается на нее. Если KeyExclusive = True, указатель текущей записи перемещается на следующую запись.Если запись не найдена, указатель текущей записи всегда перемещается на ближайшую запись с большим значением индекса.
В приводимых ниже примерах подразумевается
KeyExclusive = False.Предположим, что поисковое значение
GrNum вводится в Editi, а NN - в Edit2. Тогда обработчик нажатия клавиши поиска FindButton может выглядеть так:procedure TForm1.FindButtonClick(Sender: TObject) ;
var GrTmp, NNTmp : Longint;
begin
GrTmp := StrToInt(Edit1.Text);
NNTmp := StrToInt(Edit2.Text);
//
Поиск записиTable1.FindNearest([GrTmp,NNTmp]) ;
end;
Пусть в
Edit1 введено "12" и в Edit2 введено "4". Тогда указатель переместится на запись, у которой поле GrNum содержит 12 и поле NN содержит 4 (рис. 8.6):Пусть в
Edit 1 введено " 12" и в Edit2 введено "О". Тогда указатель переместится на запись с большим значением индекса. Наращение индекса ведется по внутреннему полю (если оно есть) и только затем - по полю более высокого приоритета. В нашем случае внутреннее поле в индексе - NN. В данном случае в НД имеется запись с тем же номером группы и большим номенклатурным номером (рис. 8.7):Однако если в Editi введено "50" и в Edit2 введено "О" (или что-либо другое, для данного состояния НД это неважно), записи с той же группой (50) и большим значением номенклатурного номера нет. Поэтому указатель записи перемещается на запись с большим номером группы (рис. 8.8).
Заметим, что теоретически возможно в условиях поиска опускать значение внешнего поля индекса (в нашем случае поля GrNum). Например, можно задать в
Edit1 "0" и в Edit2 - "1". Однако, вопреки ожиданиям, курсор НД не встанет на первую запись с номенклатурным номером 1 для первой попавшейся группы, а встанет на первую запись с группой, превышающей 0, т.е. на логически первую запись в НД при сортировке по полям GrNum, NN (рис. 8.9).Существует более громоздкая альтернатива методу
FindNearest -выполнение группы методов Set Key, EditKey, GoToNearest и заполнение полей поисковыми значениями (подробнее см. в конце описания метода FindKey). Например, выполнение методаTable1.FindNearest([GrTmp,NNTmp
]) ; может быть заменено эквивалентным по последствиям кодомTable1.SetKey;
Table1GrNum.Value := GrTmp;
Table1NN.Value := NNTmp;
Table1.GotoNearest;
ЗАМЕЧАНИЕ
. Если нужно осуществить поиск по индексу, отличному от текущего, необходимо:1. сохранить список текущих индексных полей в строковой переменной;
2. заменить список текущих индексных полей НД на необходимый;
3. осуществить поиск;
4. восстановить список текущих индексных полей НД из строковой переменной.
Пример. Пусть текущая сортировка в НД осуществляется по имени товара (т.е. в текущий момент
Tablel. IndexFieldNames = 'Tovar') и текущей является вторая логическая запись (рис. 8.10).Тогда после выполнения обработчика нажатия кнопки
FindButton, если в Editi введено "100" и в Edit2 введено "О":procedure TForm1.FindButtonClick(Sender: TObject) ;
var GrTmp, NNTmp : Longint;
OldIndexFieldNames : Strings;
begin
OldIndexFieldNames := Tablel.IndexFieldNames;
Tablel .IndexFieldNames := 'GrNunuNN';
{...}
Table1.FindNearest([GrTmp,NNTmp]) ;
Table1.IndexFieldNames := OldIndexFieldNames;
end;
указатель записи встанет на искомую запись (рис. 8.11).
Перед выполнением последней строки обработчика
Table1.IndexFieldNames := OldIndexFieldNames;
НД будет иметь вид, показанный на рис. 8.12.
Восстановление исходного индекса в последней строке приведет к изменению логического следования записей в НД, но текущая запись останется прежней. Причиной этого является правило: простое изменение сортировки в НД, если оно не сопровождалось изменением условий фильтрации записей в НД, не влечет изменения местоположения курсора НД.
8.3.5. Инкрементальный локатор
Под локатором будем понимать механизм поиска (точного или приблизительного) записей в НД с последующим позиционированием на них курсора компонента TTable. Для реализации локатора обычно применяется один или несколько компонентов TEdit для ввода условий поиска и кнопка TButton, обработчик события нажатия которой и реализует поиск.Описанные выше обработчики события нажатия кнопки
FindButton реализуют локаторы. Однако, вне рассмотрения остался еще один режим: по вводу каждого символа в TEdit переходить на запись, ближе всего лежащую к искомой. Чем больше введено символов, тем ближе курсор БД к искомой записи. Такой локатор называется инкрементальным.Пусть необходимо реализовать инкрементальный локатор для уточняющего поиска записи по названию товара. Пусть описанный выше НД отсортирован по индексному полю 'Tovar'. Пусть текущая запись в нем - логически первая. Тогда он имеет вид, показанный на рис. 8.13.
Ввод значения для поиска осуществляется в компонент
Edit3. Напишем обработчик для события OnChange, возникающего при любом изменении значения в Edit3:procedure TForm1.Edit3Change(Sender: T0b;ect) ;
begin
Table1.FindNearest([Edit3.Text]) ;
end;
Пусть нам нужно сделать текущей запись с наименованием товара "Комплект отверток". При использовании описываемого механизма инкрементального локатора необязательно вводить это название полностью. Курсор НД будет приближаться к искомой записи по мере ввода символов в Edit3.
Введем в Edit3 символ "К" (Edit3.
Text = 'К');Тогда
Tablel.FindNearest([Edit3.Text]) ;есть на самом
деле Table1.FindNearest(['К']) ;в результате курсор переместится на 1-ю запись, имеющую в поле Tovar значение, большее строки 'К' (рис. 8.14).
Введем в Edit3 следующий символ, "о" (Edit3.
Text = 'Ко').В результате курсор переместится на 1-ю запись, имеющую в поле Tovar значение, большее строки 'Ко' (рис. 8.15).
Это и есть искомая запись. Заметим, что применение инкрементальных локаторов возможно не только для символьных полей, но и для числовых. Пусть для того же НД, отсортированного по номеру группы (рис.8.16).
производится поиск по этому номеру. Для этой цели используется такой обработчик изменения значения в компоненте
Editi:procedure TForm1.Edit1Change(Sender: TObject) ;
var GrTmp : Longint;
begin
GrTmp := StrToInt(Edit1.Text);
//
поиск записиTable1.FindNearest([GrTmp]) ;
end;
Будем вводить в Edit1 значение "100". Тогда после ввода "1" текущей останется запись с номером группы, равным 1 (рис. 8.17).
после ввода "100" текущей станет запись с номером группы, равным 100, т.е. удовлетворяющая условию поиска (рис. 8.19):
8.3.6. Поиск по части текущего индекса
Если это необходимо, можно осуществлять поиск по частичному множеству индексных полей. Тогда поиск будет производиться не по всем полям данного индекса, а по их части. Для этого необходимо с помощью свойства
property KeyFieldCount: Integer; указать, сколько начальных полей индекса будут использоваться при поиске Установка значения свойства KeyFieldCount актуальна только в случае использования методов GoToKey и GoToNearest. Как осуществить поиск по частичному множеству индексных полей для методов Find и FmdNearest, см ниже.Пример. Пусть при текущем индексе по полям '
Doljnost; FIO' необходимо осуществить поиск по должности, и по 'должности; ФИО'. Условия поиска будем вводить в Editi (должность) и Edit2 (ФИО) Результаты работы приводимого ниже кода показаны на рис 8.20 а и 8 20 б.procedure TForm1.OneFieldFindButtonClick(Sender: T0bject);
begin
// поиск по 1 полю индекса
WITH Table1 do begin
SetKey;
KeyFieldCount := 1;
Table1Doljnost.Value := Editl.Text;
GoToNearest;
END; // with
end;
procedure TForm1. FullKeyFmfButtonClick (Sender : TObject);
begin
// поиск по 2 полям индекса
WITH Table1 do begin
SetKey;
KeyFieldCount := 2; II
на всякий случай, восстановимTable1Doljnost.Value := Edit1.Text;
Table1FIO.Value := Edit2.Text;
GoToNearest;
END; // with
end;
При этом есть ограничения- подмножество полей индекса должно быть в полном индексе непрерывным, т.е при индексе из 4 полей можно осуществить поиск по 1-му, 2-му, 3-му полям одновременно и нельзя по 1-му, 4-му, 6-му полям.
При использовании методов
FindKey и FindNearest необходимости в использовании KeyFieldCount нет; для поиска по частичному соответствию достаточно указать в списке часть полей данного индекса:// поиск по одному полю из двух
WITH Tablel do
FindNearest ( [Editl.Text] ) ;
// поиск по двум полям из двух:
WITH Tablel do
FindNearest( [Editl.Text, Edit2.Text] ) ;
TTableПомимо описываемых ниже методов, присущих только
TTable, наборы данных имеют также общие свойства, методы и события для фильтрации записей - Filter, Filtered, OnFilter-Record, FindFirst, FindLast, FindNext, FmdPrior Они описаны в разделе "Общие принципы работы с наборами данных"Для фильтрации записей ТБД, собственно
TTable имеет следующие методы:procedure SetRangeStart; -
устанавливает нижнюю границу фильтра;procedure EditRangeEnd;-
устанавливает верхнюю границу фильтра;procedure ApplyRange;
- осуществляет фильтрацию записей в TTable; условия фильтрации определяются методами SetRangeStart и SetRangeEnd,procedure SetRange(const StartValues, EndValues: array of const);- имеет тот же эффект, что и последовательное выполнение методов SetRangeStart, SetRangeEnd и ApplyRange. В качестве параметра используются массивы констант, каждый из которых содержит значения ключевых полей.
Заметим, что фильтрация методами ApplyRange/SetRange должна проводиться по ключевым полям. По умолчанию берется текущий индекс, определяемый свойством TTable.IndexNamewiH TTable.IndexFieldNames. В случае, если значения этих свойств не установлены, по умолчанию используется главный индекс ТБД. Поэтому, если нужно использовать индекс, отличный от главного, необходимо явно переустановить значение свойства TTable.IndexName (имя текущего индекса) или TTable.IndexFieldNames (список полей текущего индекса).
SetRangeМетод
procedure SetRange(const StartValues, EndValues: array ofconst); показывает в НД только те записи, индексные поля которых лежат в диапазоне [StartValues.. EndValues].Пример.
Пусть В НД Tablel показываются все записи из ТБД "TOV.DB" (Товары). Включим в структуру записи НД Tablel 2 поля : GrNum (Номер группы, Smallint) и Tovar (Наименование товара, String).Пусть текущий индекс построен по полю 'GrNum'.
Тогда, для фильтрации записей в НД таким образом, чтобы показывались записи только с определенным номером группы, располагаем в форме компоненты
Edit1 (для ввода номера группы) и CheckBox1. Если CheckBox1 отмечен (CheckBoxl.Checked = True), то производится фильтрация по номеру группы, введенному в Edit1; если CheckBoxl не отмечен или с него снята отметка (CheckBox1 -Checked = False), в НД показываются все записи из ТБД "TOV.DB" (рис.8.21). Напишем обработчик события CheckBox1.OnClick, возникающего при отметке CheckBoxl. Заметим, что для простоты правильность ввода в Editi не проверяется.procedure TForm1.CheckBoxIClick(Sender: TObject);
var GrNumTmp : Integer;
begin
IF CheckBoxl.Checked THEN
begin
GrNumTmp := StrToInt(Edit1.Text);
{————
фильтрация записей в НД————}WITH Tablel do begin
CancelRange;
SetRange([GrNumTmp],[GrNumTmp]) ;
END; {with}
end {then}
ELSE
{—————отмена фильтрации ——————}
Tablel.CancelRange;
end;
Неотфильтрованный НД показан на рис.8.21.
В отфильтрованном НД показываются только те записи, индексное поле текущего индекса у которых (т.е. в нашем случае поле GrNum) имеет значение, лежащее в заданном диапазоне. В данном случае диапазон определяется переменной GrNumTmp. Поэтому для GrNumTmp =3 будут показаны записи, принадлежащие к одной группе 3 (рис.8.22).
Если бы мы хотели, чтобы в НД фильтровались записи из нескольких групп, то нам следовало бы добавить в форму второй компонент
Edit2, в котором вводился бы номер конечной группы, в то время как в Edit1 вводился бы номер начальной группы. Далее необходимо модифицировать обработчик события CheckBoxl.OnClick, изменив SetRange на SetRange([GrNumTmpl],[GrNumTmp2]);procedure TForm1.CheckBoxIClick(Sender: T0b;ect) ;
var GrNumTrnpl,GrNumTmp2 : Integer;
begin
IF CheckBoxl.Checked THEN
begin
GrNumTmp1 := StrToInt(Edit1.Text);
GrNumTmp2 := StrToInt(Edit2.Text) ;
{————
фильтрация записей в НД-———}WITH Table1 do begin
CancelRange;
SetRange([GrNumTrnpl],[GrNumTmp2]) ;
END; {with}
end {then}
ELSE
{———
отмена фильтрации ——————}Table1.CancelRange;
end;
Результаты фильтрации записей с номерами группы от 2 до 4 показаны на рис.8.23.
Методы SetRangeStart, SetRangeEnd, ApplyRangeЭти методы (см. п.8.4.1) являются альтернативой методу
SetRange, который объединяет в себе функциональность трех указанных методов.В частности, рассмотренная в предыдущем примере фильтрация по начальному и конечному номеру группы может быть реализована таким образом:
procedure TForm1.CheckBoxIClick(Sender: TObject);
var GrNumTrnp1, GrNumTmp2 : Integer;
begin
IF CheckBox1.Checked THEN
begin
WITH Tablel do begin
CancelRange;
SetRangeStart ;
Table1. FieldByName ('GrNum') . Aslnteger: = GrNumTnpl ;
SetRangeEnd;
Tablel. FieldByName ('GrNum') .Aslnteger := GrMumTmp2 ;
ApplyRange;
END; {with}
end {then}
ELSE
Tablel.CancelRange;
end;
Метод CancelRangeМетод
procedure CancelRange; служит для отмены предыдущих условий фильтрации. Если предыдущую фильтрацию не отменить, возможно, следующие фильтрации принесут не такой результат, которого Вы ожидаете. Изменим пример, приводившийся выше. Сначала удалим из него вызов метода CancelRange. Пусть изначально НД сортируется по наименованию товара (поле 'Tovar'). Разместим в форме группу зависимых переключателей RadioGroup1, позволяющую переключать текущие индексы Tablel:procedure TFormI.RadioGroup1Click(Sender: TObject);
begin
WITH RadioGroupl do begin
CASE Itemlndex OF
0 : Table1.IndexFieldNames := 'Tovar'; //
текущий индекс по полю 'Tovar'1 : Tablel.IndexFieldNames := 'GrNum'; //
текущий индекс по полю 'GrNum'END;//case
END;//with
end;
Когда пользователь хочет произвести фильтрацию по номеру группы, он нажимает кнопку "Фильтровать", для которой реализован следующий обработчик нажатия:
procedure TForm1.Button1Click(Sender: TObject);
var GrNumTrnp1,GrNumTmp2 : Integer;
begin
{проверка правильности
Edit1.Text и Edit2.Text фильтрация по начальному и конечному номеру группы}WITH Tablel do begin
//отмечаем строку текущего выбора в RadioGroupl:
RadioGroupl.Itemlndex := 1;
{смена текущего индекса}
IndexFieldNames := 'GrNum';
SetRange ( [GrNumTrnp1] , [GrNumTmp2] ) ;
END; {with}
end;
Как видно, для фильтрации по полю
GrNum необходимо сменить текущий индекс таким образом, чтобы GrNum было индексным полем. Начинаем работу с неотфильтрованным НД (рис.8.24):Фильтруем НД по номеру группы, например, только по 3 группе (рис.8.25).
Через некоторое время возвращаемся к сортировке по товару. Для этого отмечаем соответствующий переключатель в группе
RadioGroup1. При этом видим, что показываются записи всех групп (рис.8.26):Это происходит от того, что при смене текущего индекса невозможно осуществить фильтрацию по другому индексу, уже не являющемуся текущим.
Пусть через некоторое время нам вновь необходимо отфильтровать НД по третьей группе. Напомним, что обработчик нажатия кнопки "Фильтровать" снова сделает текущим индекс, построенный по полю GrNum. И с удивлением отмечаем, что хотя сортировка и меняется (по GrNum), фильтрации не происходит (рис.8.27).
Однако, если попробовать сделать фильтрацию по группе с номером 2, фильтрация будет осуществлена (рис.8.28).
Увиденное можно объяснить следующим образом. Первоначально имеет место фильтрация по индексу
GrNum в диапазоне номеров групп [3..3]. После этого мы делаем текущим индекс, построенный по полю Tovar. Фильтрация по группам теперь невозможна, поскольку поле GrNum не входит в новый текущий индекс. Когда мы вновь делаем текущим индекс, построенный по полю GrNum, при этом не меняя диапазона групп [3..3], фильтрация не выполняется, поскольку она не отменена и диапазоны фильтрации не изменились. Когда же мы задаем новые условия фильтрации в диапазоне групп [2..2], НД фильтруется, т.к. изменился диапазон. Обойти эту особенность можно, отменяя перед новой фильтрацией (по какому бы то ни было индексу) результаты предыдущей фильтрации методом НД CancelRange:procedure TForm1.ButtonlClick(Sender: T0bject) ;
var GrNumTrnp1,GrNumTmp2 : Integer;
begin
{проверка правильности Edit1.Text и Edit2.Text}
{фильтрация по начальному и конечному номеру группы}
WITH Tablel do begin
CancelRange;
//отмечаем строку выбора текущего выбора в RadioG
roupl:RadioGroup1.Itemlndex := 1;
{смена текущего индекса}
IndexFieldNames := 'GrNum';
SetRange([GrNumTrnpl],[GrNumTmp2]) ;
END; {with}
end;
Методы EditRangeStart, EditRangeEndprocedure EditRangeStart;
procedure EditRangeEnd;
Эти методы предназначены для смены условий фильтрации, установленных ранее с использованием методов соответственно
SetRangeStart и SetRangeEnd. Напомним, что сама фильтрация в этом случае выполняется методом Apply Range. Преимущества их использования ясны не всегда. Например, можно было бы предположить, что для рассмотренной в п.8.4.4 ситуации эти методы способны заменить использование CancelRange :procedure TFormI.ButtonlClick(Sender: TObject);
var GrNumTrnpl, GrNumTmp2 : Integer;
const Num : Integer = 0;
begin
{проверка правильности
Edit.Text}INC(Num) ;
WITH Table1 do begin
IF Num = 1 THEN
begin
SetRangeStart;
FieldByName('GrNum').As Integer:= GrNumTrnp1;
SetRangeEnd;
FieldByName('GrNum').Aslnteger := GrNumTmp2;
ApplyRange;
end
ELSE
begin
EditRangeStart;
FieldByName('GrNum').Aslnteger:= GrNumTrnp1;
EditRangeEnd;
FieldByName('GrNum').Aslnteger := GrNumTmp2;
ApplyRange;
end;
END; {with}
end;
Однако результат будет таким же ошибочным. Указанный код будет правильно работать только в случае, когда индекс по 'GrNum' является принятым по умолчанию и в процессе работы не изменяется (представим, что в показанном выше примере мы удалили переключатели
RadioGroupl для выбора текущего индекса). Однако в этом случае правильно работает и кодprocedure TForm1.ButtonlClick(Sender: TObject);
var GrNumTrnp1,GrNumTmp2 : Integers;
begin
{проверка правильности
Edit1.Text и Edit2.Text}{фильтрация по начальному и конечному номеру группы}
WITH Tablel do begin
SetRangeStart;
FieldByName('GrNum') .Aslnteger:= GrNumTrnp1;
SetRangeEnd;
FieldByName('GrNum').Aslnteger := GrNumTmp2;
ApplyRange;
END; {with}
end;
Свойство KeyExclusiveСвойство
property KeyExclusive: Boolean; применяется для фильтрации записей в TTable с использованием методов SetRangeStart, SetRangeEnd u EditRangeStart, EditRangeEnd.Свойство
KeyExclusive влияет на включение в отфильтрованный НД записей, у которых индексные поля содержат граничные значения диапазона фильтрации. KeyExclusive включается и отключается отдельно для начального и конечного условия фильтрации.Если свойство для данной границы диапазона фильтрации (верхней или нижней) установлено в
False, записи, содержащие в индексном поле (полях) значение, указанное в качестве данной границы диапазона, включаются в отфильтрованный НД; если установлено в True, - не включаются. По умолчанию применяется значение False. Например:WITH Tablel do begin
CancelRange;
SetRangeStart;
KeyExclusive := True;
FieldByName('GrNum').Aslnteger:= GrNumTrnp1;
SetRangeEnd;
FieldByName('GrNum').Aslnteger := GrNumTmp2;
ApplyRange;
END; {with}
8.4.7. Фильтрация по составному индексу
Если индекс, по которому необходимо осуществить фильтрацию, состоит более чем одного поля:
• при использовании метода
Set Range значения полей должны перечисляться через запятую внутри квадратных скобок;• при использовании
SetRangeStart, SetRangeEnd и т.д. значение каждого поля должно устанавливаться явно.Пример. Пусть рассмотренный выше НД отсортирован по индексу, состоящему из полей '
GrNum;Tovar'. Реализуем фильтрацию записей по заданному значению поля GrNum и любому значению поля Tovar (рис.8.29).Для простоты проверку правильности ввода номера группы не производим. Обработчик выбора
CheckBoxl ("Фильтровать") выглядит так:procedure TFormI.CheckBoxIClick(Sender: TObject);
var GrNumTmp : Integer;
TovarTmp : String;
begin
IF CheckBoxl.Checked THEN
begin
GrNumTmp := StrToInt(Editi.Text);
TovarTmp := Edit2.Text;
{——————
фильтрация записей в НД————}WITH Tablel do begin
CancelRange;
SetRange([GrNumTmp,TovarTmp],[GrNumTmp,'
яя']) ;END; {with}
end {then}
ELSE
{————————
отмена фильтрации ———}Tablel.CancelRange;
end;
Реализацию выборки обеспечивает метод
SetRange([GrNumTmp,TovarTmp],[GrNumTmp,'яя']);
Интересно, что если требуется показывать в НД все записи группы, начинающиеся со значения в
Edit2.Text, то в качестве значения товара в конечном условии фильтрации нужно объявить максимально возможное значение, которое может встретиться в качестве названия товара. Поскольку строчные буквы имеют бoльшие коды, чем заглавные, и название товара не может начинаться с 'яя', эти символы вполне могут использоваться как верхний ограничитель наименования товара.Пусть введена группа и не введено наименование товара. В этом случае в отфильтрованный НД попадут все товары данной группы, т.е. записи , у которых определено наименование товара (рис.8.3
0):Пусть введен номер группы и наименовании товара - 'Макароны'. В этом случае в отфильтрованный НД попадут товары данной группы, у которых наименование больше или равно "Макароны" (рис.8.31):
8.4.8. Фильтрация по частичному соответствию
Для случаев сортировки по символьным полям полезно делать фильтрацию по частичному соответствию индексного поля (полей) условиям фильтрации.Пример. Пусть рассматривавшаяся выше ТБД отсортирована п( наименованию товара '
Tovar' (рис.8.32):Обработчик отметки
CheckBox1 ("Фильтровать"):procedure TForm1.CheckBoxIClick(Sender: TObject);
begin
IF CheckBoxl.Checked THEN
begin
{————фильтрация записей в НД————}
WITH Tablel do begin
CancelRange;
SetRange([Editl.Text],['
яя']);END; {with}
end {then}
ELSE
{———отмена фильтрации ——————
}Tablel.CancelRange;
end;
В этом случае в НД будут показаны все записи с названием товара, равны или большим указанного в
Edit1 (рис.8.33):Сменим вызов метода
SetRange на следующий:SetRange([Edit1.Text],[Edit1.Text + '
яя']);Тогда в результате фильтрации в отфильтрованный НД попадут только записи, начинающиеся с введенного в Editi фрагмента названия товара, т.е. с буквы М (рис.8.34):
8.4.9. Фильтрация по части составного индекса
В качестве условий фильтрации могут быть заданы не все поля текущего индекса, а только ведущее поле или группа ведущих полей.
В частности, для предыдущего примера можно указать в качестве текущего индекс '
Tovar;GrNum'. Тогда применение методаSetRange([Edit1.Text],[Edit1.Text + '
яя']);означает, что, поскольку в квадратных скобках в качестве начального и конечного условия фильтрации указаны не два значения поля, а одно, фильтрацию) следует проводить на предмет соответствия ведущего поля индекса (в нашем случае '
Tovar') заданному поисковому значению (в нашем случае начальное значение - Edit1.Text; конечное значение - Edit1.Text + 'яя').8.4.10. Ограничения возможностей фильтрации при использовании методов
SetRange/SetRangeStart и др.Заметим, что рассмотренный механизм фильтрации позволяет отфильтровывать только те записи, у которых значения ключевых полей больше или равны нижней границе и меньше или равны верхней границе фильтрации. Иными словами, затруднительно задать сложное условие типа "все записи, у которых поле А < 100 и7и поле
Z содержит вхождение строкм “поиск” ".При возникновении подобных проблем вместо компонента
TTable стоит использовать компонент TQuery и запросы на SQL.8.5. Множественный взгляд на НД
Часто в некий момент времени требуется: обращаться к одному и тому же НД из разных форм; работать с одной и той же ТБД, используемой для получения разных НД с помощью компонентов, размещенных в одной или разных формах.
Чтобы работа с одной ТБД из разных наборов или форм происходила над одной и той же физической записью ТБД, можно применять следующие механизмы: совмещать курсоры разных НД методом
GotoCurrent; переназначать свойство DataSet компонента TDataSource во время выполнения; использовать в разных формах НД и TDataSource, расположенные в одном и том же контейнере TDataModule.8.5.1. Совмещение курсоров двух НД
Часто, при одновременной работе с одной и той же ТБД выгодно применять два или более НД. Например, если в записи много полей, ее неудобно изменять в одном компоненте
TDBGrid и для этого удобнее воспользоваться отдельной формой (см. следующий раздел). В этом случае требуется совместить курсоры двух НД, для того чтобы работа производилась с одной и той же физической записью ТБД.Для этой цели применяется метод
procedure Table1 .GoToCurrent(Table2:TTable); устанавливающий курсор НД Table 1 на ту же запись, на которой находится курсор НД Table2.ЗАМЕЧАНИЕ.
Метод GoToCurrent переводит НД Table1 в режим dsBrowse с запоминанием изменений (метод Post). В случае невозможности перевода в режим dsBrowse возбуждается исключение и курсор НД Table2 остается неизменным (т.е. совмещение курсоров не происходит). 8.5.2. Создание отдельной формы для изменения записи НД. Форма добавления\корректировки записи с использованием механизма совмещения курсоровПусть имеется ТБД "Сотрудники кафедры" и необходимо добавлять новые или корректировать существующие записи ТБД не в
TDBGrid, а в отдельной форме. Назовем форму, в которой расположен TDBGrid, родительской. Назовем форму, в которой осуществляется добавление или корректировка записи, дочерней. Соответственно, TTable, расположеннье в этих формах, назовем родительским и дочерним. В этом случае порядок действий таков:1. Заводим новую форму (дочернюю), в ней -
TTable, TDataSource и группу компонентов TDBEdit (TDBComboBox, TDBLookup и т.д.), связанных между собой стандартным образом. Естественно, что компоненты TTable в родительской и дочерней формах должны указывать на одну таблицу базы данных. Желательно, чтобы и индексные файлы в них были одинаковы (свойство Table IndexName) Внешний вид этих форм показан соответственно на рис 8.35.а и 8.35 б.И в родительской и в дочерней форме компонент TTable, связанный с ТБД "Сотрудники кафедры", имеет имя
Table1. Поэтому в тех случаях, когда из дочерней формы нужно обратиться к Table1 в родительской форме, следует приписывать ей в качестве префикса название родительской формы, Prnt. Table7. В то же время, при обращении к Table1, принадлежащему дочерней форме, из самой дочерней формы можно указывать префикс {Chld.Table7), а можно и не указывать (Table1), например: Table1.GoToCurrent(PrntForm.Table1);Здесь
Table1.GoCurrent означает "выполнить метод GoToCnrrent, принадлежащий компоненту Table1 дочерней формы". Чтобы исключить разночтения, будем далее в программном коде явно указывать название формы при обращении к Table1ChldForm.Table1.GoToCurrent(PrntForm.Table1);
2. В модуле
unit, содержащем родительскую форму, разместим тестовую переменную SotrState, проинициализировав ее пустым значением:const
SotrState : String = '';
Эта переменная пригодится нам в дальнейшем для передачи в дочернюю форму кода требуемой операции '
Insert' или 'Edit'. Можно, конечно, было переводить родительскую TTable в режимы dslnsert или dsEdit в родительской форме, однако применение такого подхода работает при совмещении указателей на компонент TDataSource (см. ниже раздел "Переназначение DataSource во время выполнения"), но не подходит при использовании в дочерней форме метода GoToCurrent: этот метод автоматически переводит родительский НД в режим dsBrowse3. В родительской форме добавляем 2 кнопки для вызова дочерней формы одну (
InsertButton) на добавление записи, другую (EditButton) - на корректировку При этом присваиваем переменной SotrState соответственно значение 'Insert' или 'Edit'.После возврата из дочерней формы переменной
SotrState присваивается пустое значение.4 В процедуре дочерней формы, вызываемой при активизации формы, для случая добавлении записи переводим
Table дочерней формы в состояние dsInsert, для случая корректировки - совмещаем курсоры Table родительской и дочерней форм.ДочерняяТаЬ1е.GoToCurrent(PoдитeльcкaяTable) и затем переводим дочернюю форму в состояние
dsEdit.5 В дочерней форме добавляем клавиши "Запомнить" (
PostButton) и "Отменить" (CancelButton) Кнопке "Отменить" приписываем модальный результат mrCancel (установив свойство ModalResult = mrCancel). Это важно, поскольку дочерняя форма будет вызываться из родительской как модальная Кнопке "Запомнить" свойство mrOk не назначаем (т е. ее свойство ModalResult = mrNone). В случае, если выполнение метода Post прошло успешно (и исключение не было возбуждено), явно устанавливаем результат работы родительской формы в mrOk: Chid.ModalResult := mrOk;Это нужно для того, чтобы в случае неуспешности выполнения метода
Post мы могли вернуть фокус управления на поля дочерней формы для того, чтобы пользователь попробовал скорректировать значения и повторить попытку выполнения метода Post для запоминания изменений, внесенных в запись НД в дочерней форме. Если бы кнопка "Отменить" автоматически выставляла модальный результат mrOk при своем нажатии, после завершения обработчика нажатия этой кнопки происходил бы выход из дочерней формы, несмотря на то, что содержащийся в обработчике текст в случае неудачи Post предписывает не выходить из дочерней формы, а передать фокус управления ее полям.6. Как сказано выше, в обработчике нажатия клавиши "Запомнить" выполняем метод
Post, а в обработчике нажатия клавиши "Отменить" - метод Cancel, по отношению к НД Table1 дочерней формы. Поскольку Table1 как дочерней, так и родительской формы, во-первых, связаны с одной и той же ТБД "Сотрудники кафедры", а во-вторых, курсоры этих НД совмещены (что актуально для режима dsEdit), выполнение Chid.Table1.Post равносильно выполнению Prnt.Table1 .Post. В случае возбуждения исключения EDBEngineError для простоты полагаем, что произошло дублирование ключевого поля (если в ТБД есть уникальные ключевые поля), хотя, впрочем, могут быть и другие причины.Заметим, что для
PrntForm.Table1 нужно вызывать метод Refresh, который обновляет содержимое НД в родительской форме.Родительская форма:
// нажата кнопка "Вставить"
procedure TPrntForm.InsertButtonClick(Sender: TObject);
begin
SotrState := 'Insert';
ChldForm.ShowModal;
SotrState := '';
end;
// нажата кнопка "Изменить"
procedure TPrntForm.EditButtonClick (Sender: TObject) ;
begin
SotrState := 'Edit';
ChldForm.ShowModal;
SotrState := '';
end;
// нажата кнопка "Удалить"
procedure TPrntForm.DeleteButtonClick(Sender: TObject);
begin
IF MessageDIg
('Подтвердите удаление записи',mtlnformation, [mbYes, mbNo], 0) = mrYes THEN
Prnt.Table1.Delete;
end;
Дочерняя форма: при активизации дочерней формы переводим НД в режимы Insert или Edit. В последнем случае совмещаем курсоры
procedure TChldForm.FormActivate(Sender: TObject);
begin
IF SotrState = 'Edit' THEN
begin
ChldForm.Table1.GoToCurrent(PrntForm.Table1);
ChldForm.Table1.Edit;
end
ELSE
ChldForm.Table1.Insert;
end;
// обработчик нажатия клавиши "Запомнить"
procedure TChldForm.PostButtonClick(Sender: TObject);
begin
TRY
ChldForm.Table1.Post;
PrntForm.Table1.Refresh;
IF SotrState = 'Insert' THEN
PrntForm.Table1.GoToCurrent(ChldForm.Table1);
// при успешном выполнении Post выходим из модальной формы:
ChldForm.ModalResult := mrOk;
EXCEPT
on EDBEngineError do begin
ShowMessage('Дублирование ключевого поля!');
DBEditI.SetFocus; // возвращаемся на 1 поле DBEdit
end; {on}
ELSE
begin
ShowMessage('Ошибка иного типа при Post');
DBEditI.SetFocus;
end;
END; {try}
end;
//
нажата кнопка "Отменить" (ModalResult = mrCancel)Procedure TChldForm.CancelButtonClick (Sender: TObject);
begin ChldForm.Table1.Cancel;
end;
// если при выходе из дочерней формы набор данных - не в состоянии dsBrowse (что может быть при нажатии кнопки 'х') вверху слева, НД принудителъно переводится в режим dsBrowse
procedure TChldForm.FormDeactivate(Sender: TObject);
begin
IF ChldForm.Table1.State <> dsBrowse THEN
ChldForm.Table1.Cancel;
end;
КОММЕНТАРИЙ.
Может оказаться, что на момент выхода из формы методы Posг или Cancel к НД не выполнены (например, когда выход осуществляется также по нажатию иных кнопок, например "Выход"). Поэтому в родительской форме, после выхода из дочерней формы, будет нелишним проверить, находится ли НД в состоянии dsBrowse и если нет, принудительно перевести его в это состояния методом Cancel, отменяющим все сделанные изменения в записи. Естественно, что можно предусмотреть и иную реакцию на попытку выхода при НД, находящемся в режимах dslnsert или dsEdit.Представление обеих форм в момент внесения изменений в ТБД из дочерней формы показано на рис.8.36.
TDataSource во время выполненияСпособ совмещения курсоров не является единственным для обеспечения работы с НД из разных форм. Другой способ состоит в переадресации компонента TDaгaSource во время выполнения.
Пусть имеется задача, когда из разных форм нужно работать с одним и тем же НД. Частным случаем данной задачи является форма обновления записей, рассмотренная в предыдущем разделе. В этом случае необходимо:
1. В родительской форме расположить компоненты
TDataSource (назовем его DataSourcel), TTable, TDBGrid (или иные визуальные компоненты дляработы с БД) и связать указанные компоненты между собой стандартным способом.
2. Во всех дочерних таблицах расположить компоненты
TDataSource (назовем его DataSource2), TDBEdit (или иные визуальные компоненты для работы с БД) и связать их с DataSource2. При этом свойство DataSource2.DataSet не должно указывать ни на какой НД, т.е. должно оставаться пустым в инспекторе объектов во время проектирования, а во время выполнения в обработчике события создания дочерней формы полезно поместить предложение DataSource2.DataSet := nil;Часто бывает полезным для присваивания имен полей расположить в дочерней форме еще и компонент
TTable, связав его стандартным образом с DataSource2 и визуальными компонентами для работы с НД. Однако после этого компонент Table нужно из дочерней формы удалить и очистить свойство DataSource2. DataSet описанным выше образом.3. Во время выполнения в обработчик события активизации дочерней формы или при вызове дочерней формы из родительской необходимо поместить предложение
DataSource2.DataSet := ИМяРодительскойФормы.ИмяКомпонентаТаЬ1е;
Таким образом,
DataSource дочерней формы будет указывать на НД из родительской формы и работать с его текущей записью.Изменение в дочерней форме местоположения курсора НД приведет к тому, что данное изменение будет актуально и в родительской форме, равно как любые действия по изменению, добавлению и удалению записей НД, произведенные в дочерней форме. Причина простая: и родительская, и дочерняя формы работают с одним и тем же НД.
Подобные действия можно реализовать в любом количестве дочерних форм, таким образом одновременно работая с одним и тем же набором данных из разных форм.
4. При деактивизации дочерней формы необходимо разрушить связь между
DataSource дочерней формы и НД из родительской формы, включив в обработчик события деактивизации дочерней формы предложениеDataSource2.DataSet := nil;
DataSource дочерней формыЭтот пример аналогичен приведенному в разделе "Создание отдельной формы для изменения записи ПД. Форма добавления 1корректировки записи с использованием механизма совмещения курсоров ".
Отличия в реализации следующие. Во-первых, НД переводится в состояние
Insert или Edit в родительской форме. Во-вторых, в дочерней форме доступ к НД осуществляется переадресацией DataSource во время выполнения.Родительская форма:
// нажата кнопка "Вставить"
procedure TPrntForm.InsertButtonClick(Sender: TObject);
begin
PrntForm.Table1.Insert;
ChldForm.ShowModal;
end;
// нажата кнопка "Изменить"
procedure TPrntForm.EditButtonClick(Sender: TObject);
begin
PrntForm.Table1.Edit;
ChldForm.ShowModal;
end;
// нажата кнопка "Удалить"
procedure TPrntForm.DeleteButtonClick(Sender: TObject);
begin
IF MessageDIg('
Подтвердите удаление записи', mtlnformation, [mbYes, mbNo], 0) = mrYes THEN PrntForm.Table1.Delete;end;
Дочерняя форма:
// обработчик активизации дочерней формы
procedure TChldForm.FormActivate(Sender: TObject);
begin
// при активизации дочерней формы ее DataSource указывает на НД из родительской формы
ChldForm.DataSourcel.DataSet := PrntForm.Table1;
end;
// обработчик нажатия клавиши "Запомнить"
procedure TChldForm.PostButtonClick(Sender: TObject);
begin
TRY
PrntForm.Table1.Post;
// при успешном выполнении Post выходим из модальной формы:
ChldForm.ModalResult := mrOk;
EXCEPT
on EDBEngineError do begin
ShowMessage('Дублирование ключевого поля!');
DBEditI.SetFocus; // возвращаемся на 1 поле DBEdit
end; {on}
ELSE
begin
ShowMessage('Ошибка иного типа при Post');
DBEditI.SetFocus;
end;
END; {try}
end;
//
нажата кнопка "Отменить" (ModalResulfc = mrCancel)procedure TChldForm.CancelButtonClick(Sender: TObject);
begin
PrntForm.Table1.Cancel;
end; ,
// обработчик события деактивизации дочерней формы
DataSource дочерней формы указывает в "пустоту";// принудительный перевод НД в состояние
Browse, если на момент выхода из дочерней формы НД находится//
в ином состоянииprocedure TChldForm.FormDeactivate(Sender: TObject);
begin
ChldForm.DataSourcel.DataSet := nil;
IF PrntForm.Table1.State <> dsBrowse THEN
PrntForm.Table1.Cancel;
end;
// обработчик события создания формы
procedure TChldForm.FormCreate(Sender: TObject);
begin
// DataSource
дочерней формы изначально указывает в "пустоту"ChldForm.DataSourcel.DataSet := nil;
end;
8.5.5. Использование контейнера
TDataModuleКомпонент типа
TDataModule представляет собой контейнер, в который вмещаются компоненты TTable, TQuery, DataSource и т.д. Создать экземпляр dataModule можно, выбрав в среде Delphi пункт меню File|NewDataModule. Компонент TDataModule похож на форму (TForm) в том отношении, что служит контейнером для иных компонентов. Однако, в отличие от TForm, в dataModule можно помещать только невизуальные компоненты для работы базами данных. Связь компонентов TDataSource, TTable и TQuery, расположенных в DataModule, производится так же, как если бы они были расположены в одной форме.TDataModule
нужно сохранить под каким-либо именем и добавить имя модуля unit, в котором описан TDataModule, в текст модулей unit всех иных рм приложения, которые будут использовать НД и TDataSource, положенные в этом TDataModule. Это производится в главном меню среды delphi, в элементе меню File|Use Unit.В дальнейшем визуальные компоненты, работающие с данным НД, должны в своем свойстве
DataSource содержать имя соответствующего компонентаTDataSource
из TDataModule. При этом имя является составным: сначала идет имя компонента TDataModule и затем через точку - имя компонента TDataSource, например, DataModulel.DataSource1.Перепишем предыдущий пример для использования механизма
TDataModule. Для этого добавим в приложение компонент DataModule1 и разместим в нем компоненты Table1 (связанный с ТБД "Сотрудники кафедры") и DataSourcel (связанный с DataModulel.Table1). Тогда в родительской форме установим свойство DBGrid1.DataSource = DataModulel .DataSourcel; в дочерней форме установим свойства DataSource всех компонентов, работающих с отдельными полями ТБД "Сотрудники кафедры" (в данном случае это компоненты TDBEdit) равными DataModulel .DataSourcel. При этом не будем забывать о заполнении свойства DataField этиx компонентов, иначе компоненты TDBEdit не будут связаны с полями. Пример компонента TDataModule с размещенными в нем компонентами TTable и TDataSource показан на рис. 8.37.Виды родительской и дочерней форм во время разработки приложения показаны соответственно на рис. 8.38 а) и б).
Как видно из рисунков, в формах
PrntForm и ChldForm напрочь отсутствуют компоненты TTable и TDataSource, связанные с ТБД "Сотрудники кафедры".Приведем обработчики событий для обеих форм:
Родительская форма
// нажата кнопка "Вставить"
procedure TPrntForm.InsertButtonClick(Sender: TObject);
begin
DataModulel.Table1.Insert;
ChldForm.ShowModal ;
end;
// нажата кнопка "Изменить"
procedure TPrntForm.EditButtonClick(Sender: TObject);
begin
DataModulel.Table1.Edit;
ChldForm.ShowModal ;
end;
// нажата кнопка "Удалить"
procedure TPrntForm.DeleteButtonClick(Sender: TObject);
begin
IF MessageDIg('Подтвердите удаление записи',
mtInformation, [mbYes, mbNo], 0) = mrYes THEN DataModulel.Table1.Delete;
end;
Дочерняя форма
// обработчик нажатия клавиши "Запомнить"
procedure TChldForm.PostButtonClick(Sender: TObject);
begin
TRY
DataModulel.Table1.Post;
// при успешном выполнении Post выходим из модальной формы:
ChldForm.ModalResult := mrOk;
EXCEPT
on EDBEngineError do begin
ShowMessage('Дублирование ключевого поля!');
DBEdit1.SetFocus; // возвращаемся на 1 поле
DBEditend; {on}
ELSE
begin
ShowMessage('Ошибка иного типа при Post');
DBEdit1.SetFocus;
end;
END; {try}
end;
//
нажата кнопка "Отменить" (ModalResulfc = mrCancel)procedure TChldForm.CancelButtonClick(Sender: TObject);
begin
DataModulel.Table1.Cancels;
end;