Devart Blog

Use VirtualDataSet to access objects list via TDataSet interface

Posted by on November 18th, 2016

TVirtualDataSet is a virtual dataset that can be used as an intermediate between data stored in an application and data-aware controls used in forms. TVirtualDataSet behaves the same way as any other TDataSet descendant and, therefore, it is compatible with all data-aware controls. However, it does not store data in memory. Work with TVirtualDataSet is implemented by means of events occurring during data exchange between an application and controls. As a result, you can choose a variety of entities as a data storage source: arrays, lists, records, XML files, etc.

Let’s use TVirtualDataSet to create a small application implementing the Master-Detail relationhip using standard Emp and Dept tables. We will fill these tables directly in the application code. As a data storage source, we will use regular TList whose elements will be the instances of TDept and TEmp classes correspondingly. By the example of the Dept entity, the TDept class is represented by the following declaration:

TDept = class
  private
    FDeptNo: Integer;
    FDName: String;
    FLoc: String;
  public
    constructor Create(const DeptNo: Integer; const DName: string; const Loc: string);
    property DeptNo: Integer read FDeptNo write FDeptNo;
    property DName: string read FDName write FDName;
    property Loc: string read FLoc write FLoc;
  end;

Thus creation of a single record will look like this:

Dept.Add(TDept.Create(DeptNoValue, DNameValue, LocValue));

where DeptNoValue, DnameValue, LocValue are values of the DeptNo, DName, Loc fields,  correspondingly, e.g:

Dept.Add(TDept.Create(10, 'ACCOUNTING', 'NEW YORK'));

The Emp entity is filled with data in the same way. Remember that Dept and Emp in our example are instances of the TList class. How can we manage them using standard data-aware controls? TVirtualDataSet exists exactly for this purpose. Let’s display Dept content in the DBGrid component. To do this, we need to implement two TVirtualDataSet events: OnGetRecordCount and OnGetFieldValue. In the OnGetRecordCount event, it is necessary to determine a number of records that our virtual dataset will contain. In our case this is the number of elements in Dept:

Count := Dept.Count;

The OnGetRecordCount event always occurs when TVirtualDataSet receives a number of records contained in the dataset.

We will use the OnGetFieldValue event to implement the data collection method. It arises every time when TVirtualDataSet needs to obtain a field value. The field value must be returned into the out Value: Variant parameter. The Field parameter defines which attribute should receive a value at the moment and the RecNo parameter contains an absolute record number. Thus in our case, the OnGetFieldValue handler will look like this:

DeptItem := TDept(Dept.Items[RecNo-1]);
case Field.FieldNo of
  1: Value := DeptItem.DeptNo;
  2: Value := DeptItem.DName;
  3: Value := DeptItem.Loc;
end;

The Dept entity is processed in a similar way. Now let’s describe the Master-Detail relationship between these entities by using the following code:

EmpDataSet.MasterFields := 'DeptNo';
EmpDataSet.DetailFields := 'DeptNo';
EmpDataSet.MasterSource := MasterDataSource;

As a result we implement this relationship using two TDBGrid components:

Therefore, using TVirtualDataSet allowed us to connect two TList classes by the Master-Detail relationship and to display the result by means of the data-aware TDBGrid components.

With the help of TVirtualDataSet you can not only display necessary data but also modify them. This functionality can also be implemented by using the following methods: OnInsertRecord, OnModifyRecord and OnDeleteRecord.

All the three handlers are essential for displaying data modification (when inserting, editing and deleting) in the TVirtualDataSet data source. So, for example, we need to insert a new record into the Dept table. Let’s add the following record (the Insert button) using the DBNavigator component.

After this you should confirm the record insertion by pressing the Post button. Let’s make it clear that the Dept records are sorted by the DeptNo field. After inserting, our record took a required position in the dataset and became active:

datasetdemo

This behavior is implemented in the OnInsertRecord handler:

procedure TfmMain.DeptDataSetInsertRecord(Sender: TObject; var RecNo: Integer);
var
  NewItem: TDept;
begin
  NewItem := TDept.Create(DeptDataSet.FieldByName('DeptNo').AsInteger, DeptDataSet.FieldByName('DName').AsString, DeptDataSet.FieldByName('Loc').AsString);
  Dept.Insert(0, NewItem);
  Dept.Sort(CompareDeptByDeptNo);
  RecNo := Dept.IndexOf(NewItem) + 1;
end;

The OnInsertRecord handler works this way. At first, we should create a new record and add it to TList. Then we sort TList. Here we use the following function :

function CompareDeptByDeptNo(Item1, Item2 : Pointer): Integer;
begin
  Result := Sign(TDept(Item1).DeptNo - TDept(Item2).DeptNo);
end;

It returns a comparison result of the DeptNo fields of two records in the TDept entity.
And finally, we specify a value for the RecNo parameter corresponding to the inserted record index. This will allow to make the record active immediately after inserting it.

Handling the OnModifyRecord and OnDeleteRecord events is similar. The first event appears when we need to save modified data in a used source, the second one – in a case the data needs to be deleted. The var RecNo: Integer parameter has the same purpose, as in the OnInsertRecord handler. In our example the events handling is implemented for the Emp entity:

procedure TfmMain.EmpDataSetModifyRecord(Sender: TObject; var RecNo: Integer);
var
  Item: TEmp;
begin
  Item := TEmp(Emp.Items[RecNo - 1]);
  Item.FEmpNo := EmpDataSet.FieldByName('EmpNo').AsInteger;
  Item.FEName := EmpDataSet.FieldByName('EName').AsString;
  Item.FJob := EmpDataSet.FieldByName('Job').AsString;
  Item.FMgr := EmpDataSet.FieldByName('Mgr').AsVariant;
  Item.FHireDate := EmpDataSet.FieldByName('HireDate').AsDateTime;
  Item.FSal := EmpDataSet.FieldByName('Sal').AsFloat;
  Item.FComm := EmpDataSet.FieldByName('Comm').AsVariant;
  Item.FDeptNo := EmpDataSet.FieldByName('DeptNo').AsInteger;
end;
procedure TfmMain.EmpDataSetDeleteRecord(Sender: TObject; RecNo: Integer);
begin
  TEmp(Emp.Items[RecNo - 1]).Free;
  Emp.Delete(RecNo - 1);
  EmpDataSet.Refresh;
end;

Therefore, this example illustrates the use of TVirtualDataSet to link standard data-aware controls with any data sources.

2 Responses to “Use VirtualDataSet to access objects list via TDataSet interface”

  1. Martin Says:

    Hello

    please source code demo. Thank You
    Martin

  2. DAC Team Says:

    The described project is included in the demo samples supplied with VirtualDAC : [VirtualDAC install folder]\Demos\VirtualDataSet\

Leave a Reply