Foros Club Delphi

Foros Club Delphi (https://www.clubdelphi.com/foros/index.php)
-   Trucos (https://www.clubdelphi.com/foros/forumdisplay.php?f=52)
-   -   Colecciones y persistencia (https://www.clubdelphi.com/foros/showthread.php?t=80498)

marcoszorrilla 29-06-2006 23:46:59

Colecciones y persistencia
 
// A pesar de su sencillez, uno de los aspectos menos comprendidos de la Programación
// de Componentes para Delphi es el uso de colecciones (collections). Hace poco descargué
// desde Internet un componente freeware para mejorar el trabajo con rejillas, al estilo
// de TStringGrid. El componente funcionaba bastante bien; de hecho, estaba inspirado en
// el TDBGrid oficial de Delphi, como reconocía el autor. El problema sobrevino cuando
// intenté configurar las columna de la nueva rejilla en tiempo de diseño: aunque el
// componente tenía una propiedad Columns, no estaba publicada para tiempo de diseño, y
// había que configurar toda esta información "a mano", en tiempo de ejecución. El autor
// se había cepillado todo el código relacionado con el almacenamiento y edición de la
// información vectorial de columnas. Claro, el código utilizaba esas
// "misteriosas colecciones"...

// COLECCIONES Y ELEMENTOS

// Si usted quiere crear una propiedad que almacene un número variable de objetos
// relacionados, es muy probable que lo que necesite sea una colección. ¿Una colección?
// ¿Por qué no una lista TList? Muy sencillo: TList desciende directamente de TObject,
// mientras que TCollection desciende de TPersistent, por lo que ya trae incorporado las
// técnicas básicas para que toda la colección o lista de elementos pueda almacenarse en
// disco, o en cualquier flujo de datos (stream) en general. Basta con que definamos una
// propiedad, cuyo tipo esté basado en TCollection, en la sección published de un componente
// para que Delphi o C++ Builder puedan almacenar sus datos en un DFM, y para que la
// propiedad aparezca en el Inspector de Objetos, del mismo modo que si se tratase de un
// vulgar Integer o String.

// La primera peculiaridad que observamos cuando trabajamos con colecciones es la forma
// en que se crean. Esta es la definición del constructor:
Código Delphi [-]
constructor TCollection.Create(ItemClass: TCollectionItemClass);
// Recuerde que TCollection desciende de TPersistent, no de TComponent, por lo cual no
// está obligada a tener un "propietario" (owner). El parámetro del constructor es una
// referencia de clase, y debe contener el nombre de una clase derivada de TCollectionItem.
// Esto quiere decir que la colección sabe de antemano qué tipo de objetos va a alojar.
// Ya en esto se diferencia de TList, que permite guardar punteros arbitrarios.

// El próximo paso es saber cómo está definida
Código Delphi [-]
TCollectionItemClass:

type
  TCollectionItem = class(TPersistent)
    // Etcétera
  end;

  TCollectionItemClass = class of TCollectionItem;
// Esto quiere decir que los elementos que podemos almacenar dentro de una colección deben
// pertenecer a alguna clase derivada de TCollectionItem. Por ejemplo, no podemos almacenar
// objetos TTable directamente en una colección. Por el contrario, debemos crear una clase
// auxiliar TTableItem, que herede de TCollectionItem, y que apunte internamente a una tabla.

// ELEMENTOS DE COLECCIONES Y PERSISTENCIA

// Recuerde que el objetivo final de todo esto es poder almacenar listas de elementos más
// o menos arbitrarios en los ficheros DFM. Usted puede directamente crear propiedades que
// apunten a tablas (TTable) y, como esta clase deriva de TPersistent, Delphi puede
// automáticamente almacenar en un DFM los datos necesarios para reconstruir un objeto de
// esta clase. Más exactamente, lo que Delphi almacena en el DFM son los valores de las
// propiedades published del objeto. Se supone que estas son las propiedades
// "características" de la instancia.

// Por lo tanto, Delphi también sabe cómo guardar las propiedades que definamos como
// published en una clase derivada de TCollectionItem. Si queremos guardar listas de
// punteros a tablas, necesitamos una clase auxiliar definida de este modo:
Código Delphi [-]
type
  TTableItem = class(TCollectionItem)
  private
    FTable: TTable; // Recuerde que esto es un puntero
    procedure SetTable(Value: TTable);
    // Etcétera ...
  published
    property Table: TTable read FTable write SetTable;
  end;
// Cuando Delphi vaya a guardar una colección de estos objetos, recorrerá uno a uno los
// elementos de la colección, y los irá guardando individualmente. Para este objeto en
// particular, guardará el valor de la propiedad Table. Esta propiedad, que es concretamente
// un puntero a un componente, se almacena en el DFM utilizando el nombre del componente
// apuntado, junto al nombre del formulario o módulo en que se encuentra, si no está en
// el mismo formulario o módulo que el componente que contiene a la colección.

// AYUDANDO AL INSPECTOR DE OBJETOS

// Sin embargo, no terminan aquí nuestras responsabilidades. Es muy importante que
// redefinamos el método virtual Assign. De este modo, Delphi sabrá cómo copiar las
// propiedades importantes desde un elemento a otro. El código que viene a continuación
// sirve de muestra:
Código Delphi [-]
procedure TTableItem.Assign(Source: TPersistent);
begin
  if Source is TTableItem then
    // Una sola propiedad a copiar
    Table := TTableItem(Source)
  else
    inherited Assign(Source);
end;
// Es posible también que queramos redefinir el método GetDisplayName:
Código Delphi [-]
function TTableItem.GetDisplayName: string;
begin
  if Assigned(FTable) then
    Result := FTable.Name
  else
    Result := inherited GetDisplayName;
end;
// ¿Se ha fijado en que cuando crea una columna para una rejilla de datos, la columna
// aparece en el Editor de Columnas con el título TColumn, mientras que cuando a la
// columna le asigna uno de los campos del conjunto de datos asociado en el Editor de
// Columnas aparece el nombre del campo? Pues ése es el objetivo de GetDisplayName:
// indicar con qué nombre aparecerá el elemento de la colección al editarlo con ayuda
// del Inspector de Objetos.

// Por último, si necesita un constructor para realizar alguna inicialización especial,
// tenga en cuenta que el constructor de TCollectionItem es un constructor virtual, y
// que no puede modificar sus parámetros. Esta es su declaración:

constructor TCollectionItem.Create(Collection: TCollection); virtual;

// ¿Se da cuenta de que al constructor se le pasa la colección a la cuál va a pertenecer
// el nuevo elemento? de este modo, al crear un elemento estaremos automáticamente
// insertándolo en su colección.

// RETOCANDO LA COLECCIÓN

// ¿Y qué hay de la colección en sí? ¿Podemos declarar directamente propiedades de tipo
// TCollection? Pues casi, porque tenemos que ocuparnos solamente de un detalle: hay que
// redefinir el método GetOwner. Este método se utiliza en dos subsistemas diferentes
// de Delphi. En primer lugar, lo utiliza la función GetNamePath, que es a su vez utilizada
// por el Inspector de Objetos para nombrar correctamente a los elementos durante su edición.
// Volviendo al ejemplo del TDBGrid, cuando estamos modificando las propiedades de una
// columna, en el Inspector de Objetos aparece un nombre como el siguiente para la columna
// seleccionada:

DBGrid1.Columns[1]
// DBGrid1 es el objeto que contiene a la colección, su propietario. Este propietario
// es también utilizado por el algoritmo que se utiliza en la grabación del componente
// en el DFM (podéis encontrar más detalles acerca de este proceso, en general, en el
// libro Secrets of Delphi 2, de Ray Lischner).

// De todos modos, si solamente tenemos que redefinir el propietario de la colección,
// basta con utilizar la clase TOwnedCollection, cuyo constructor tiene el siguiente
// prototipo:
Código Delphi [-]
constructor TOwnerCollection.Create(AOwner: TComponent;
  ItemClass: TCollectionItemClass);

// Sin embargo, si vamos a programar bastante con la colección, es preferible derivar
// una clase a partir de TCollection y, además de redefinir GetOwner, introducir una
// propiedad vectorial por omisión, de nombre Items. TCollection ya tiene una propiedad
// con este nombre:
Código Delphi [-]
property TCollection.Items[I: Integer]: TCollectionItem;
// Pero como notará, el tipo de objeto que devuelve es el tipo general TCollectionItem,
// lo que nos obligará a utilizar constantemente la conversión de tipos. Además, esta
// propiedad no es la propiedad vectorial por omisión de la clase. Para acceder a una
// de las tablas de la lista de tablas de nuestro hipotético componente, tendríamos
// que teclear una y otra vez cosas como ésta:
Código Delphi [-]
TTableItem(MiComponente.Tablas.Items[1]).Table.Open;
// Así que es frecuente que ocultemos la antigua propiedad Items en la nueva clase del
// siguiente modo:
Código Delphi [-]
type
  TTableCollection = class(TCollection)
  private
    function GetItems(I: Integer): TTable;
    procedure SetItems(I: Integer; Value: TTable);
    // Etcétera ...
  public
    property Items[I: Integer]: TTable read GetItems write SetItems;
      default;
  end;

function TTableCollection.GetItems(I: Integer): TTable;
begin
  Result := TTableItem(inherited Items[i]).Table;
end;

procedure TTableCollection.SetItems(I: Integer; Value: TTable);
begin
  TTableItems(inherited Items[i]).Table := Value;
end;
// Ahora podemos abreviar la instrucción que hemos mostrado antes de este modo:
Código Delphi [-]
MiComponente.Tablas[1].Open;
// Como en nuestro ejemplo lo importante era la tabla, y no el TTableItem, hemos hecho
// que Items devuelva la tabla asociada al elemento. Es más común, no obstante, que
// devuelva el puntero a todo el elemento.

// INTEGRANDO LA COLECCIÓN EN EL COMPONENTE

// Por supuesto, falta incorporar a la nueva clase dentro de nuestro componente, pero
// esto es muy sencillo. Sólo tiene que recordar que la colección será propiedad del
// componente, y que esto implica:

// El componente debe construir una colección internamente en el constructor, y
// destruirla en su destructor.
// El método de acceso a la propiedad para escritura debe utilizar Assign para copiar
// la colección que se asigna externamente a la colección interna mantenida por el componente.

// Existen, claro está, muchas más técnicas que podemos aprovechar en relación con las
// colecciones. Por ejemplo, podemos redefinir el método Update de la colección, para
// que ésta notifique a su propietario cuando se produzcan cambios en las propiedades
// de algunos de sus elementos.

// COMPARACION DE COLECCIONES

// Para terminar este artículo, quiero presentarle una técnica interesante:
// ¿sabe usted cómo Delphi y C++ Builder comparan dos colecciones entre sí, para ver
// si son iguales? La unidad Classes define con este propósito la siguiente función:

function CollectionsEqual(C1, C2: TCollection): Boolean;

// La técnica utilizada por CollectionsEqual consiste en guardar las dos colecciones
// en un flujo de datos en memoria (TMemoryStream). Luego obtiene los dos punteros a
// ambas zonas de memoria, ¡y realiza una comparación binaria entre estas dos! Dicho
// de otra forma: "aplana" los dos objetos complejos, y compara sus representaciones
// binarias


La franja horaria es GMT +2. Ahora son las 01:09:46.

Powered by vBulletin® Version 3.6.8
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Traducción al castellano por el equipo de moderadores del Club Delphi