FTP | CCD | Buscar | Trucos | Trabajo | Foros |
|
Registrarse | FAQ | Miembros | Calendario | Guía de estilo | Temas de Hoy |
|
Herramientas | Buscar en Tema | Desplegado |
#1
|
||||
|
||||
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: // 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 // 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: // 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: // Es posible también que queramos redefinir el método GetDisplayName: // ¿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:
// 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: // 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: // Así que es frecuente que ocultemos la antigua propiedad Items en la nueva clase del // siguiente modo: // Ahora podemos abreviar la instrucción que hemos mostrado antes de este modo: // 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 |
|
|
|