DB-Aware контролы в Delphi значительно упрощают жизнь разработчикам GUI-программ работающих с базами данных. Они многое делают сами без написания кода – отображают данные, позволяют пользователям их модифицировать и сохраняют изменения в базу данных. Но, что делать, если данные хранятся не в базе данных, а в массиве, списке, объекте или какой-нибудь другой структуре? Можно воспользоваться "memory table" – потомком TDataSet, который хранит данные в памяти. Скопировать в него данные, отобразить, обработать и скопировать обратно. Вариантов таких "memory table" много: TClientDataSet, TFDMemTable из FireDAC, TkbmMemTable, TVirtualTable из UniDAC, TMemTableEh из EhLib... Но есть способ решить этот вопрос проще, без копирования данных туда-сюда.
Поможет нам с этой задачей TVirtualDataSet из UniDAC. TVirtualDataSet – это потомок TDataSet, который не хранит данные, а является посредником между ними и работающими с TDataSet контролами или функциями. Он позволяет представить любую структуру данных (массив, список, объект и т. д.) в качестве TDataSet.
TVirtualDataSet взаимодействует с данными с помощью обработчиков событий. Для минимальной работы необходимо написать два обработчика:
Для начала в конец таблицы добавим еще некоего Билла Гейтса. Нажатие на кнопку DBNavigator "Сохранить" вызвало у TVirtualDataSet метод Post и обработчик OnInsertRecord (tPersonsInsertRecord), который дополнил наш список этим почтенным ИТ-пенсионером, пересортировал список и изменил позицию текущей записи TVirtualDataSet на новую запись вернув ее новый номер через параметр RecNo. Теперь переименуем Петрова в Гарри Гаррисона. Нажатие на кнопку DBNavigator "Сохранить" вызвало у TVirtualDataSet метод Post и обработчик OnModifyRecord (tPersonsModifyRecord), который изменил значение элемента списка, пересортировал список и изменил позицию текущей записи TVirtualDataSet если после сортировки она переместилась в списке. Обратите внимание на параметр RecNo. В этом обработчике он имеет двойное назначение – сначала указывает на номер модифицированной записи, а потом на ее новый номер после сортировки. Напоследок удалим из нашего списка Иванова. Обработчик OnDeleteRecord (tPersonsDeleteRecord) удалил из списка запись по указанному в параметре RecNo индексу. Как видите, TVirtualDataSet и несколько простых обработчиков могут позволить программисту работать с любой структурой данных как с TDataSet.
Поможет нам с этой задачей TVirtualDataSet из UniDAC. TVirtualDataSet – это потомок TDataSet, который не хранит данные, а является посредником между ними и работающими с TDataSet контролами или функциями. Он позволяет представить любую структуру данных (массив, список, объект и т. д.) в качестве TDataSet.
TVirtualDataSet взаимодействует с данными с помощью обработчиков событий. Для минимальной работы необходимо написать два обработчика:
- OnGetRecordCount – вызывается, когда TVirtualDataSet запрашивает количество записей;
- OnGetFieldValue – вызывается, когда TVirtualDataSet запрашивает значение поля.
type TPerson = record ID : Integer; FIO: String; AGE: Integer; constructor Create(const AID: Integer; const AFIO: String; const AAGE: Integer); end; implementation { TPerson } constructor TPerson.Create(const AID: Integer; const AFIO: String; const AAGE: Integer); begin ID := AID; FIO := AFIO; AGE := AAGE; end;Создадим новый VCL-проект, в котором для хранения данных объявим динамический массив FPersons с этементами типа TPerson. На главную форму проекта поместим TDBGrid для отображения данных и TVirtualDataSet с полями, соответствующими типу TPerson (ID: TIntegerField, FIO: TWideStringField и AGE: TIntegerField), который свяжем с массивом FPersons с помощью обработчиков OnGetRecordCount и OnGetFieldValue.
unit Main; interface uses System.Classes, System.SysUtils, Vcl.Forms, Vcl.Controls, Vcl.Grids, Vcl.DBGrids, Data.DB, MemDS, VirtualDataSet, DBAccess, Uni; type TMainForm = class(TForm) dgPersons: TDBGrid; tPersons: TVirtualDataSet; tPersonsID: TIntegerField; tPersonsFIO: TWideStringField; tPersonsAGE: TIntegerField; dsPersons: TUniDataSource; procedure FormCreate(Sender: TObject); procedure tPersonsGetRecordCount(Sender: TObject; out Count: Integer); procedure tPersonsGetFieldValue(Sender: TObject; Field: TField; RecNo: Integer; out Value: Variant); private FPersons: TArray<TPerson>; end; var MainForm: TMainForm; implementation {$R *.dfm} procedure TMainForm.FormCreate(Sender: TObject); begin SetLength(FPersons, 3); FPersons[0] := TPerson.Create(123, 'Иванов Иван Иванович', 25); FPersons[1] := TPerson.Create(124, 'Петров Петр Петрович', 30); FPersons[2] := TPerson.Create(125, 'Сидоров Сидор Сидорович', 27); tPersons.Open; end; procedure TMainForm.tPersonsGetRecordCount(Sender: TObject; out Count: Integer); begin Count := Length(FPersons); end; procedure TMainForm.tPersonsGetFieldValue(Sender: TObject; Field: TField; RecNo: Integer; out Value: Variant); begin case Field.FieldNo of 1: Value := FPersons[RecNo - 1].ID; 2: Value := FPersons[RecNo - 1].FIO; 3: Value := FPersons[RecNo - 1].AGE; end; end; end.Обработчик OnGetFieldValue (tPersonsGetFieldValue) вызывается каждый раз, когда TVirtualDataSet необходимо для строки данных (параметр RecNo: Integer) получить значение поля (параметр Field: TField). Обратите внимание, что нумерация свойства TField.FieldNo, в отличие от индекса TDataSet.Fields начинается с 1. Запускаем программу и в TDBGrid видим содержимое массива FPerson: Простым примером применения этого механизма может быть использование массива в качестве LookupDataSet для lookup-поля. Но с помощью TVirtualDataSet мы можем не только отображать данные произвольной структуры, но и их модифицировать. Для этого необходимо реализовать еще три обработчика:
- OnInsertRecord – вызывается, когда в TVirtualDataSet добавляется новая запись;
- OnModifyRecord – вызывается, когда в TVirtualDataSet модифицируется запись;
- OnDeleteRecord – вызывается, когда в TVirtualDataSet удаляется запись.
unit Main; interface uses System.Classes, System.SysUtils, System.Generics.Defaults, System.Generics.Collections, Vcl.Forms, Vcl.Controls, Vcl.Grids, Vcl.DBGrids, Data.DB, MemDS, VirtualDataSet, DBAccess, Uni, Vcl.ExtCtrls, Vcl.DBCtrls; type TMainForm = class(TForm) dgPersons: TDBGrid; tPersons: TVirtualDataSet; tPersonsID: TIntegerField; tPersonsFIO: TWideStringField; tPersonsAGE: TIntegerField; dsPersons: TUniDataSource; DBNavigator: TDBNavigator; procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure tPersonsGetRecordCount(Sender: TObject; out Count: Integer); procedure tPersonsGetFieldValue(Sender: TObject; Field: TField; RecNo: Integer; out Value: Variant); procedure tPersonsInsertRecord(Sender: TObject; var RecNo: Integer); procedure tPersonsModifyRecord(Sender: TObject; var RecNo: Integer); procedure tPersonsDeleteRecord(Sender: TObject; RecNo: Integer); private FPersons: TList<TPerson>; end; var MainForm: TMainForm; implementation {$R *.dfm} procedure TMainForm.FormCreate(Sender: TObject); begin FPersons := TList<TPerson>.Create(TComparer<TPerson>.Construct(function(const Left, Right: TPerson): Integer begin Result := CompareStr(Left.FIO, Right.FIO); end)); FPersons.Add(TPerson.Create(125, 'Сидоров Сидор Сидорович', 27)); FPersons.Add(TPerson.Create(123, 'Иванов Иван Иванович', 25)); FPersons.Add(TPerson.Create(124, 'Петров Петр Петрович', 30)); FPersons.Sort; tPersons.Open; end; procedure TMainForm.FormDestroy(Sender: TObject); begin tPersons.Close; FPersons.Free; end; procedure TMainForm.tPersonsGetRecordCount(Sender: TObject; out Count: Integer); begin Count := FPersons.Count; end; procedure TMainForm.tPersonsGetFieldValue(Sender: TObject; Field: TField; RecNo: Integer; out Value: Variant); begin case Field.FieldNo of 1: Value := FPersons[RecNo - 1].ID; 2: Value := FPersons[RecNo - 1].FIO; 3: Value := FPersons[RecNo - 1].AGE; end; end; procedure TMainForm.tPersonsInsertRecord(Sender: TObject; var RecNo: Integer); var NewPerson: TPerson; begin NewPerson := TPerson.Create(tPersonsID.Value, tPersonsFIO.Value, tPersonsAGE.Value); FPersons.Add(NewPerson); FPersons.Sort; RecNo := FPersons.IndexOf(NewPerson) + 1; end; procedure TMainForm.tPersonsModifyRecord(Sender: TObject; var RecNo: Integer); var TempPerson: TPerson; begin TempPerson := FPersons[RecNo - 1]; TempPerson.ID := tPersonsID.Value; TempPerson.FIO := tPersonsFIO.Value; TempPerson.AGE := tPersonsAGE.Value; FPersons[RecNo - 1] := TempPerson; FPersons.Sort; RecNo := FPersons.IndexOf(TempPerson) + 1; end; procedure TMainForm.tPersonsDeleteRecord(Sender: TObject; RecNo: Integer); begin FPersons.Delete(RecNo - 1); end; end.В FormCreate при создании списка я добавил ему компаратор, который будет сортировать список по полю "FIO". Поэтому для наглядности я заполняю FPersons в произвольном порядке. Посмотрим, как работает эта версия программы.
Для начала в конец таблицы добавим еще некоего Билла Гейтса. Нажатие на кнопку DBNavigator "Сохранить" вызвало у TVirtualDataSet метод Post и обработчик OnInsertRecord (tPersonsInsertRecord), который дополнил наш список этим почтенным ИТ-пенсионером, пересортировал список и изменил позицию текущей записи TVirtualDataSet на новую запись вернув ее новый номер через параметр RecNo. Теперь переименуем Петрова в Гарри Гаррисона. Нажатие на кнопку DBNavigator "Сохранить" вызвало у TVirtualDataSet метод Post и обработчик OnModifyRecord (tPersonsModifyRecord), который изменил значение элемента списка, пересортировал список и изменил позицию текущей записи TVirtualDataSet если после сортировки она переместилась в списке. Обратите внимание на параметр RecNo. В этом обработчике он имеет двойное назначение – сначала указывает на номер модифицированной записи, а потом на ее новый номер после сортировки. Напоследок удалим из нашего списка Иванова. Обработчик OnDeleteRecord (tPersonsDeleteRecord) удалил из списка запись по указанному в параметре RecNo индексу. Как видите, TVirtualDataSet и несколько простых обработчиков могут позволить программисту работать с любой структурой данных как с TDataSet.
Комментариев нет:
Отправить комментарий