Use VirtualDataSet para acceder a la lista de objetos a través de la interfaz TDataSet

TVirtualDataSet es un conjunto de datos virtuales que se puede utilizar como intermediario entre los datos almacenados en la aplicación y los controles utilizados en los formularios. TVirtualDataSet se comporta como cualquier otro descendiente de TDataSet y, por lo tanto, es compatible con todos los controles basados ​​en datos. Sin embargo, no almacena datos en la memoria. El trabajo con TVirtualDataSet se implementa a través de eventos que ocurren durante el intercambio de datos entre el programa y los controles. Como resultado, puede seleccionar diferentes entidades como fuente de almacenamiento: matrices, listas, registros, archivos XML, etc.

Usemos TVirtualDataSet para crear una pequeña aplicación que implemente relaciones Maestro-Detalle usando tablas estándar Emp y Dept. Completaremos estas tablas directamente en el código de la aplicación. Como fuente de almacenamiento utilizaremos la TList habitual, cuyos elementos serán las clases TDept y TEmp, respectivamente. En el ejemplo de la esencia de Dept, la clase TDept está representada por la siguiente declaración:

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;

Entonces, crear un solo registro se vería así:

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

donde DeptNoValue, DnameValue, LocValue son los valores de los campos DeptNo, DName, Loc, respectivamente, por ejemplo:

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

El objeto Emp se rellena con datos de la misma manera. Recuerde que Dept y Emp en nuestro ejemplo son instancias de la clase TList. ¿Cómo podemos administrarlos utilizando controles estándar basados ​​en datos? TVirtualDataSet existe para este propósito. Mostremos el contenido de Dept en el componente DBGrid. Para hacer esto, necesitamos implementar dos eventos TVirtualDataSet: OnGetRecordCount y OnGetFieldValue. El evento OnGetRecordCount requiere que especifique la cantidad de registros que contendrá nuestro conjunto de datos virtual. En nuestro caso este es el número de elementos en el Dept:

Count := Dept.Count;

El evento OnGetRecordCount siempre ocurre cuando TVirtualDataSet recibe una serie de registros contenidos en un conjunto de datos.

Usaremos el evento OnGetFieldValue para implementar el método de recopilación de datos. Ocurre cada vez que TVirtualDataSet necesita obtener un valor de campo. El valor del campo debe devolverse al parámetro Out Value: Variant. El parámetro Field especifica qué atributo debe obtener el valor en ese momento, y el parámetro RecNo contiene el número de registro absoluto. Entonces, en nuestro caso, el controlador OnGetFieldValue se verá así:

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

El tema del Departamento se trata de manera similar. Ahora describamos la relación Maestro-Detalle entre estas entidades usando el siguiente código:

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

Como resultado, implementamos esta conexión usando dos componentes TDBGrid:

Por lo tanto, el uso de TVirtualDataSet nos permitió conectar las dos clases TList con la conexión Master-Detail y mostrar el resultado usando componentes TDBGrid que soportan los datos.

Con TVirtualDataSet no solo puede mostrar los datos requeridos, sino también cambiarlos. Esta funcionalidad también se puede implementar mediante los siguientes métodos: OnInsertRecord, OnModifyRecord y OnDeleteRecord.

Se requieren los tres controladores para mostrar las modificaciones de datos (al insertar, editar y eliminar) en la fuente de datos TVirtualDataSet. Por ejemplo, necesitamos insertar un nuevo registro en la tabla Dept. Agreguemos la siguiente entrada (botón Insertar) usando el componente DBNavigator.

A continuación, debe confirmar la inserción de la entrada haciendo clic en el botón Publicar. Aclaremos que los registros de departamento están ordenados por el campo DeptNo. Después de la inserción, nuestro registro ocupó la posición correcta en el conjunto de datos y se activó:

demostración de datos

Este comportamiento se implementa en el controlador OnInsertRecord:

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;

El controlador OnInsertRecord funciona de esta manera. Primero necesitamos crear un nuevo registro y agregarlo a la TList. Luego ordenamos la TList. Aquí usamos la siguiente función:

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

Devuelve el resultado de comparar los campos DeptNo de dos registros en la esencia de TDept.
Finalmente, especificamos un valor para el parámetro RecNo que corresponde al índice del registro insertado. Esto hará que la entrada se active inmediatamente después de que se inserte.

El manejo de eventos OnModifyRecord y OnDeleteRecord es similar. El primer evento es cuando necesitamos guardar los datos modificados en la fuente utilizada, el segundo, en caso de que necesitemos eliminar los datos. El parámetro var RecNo: Integer tiene el mismo propósito que el controlador OnInsertRecord. En nuestro ejemplo, el manejo de eventos se implementa para la entidad Emp:

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;

Por lo tanto, este ejemplo ilustra el uso de TVirtualDataSet para asociar controles basados ​​en datos estándar con cualquier fuente de datos.

Artículos de interés

Subir