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.





Комментариев нет:
Отправить комментарий