Foros Club Delphi

Foros Club Delphi (https://www.clubdelphi.com/foros/index.php)
-   OOP (https://www.clubdelphi.com/foros/forumdisplay.php?f=5)
-   -   [RESUELTO] Guardar estructura de memoria (clases) a disco (https://www.clubdelphi.com/foros/showthread.php?t=82429)

Neftali [Germán.Estévez] 06-03-2013 19:59:13

[RESUELTO] Guardar estructura de memoria (clases) a disco
 
Buenas a todos.
No se si el título explica claramente lo que necesito hacer, así que intentaré explicarme un poco mejor.

Se trata de que tengo en memoria una serie de datos almacenados en clases y listas de elementos que debo exportar a disco. Es una estructura compleja, por ejemplo una clase A, que posee propiedades (algunas enteros y otras cadenas) y una lista de elementos de la clase B. A su vez la clase B posee algún ENTERO, algún STRING y una lista de elementos de la Clase C. La clase C a su vez son una lista de PUNTOS y alguna propiedad más.

La estructura no es exactamente así, pero es bastante aproximada para que os hagáis una idea. La representaríamos como se ve a continuación.

Código Delphi [-]

Clase A
==============
prop1: integer
prop2: integer
prop3:string
prop4: List of ClaseB
...

Clase B
=============
prop1: boolean
prop2: string
prop3: integer
prop4: List of claseC
...

Clase C
==============
prop1: List of Tpoint
prop2:Tcolor
...

Ya tengo una o dos ideas para almacenarlo en disco (BACKUP) y después poder recuperarlo (RESTORE), pero la verdad es que ninguna de ellas me resulta sencilla.
Ya sea guardar en un formato tipo XML/JSON/... o incluso guardarlos en ficheros planos y empaquetarlo todo en un ZIP.

El caso es que estaba dándole vueltas a si habría alguna manera más fácil de hacerlo que no signifique recorrer todos los elementos de todas las clases. Algo así como el WriteComponent de la clase TStream. Algo que me permita "volcar" toda la clase y recuperarla de una sóla vez.
A lo mejor estoy "desvariando"... Si es así no me hagáis caso... :o:o:o

Se aceptan ideas, sugerencias, críticas,... ;)

P.D: No necesito código sólo una idea/orientación.

roman 06-03-2013 20:08:53

Quizá si derivas de TComponent/TCollectionItem y usas TCollection para las listas puedas usar WriteComponent y ReadComponent.

// Saludos

dec 06-03-2013 20:16:09

Hola,

O lo ya apuntado o usar RTTI de forma más o menos similar a la explicada en este artículo. Reconozco que ahora mismo no se me ocurre otra forma. Quizá que tratando de hacerlo con "WriteComponent" y "ReadComponent" el asunto pueda resultar algo más rápido de implementar (digo yo...) y también más rápido a la hora de realizar la tarea en cuestión.

Neftali [Germán.Estévez] 06-03-2013 20:32:46

Cita:

Empezado por roman (Mensaje 456109)
Quizá si derivas de TComponent/TCollectionItem y usas TCollection para las listas puedas usar WriteComponent y ReadComponent.

Después de escribir lo del WriteComponent lo he pensado, pero hay también algún record.
De todas formas si fuera por eso estos records se podrían convertir a otra cosa.

Le doy una vuelta Román.
Gracias.

Neftali [Germán.Estévez] 06-03-2013 20:37:11

Cita:

Empezado por dec (Mensaje 456113)
O lo ya apuntado o usar RTTI de forma más o menos similar a la explicada en este artículo.

Hola David, gracias por el link.
Lo he ojeado por encima y tiene buena pinta aunque no se si cubre lo que necesito. ¿Qué pasaría con las clases anidadas?
No veo claro que con eso se puedan añadir al mismo fichero; Y me queda también la duda de las listas.

Al González 06-03-2013 20:55:51

Hola Germán.

Cita:

Empezado por roman (Mensaje 456109)
Quizá si derivas de TComponent/TCollectionItem y usas TCollection para las listas puedas usar WriteComponent y ReadComponent.

Este que comenta Román sería el camino más directo, pero supongo que por una buena razón esas clases no derivan de TComponent. Relacionado con esto, ya has de saber que la clase padre de TComponent, TPersistent, es la primera en introducir mecanismos de streaming:
Cita:

Use TPersistent as a base class when declaring objects that are not components, but that need to be saved to a stream or have their properties assigned to other objects.
Por otro lado, sea cual sea el mecanismo que uses, es menester que la información de las propiedades esté disponible mediante reflexión (RTTI), lo cual hace que tengas que declarar dichas propiedades con visibilidad publicada (published).

Eso último se flexibiliza a partir de Delphi 2010, y el artículo que refiere dec va muy bien con el tema. ^\||/

Ya nos dirás más, saludos. :)

Neftali [Germán.Estévez] 06-03-2013 21:07:09

Le voy a dar otra vuelta al artículo, porque me parece que la primera vez lo he mirado "demasiado por encima"... :o

En cuanto a porqué las clases no derivaban de TComponent, es simplemente porque no son componentes. :D
En este caso no habría ningún problema en derivarlas de TPersistent si eso me aporta beneficios y tampoco lo hay en cuanto a la visibilidad de las propiedades, pues ahora son públicas y no hay problema en convertirlas a published para añadir datos de RTTI.

A ver si mañana puedo hacer alguna prueba para evaluar ambas soluciones y os comento...

Gracias.

roman 06-03-2013 21:13:44

Cita:

Empezado por Al González (Mensaje 456118)
Relacionado con esto, ya has de saber que la clase padre de TComponent, TPersistent, es la primera en introducir mecanismos de streaming:

Sí lo sabía pero oprimí Enviar muy rápido :) De hecho, TCollectionItem y TCollection derivan directamente de TPersistent.

// Saludos

Al González 06-03-2013 21:24:17

Sobre los objetos anidados, no hay más camino que recorrer todos los elementos, entrando a cada propiedad objeto o lista de objetos. Eso ya lo hace WriteComponent. Pero si quieres implementar un mecanismo propio que evite la derivación de TComponent, entonces puedes echar una mirada a los métodos WriteProperties y WriteProperty de la clase nativa TWriter. Aunque en lo personal y teniendo Delphi 2010 o superior, buscaría en él algo de la RTTI extendida que facilite las cosas. :) ^\||/

Lo de la "buena razón", ni qué decir...gajes del oficio de abreviar. :p

dec 06-03-2013 21:40:18

Hola,

Al, por lo poco que sé, me parece que, a partir de Delphi 2010, no es necesario para trabajar con RTTI declarar propiedades "published", puesto que antes de Delphi 2010 sí que era como dices. Germán, me extraña que no tengas tú sobrada experiencia recorriendo objetos y posibles propiedades luego de tu proyecto GLibWMI. Creo recordar que también te toca lidiar con diferentes tipos de propiedades y resultados, etc. Pienso que no es exactamente igual, pero, es parecido. Además, es posible que no tengas que construir todo un "Serializador de objetos", sino algo que te apañe de momento, al menos. ;)

Al González 06-03-2013 23:54:48

Cita:

Empezado por Al González (Mensaje 456118)
[...] sea cual sea el mecanismo que uses, es menester que la información de las propiedades esté disponible mediante reflexión (RTTI), lo cual hace que tengas que declarar dichas propiedades con visibilidad publicada (published).

Eso último se flexibiliza a partir de Delphi 2010, y el artículo que refiere dec va muy bien con el tema. ^\||/

Cita:

Empezado por dec (Mensaje 456126)
Al, por lo poco que sé, me parece que, a partir de Delphi 2010, no es necesario para trabajar con RTTI declarar propiedades "published", puesto que antes de Delphi 2010 sí que era como dices.

Tienes razón, David. Por eso lo de: «Eso último se flexibiliza a partir de Delphi 2010». :)

Delphius 07-03-2013 04:43:25

Sea cual fuese el camino que elijas Neftali, todo te llevará a hacer uso de RTTI... es inevitable.
Ya sea que derives de TPersistent, TComponent y/o diseñes tus propios métodos (fuera de la jerarquía de clases de las anteriores) para llevar a cabo esto.. te toparás con RTTI.
No conozco el método WriteComponent que comentan pero estoy casi segurísimo que por dentro todo, de uno u otro modo, lo que hace es valerse de la RTTI (ya sea la básica, o la extendida desde la salida de 2010) para tener acceso a las propiedades del componente para guardar los datos en algún archivo.

Lo que habría que estudiar es el problema que pudiera afectar al anidamiento. Partamos de lo fundamental ¿Que sucede, por defecto, con el writeComponent cuando uno guarda el objeto? En base a eso ya se podría saber si es viable esta opción... O si se deberá irse a los palos e ingeniárselas para implementar algo propio que vaya leyendo el objeto en cuestión y definir algún formato/estructura propia para el archivo.

La otra posibilidad es ver si de por casualidad no hay algún motor de persistencia que tenga la posibilidad de "exportar" a archivos los objetos y no a bases de datos.

Saludos,

roman 07-03-2013 04:51:14

Cita:

Empezado por Delphius (Mensaje 456149)
Sea cual fuese el camino que elijas Neftali, todo te llevará a hacer uso de RTTI... es inevitable.

Bueno, yo creo que esto es demasiado tajante. No veo porqué no pueda volcarse la estructura en un archivo XML, por ejemplo, sin utilizar ni un gramo de RTTI. Otra cosa es que [muy] posiblemente el uso de RTTI pueda facilitar el trabajo.

// Saludos

Neftali [Germán.Estévez] 07-03-2013 09:01:26

Cita:

Empezado por dec (Mensaje 456126)
Germán, me extraña que no tengas tú sobrada experiencia recorriendo objetos y posibles propiedades luego de tu proyecto GLibWMI.
...Además, es posible que no tengas que construir todo un "Serializador de objetos", sino algo que te apañe de momento, al menos. ;)

Si no es por no ir, si hay que ir, se va... :p
Pero es que me daba mandra y me entró la curiosidad...

Al final estoy viendo que es bastante problabe que tenga que hacer algún recorrido.

Delphius 07-03-2013 15:41:09

Cita:

Empezado por roman (Mensaje 456150)
Bueno, yo creo que esto es demasiado tajante. No veo porqué no pueda volcarse la estructura en un archivo XML, por ejemplo, sin utilizar ni un gramo de RTTI. Otra cosa es que [muy] posiblemente el uso de RTTI pueda facilitar el trabajo.

// Saludos

Podrá haber parecido un tanto tajante pero es que a mi modo de ver (y hasta donde llegan mis conocimientos) no hay otra forma.
Si en verdad se puede volcar un objeto, de forma genérica, a un archivo XML (o uno cualquiera) sin usar RTTI ¿puedo pedir una muestra de tu parte?

Porque a menos que el árbol de jerarquía al que se enfrente Neftali sea pequeño y sus clases no tengan demasiada complejidad pues allí si podría imaginarme una alternativa casera, y sin usar RTTI... pero a la larga me sentiría como que estoy haciendo doble (o triple) trabajo.
Mi versión casera sería la siguiente:
1) En la clase base de mis clases "persistentes" (si es que la hay) declaro un método Materialize abstracto.
2) Luego en cada clase concreta le doy la implementación adecuada y me pongo como loco, propiedad a propiedad, o atributo por atributo a pasar los datos al archivo. Algo como:

Código Delphi [-]
writeln('NombrePropiedadN');
writeln(PropiedadN);
writeln('NombrePropiedadN');
writeln('NombrePropiedadMTipoRegistro');
writeln('NombreCampoZDeLaPropiedadMdeTipoRegistro');
write(PropiedadMTipoRegistro.CampoZ);

Y si... no hay un gramo de RTTI pero, ¿En serio consideras algo práctico esto? Y Mira que entiendo que podría ser una salida muy fácil y simple (y hasta cierto punto, "económica")... ¡De veras estoy intentandolo llegar al principio KIS! :D

De poder se puede... pero, hasta donde tengo entendido me parece que esto es justamente lo que quiere evitar Neftali y busca algo que sea más genérico y le haga más directa la cosa. De allí que a que yo diga... "Pos, no queda otra... todo apunta a roma". ;)

Saludos,
PD: No se porqué pero si escribo dentro de las etiquetas delphi las < y /> me elimina el texto interno. Seguramente es por una cuestión de seguridad ;) En mi código debiera leerse '<Algo>' y '</Algo>'. Como si estuviera escribiendo un XML a los pelos.

ecfisa 07-03-2013 16:10:29

Cita:

Empezado por Delphius (Mensaje 456187)
...
PD: No se porqué pero si escribo dentro de las etiquetas delphi las < y /> me elimina el texto interno. Seguramente es por una cuestión de seguridad ;) En mi código debiera leerse '<Algo>' y '</Algo>'. Como si estuviera escribiendo un XML a los pelos.

Hola Delphius.

Se debe a un problema existente en la interface del editor de mensajes cuando tenes seleccionado "Interfaz Mejorada - Edición con WYSIWYG". También suele descolocar los códigos Delphi en el mensaje cuando haces "Vista previa del Mensaje" antes de publicarlo.

Por ese motivo me he acostumbrado a usar el modo "Editor Estándar - Controles de Formato Extra", que aunque un tanto espartano, no presenta ningún problema.

Saludos. :)

Neftali [Germán.Estévez] 07-03-2013 16:55:28

Estoy en ello.... En breve espero presentar resultados.
Mi intención es intentar conseguirlo haciendo lo que os comenté; Utilizando los métodos que trae Delphi para volcar componentes en un Stream y recuperarlos.

Se puede hacer, como bien habéis dicho volcando a XML (o JSON) haciendo un "parser" (ya sea específico para estas clases o genérico para todas -que viene a ser algo similar al link que me puso David al principio-), pero como también comenté, la gracia está en conseguirlo sin eso.

Un saludo.

roman 07-03-2013 18:23:57

Cita:

Empezado por Delphius (Mensaje 456187)
Podrá haber parecido un tanto tajante pero es que a mi modo de ver (y hasta donde llegan mis conocimientos) no hay otra forma.

Veamos...

Cita:

Empezado por Delphius (Mensaje 456187)
Si en verdad se puede volcar un objeto, de forma genérica, a un archivo XML (o uno cualquiera) sin usar RTTI ¿puedo pedir una muestra de tu parte?

¿Quién habló de genericidad? Hasta donde colijo de lo decrito por Neftalí en un principio, se trata de clases específicas de su aplicación.

Cita:

Empezado por Delphius (Mensaje 456187)
Porque a menos que el árbol de jerarquía al que se enfrente Neftali sea pequeño y sus clases no tengan demasiada complejidad pues allí si podría imaginarme una alternativa casera, y sin usar RTTI... pero a la larga me sentiría como que estoy haciendo doble (o triple) trabajo.

¿Acaso mencioné que sería fácil? Justamente mencioné que otra cosa sería que el uso de RTTI pueda facilitar el trabajo.

Cita:

Empezado por Delphius (Mensaje 456187)
Mi versión casera sería la siguiente:
1) En la clase base de mis clases "persistentes" (si es que la hay) declaro un método Materialize abstracto.
2) Luego en cada clase concreta le doy la implementación adecuada y me pongo como loco, propiedad a propiedad, o atributo por atributo a pasar los datos al archivo. Algo como:

Código Delphi [-]
writeln('NombrePropiedadN');
writeln(PropiedadN);
writeln('NombrePropiedadN');
writeln('NombrePropiedadMTipoRegistro');
writeln('NombreCampoZDeLaPropiedadMdeTipoRegistro');
write(PropiedadMTipoRegistro.CampoZ);

No entiendo por qué hay que ponerse como loco. Para eso se programan las cosas. Digo, creo que recorrer estructuras de datos tampoco es que sea uno de los doce trabajos de Hércules.

Cita:

Empezado por Delphius (Mensaje 456187)
Y si... no hay un gramo de RTTI pero, ¿En serio consideras algo práctico esto? Y Mira que entiendo que podría ser una salida muy fácil y simple (y hasta cierto punto, "económica")... ¡De veras estoy intentandolo llegar al principio KIS! :D

¿En qué momento dije que fuera práctico? De todas formas habría que ver el problema específico para determinar su grado de practicidad. Que tampoco es que me parezca tarea titánica.

Cita:

Empezado por Delphius (Mensaje 456187)
De poder se puede... pero, hasta donde tengo entendido me parece que esto es justamente lo que quiere evitar Neftali y busca algo que sea más genérico y le haga más directa la cosa. De allí que a que yo diga... "Pos, no queda otra... todo apunta a roma". ;)

¡Ah! Bueno, pero es muy distinto decir "Sea cual fuese el camino que elijas Neftali, todo te llevará a hacer uso de RTTI... es inevitable."

a matizarlo con "me parece que esto es justamente lo que quiere evitar Neftali y busca algo que sea más genérico y le haga más directa la cosa."

// Saludos

Delphius 07-03-2013 20:14:03

A ver Roman, yo releo el hilo y lo que estoy entendiendo es que si bien su diseño parece obedecer a algo como:

ClaseA <>--- ClaseB <>--- ClaseC <>--- TPoint

El mismo señala que es una aproximación y no una idea de que son únicamente 3 clases.

Creo que si vino con la duda es porque el trabajo no es tan directo, y si digo que al final va a llegar a trabajar con RTTI o que los caminos conducen a Roma es porque justamente si la intención es evitar algunas opciones, las que quedan de una u otra llevan a otro camino que igualmente no lleva a una solución tan directa como para hacer el trabajo de un tirón a como le gustaría.
Que será trabajoso seguro.

Y si te pedí un ejemplo que no fuera RTTI lo decía con buena manera, y yo me imaginaba algo que no fuera algo como lo que "pseudo" propuse. Te juro que quizá me imaginé que tenías (y quizá si lo tienes) algún haz en tu manga que sea NO-RTTI y sin llegar a ese enjendro que he propuesto. De allí que te preguntaba si tienes algo en mente.
En mi propuesta es lo más KIS que me vino :( ... Yo no te lo decía a ti en forma despectiva.
¿Hay algo más simple? ¿Que trucos tienes? En verdad quisiera saberlo; que me pica la curiosidad... tu me conoces :D :p . Yo no lo decía en mal plan. Es que me asombró que tu digas de que se puede. En serio, yo al leer tus palabras me dije: "¡No jodas! ¿Y sin hacer el mavarracho que me mandé?. Tengo que saberlo"

Si yo tuviera que implementar algo como mi propuesta, al llegar a la 4ta clase me volvería loco (más de lo que estoy). Aún para clases con pocos atributos. Es un diseño que llevaría muy posiblemente a metidas de dedos y problemas que vaya a saber cuando se los descubriría... si te digo que hasta confundo filas con columnas de una matriz al implementar algunas funciones algebraicas. :o

Esto me llevó al planteo de la posibilidad de si se puede contar con alguna forma genérica de llevar esto. Mis pensamientos me decían de que podría concebirse de alguna clase tipo XML-Parser que cuente con la funcionalidad de escribir/leer un XML de forma más cómoda. Algo como:
Código Delphi [-]
XMLParser.writeNode(NombreEtiqueta, Valor);

Pero inmediatamente me dije, segurísimo que ya existe una... creo que hasta tiene un nombre parecido. ¿Por casualidad no hay ya un TXMLParser en la VCL? No tengo Delphi a mano, como para comprobarlo.

Y bueno, a pesar de esta clase... mi cabeza me sigue diciendo que aún así no está resuelta la pregunta. ¿Y ahora? Me mando un método y pongo tanto "WriteNode" o "WriteSubNode" como propiedades tenga? Esa solución me lleva a que me estoy complicando, mejor tiro a RTTI y "automatizo".

Saludos,

roman 07-03-2013 20:45:56

Cita:

Empezado por Delphius (Mensaje 456231)
El mismo señala que es una aproximación y no una idea de que son únicamente 3 clases.

Eso no indica que quier hacer algo genérico, simplemente que no pone todos los detalles. Si las clases son conocidas por él de antemano no veo porqué no ha de poder hacerse un volcado a XML que, justamente, es una estructura jerárquica.

¿Código? No. No tengo ni una sóla línea. Pero estoy seguro que lo podría hacer. Claro, no sin antes buscar alguna opción que facilite las cosas, tal cómo hace Neftalí :).

Y si lo hiciera con XML, desde luego usaría algo hecho para crear/leer archivos xml, no reinventaría la rueda.

// Saludos

Neftali [Germán.Estévez] 08-03-2013 17:45:17

Buenas a todos.

Pues ya están las pruebas realizadas y la verdad con bastante buen resultado (al menos para lo que yo pretendía).
La explicación más extensa (con todo el código incluido y el ejemplo) la he añadido a una entrada en mi blob(Persistencia de una estructura de clases), donde está explicada con todos los detalles. De todas formas, ya que aquí salió la pregunta es lógico que explique los resultados.

La conclusión en pocas palabras es: ¡¡¡QUE SI SE PUEDE!!!

Como ya comenté se trataba de intentar guardar y restaurar el contenido de una estructura de clases en memoria sin tener que programar nada sobre las propias clases, para realizar este Backup/restore. La solución que intentaba probar, era la de utilizar los métodos que Delphi usa para guardar el contenido de un formulario en disco (en formato del DFM).

La clave está en la utilización de las clases TCollection y TCollectionItem para las clases basadas en listas (que era lo que más dificultad se me antojaba que tendría).
Tal y como comento en la entrada del blog, he tenido que modificar algo la definición de las clases (manteniendo la misma estructura) para conseguir que el proceso funcionara.
  • Alguna de las clases han pasado a derivar de TCollectionItem y otras de TCollection, básicamente para cambiar las lista con la que anteriormente trabajaba por colecciones. Ha afectado muy poco a la implementación, ya que sólo he tenido que cambiar algun método de acceso.
  • Las propiedades definidas en las clases que queremos almacenar hay que pasarlas de la sección public a la sección published.
  • La clase principal deriva ahora de TComponent para poder añadirle “persistencia”.
  • Como ya he comentado, algunos pequeños cambios en la implementación, necesarios para adecuar código a los cambios de definición, pero que no han sido nada importantes.
  • Por último añadir el registro de las clase utilizando el procedimiento RegisterClass.

Os adjunto cómo ha quedado la definición definitiva de las clases.

Código Delphi [-]
  TTrackData = class;
  TPointInfo = class;
  TTrack = class;

  {Clase para encapsular la información de un punto.}
  TPointInfo = class(TCollectionItem)
  private
    FEle: string;
    FLon: string;
    FLat: string;
    FTime: string;
    FLatF: Double;
    FLonF: Double;
  public
    // constructor de la clase
    constructor Create(ACol:TCollection; ALat, ALon, AEle, ATime: string;
                       ALatF, ALonF : Double); overload; virtual;

    function _debug(TS:TStrings):string; virtual;
  published
    property Lat:string read FLat write FLat;
    property Lon:string read FLon write FLon;
    property Ele:string read FEle write FEle;
    property Time:string read FTime write FTime;

    property LatF : Double read FLatF write FLatF;
    property LonF : Double read FLonF write FLonF;
  end;

  {: Clase para encapsular la información de un WayPoint.}
  TWayPoint = class(TPointInfo)
  private
    FNombre: string;
    FDesc: string;
    FSimbolo: string;
  public
    // constructor de la clase
    constructor Create(ATrack:TTrackData;
                       AWPNombre, AWPDesc:string;
                       ASimbolo:string;
                       ALat, ALon, AEle, ATime: string;
                       ALatF, ALonF: Double); overload;

    function _debug(TS:TStrings):string; override;
  published
    property Nombre:string read FNombre write FNombre;
    property Desc:string read FDesc write FDesc;
    property Simbolo:string read FSimbolo write FSimbolo;
  end;


  {: Clase para almacenar una lista de puntos.}
  TPointList = Class(TCollection)
  private
    function GetPoint(index: integer): TPointInfo;
  public
    procedure AddPoint(pointInfo:TPointInfo); overload;
    procedure AddPoint(ALat, ALon, AEle, ATime: string;
                       ALatF, ALonF:Double); overload;
    property Point[index:integer]:TPointInfo read GetPoint;
  published
    procedure _debug(TS:TStrings);
    procedure _debugCount(TS:TStrings);

  end;

  TTrack = class(TCollectionItem)
  private
    FTrackPoints: TPointList;
    FPaintColor: Integer;
    FPaintWidth: Integer;
    FTrackName: string;
  public
    // constructor de la clase
    constructor Create(Collection: TCollection); override;
    destructor Destroy; override;
    procedure _debug(TS:TStrings);
  published
    property TrackPoints:TPointList read FTrackPoints write FTrackPoints;
    property TrackName:string read FTrackName write FTrackName;
    property PaintWidth:Integer read FPaintWidth write FPaintWidth;
    property PaintColor:Integer read FPaintColor write FPaintColor;
  end;
  {: Clase para encapsular datos de un segmento que forma un track.}
  TTrackList = class (TCollection)
  private
  public
    constructor Create(ItemClass: TCollectionItemClass);
  end;

  {: Clase para encapsular los Waypoints de un Track.}
  TWayPointList = class (TCollection)
  private
  public
    procedure _debug(TS:TStrings);
  end;

  {: Clase para encapsular toda la información de un track.}
  TTrackData = class(TComponent)
  private
    FVersion: string;
    FXsi: string;
    FMaxLon: string;
    FMaxLat: string;
    FCreator: string;
    FHRef: string;
    FTime: string;
    FMinLon: string;
    FMinLat: string;
    FText: string;
    FTrackList: TTrackList;
    FWayPointList: TWaypointList;
  public
    // Limpiar el contenido de la clase
    procedure Clear;
    // constructor de la clase
    constructor Create(AOwner: TComponent); override;
    destructor Destroy();
  published
    // Datos
    property Creator:string read FCreator write FCreator;
    property Version:string read FVersion write FVersion;
    property Xsi:string read FXsi write FXsi;
    property HRef:string read FHRef write FHRef;
    property Text:string read FText write FText;
    property Time:string read FTime write FTime;
    property MinLat:string read FMinLat write FMinLat;
    property MinLon:string read FMinLon write FMinLon;
    property MaxLat:string read FMaxLat write FMaxLat;
    property MaxLon:string read FMaxLon write FMaxLon;

    // Lista de tracks
    property TrackList:TTrackList read FTrackList write FTrackList;
    // Lista de WayPoints
    property WayPointList:TWaypointList read FWayPointList write FWayPointList;
  end;


Utilizando un par de procedimientos extraídos de la propia ayuda de embarcadero, que los muestra como código de ejemplo de la clase TMemoryStream, me ha bastado para realizar las dos accciones (Guardar y Restaurar).

Código Delphi [-]
function ComponentToStringProc(Component: TComponent): string;
var
  BinStream:TMemoryStream;
  StrStream: TStringStream;
  s: string;
begin
  BinStream := TMemoryStream.Create;
  try
    StrStream := TStringStream.Create(s);
    try
      BinStream.WriteComponent(Component);
      BinStream.Seek(0, soFromBeginning);
      ObjectBinaryToText(BinStream, StrStream);
      StrStream.Seek(0, soFromBeginning);
      Result:= StrStream.DataString;
    finally
      StrStream.Free;
    end;
  finally
    BinStream.Free
  end;
end;

function StringToComponentProc(Value: string): TComponent;
var
  StrStream:TStringStream;
  BinStream: TMemoryStream;
begin
  StrStream := TStringStream.Create(Value);
  try
    BinStream := TMemoryStream.Create;
    try
      ObjectTextToBinary(StrStream, BinStream);
      BinStream.Seek(0, soFromBeginning);
      Result:= BinStream.ReadComponent(nil);
    finally
      BinStream.Free;
    end;
  finally
    StrStream.Free;
  end;
end;

Con los cambios en las clases y un par de líneas como estas:

Código Delphi [-]
  // convertir la clase (TTrackData) a texto
  Str := ComponentToStringProc(td1)

Y para restaurar el contenido basta con esta:

Código Delphi [-]
  // Restaurar el contenido a un objeto de la clase TTrackData
  td2 := TTrackData(StringToComponentProc(Str));

Con estas líneas he conseguido generar con unos cuantos datos de ejemplo, un árbol como este:


Código Delphi [-]
//---------------------------------------------------------------------------------------------------
object TTrackData
  Creator = '-Neftal'#237'- German'
  Version = 'v.1.0'
  HRef = 'http://neftali.clubdelphi.com'
  Text = 'Importaci'#243'n de tracks'
  Time = '01/01/2013 08:00:00'
  TrackList = <
    item
      TrackPoints = <
        item
          Lat = '2.11'
          Lon = '4.23'
          Ele = '120'
        end
        item
          Lat = '2.111'
          Lon = '4.333'
          Ele = '120'
          Time = '01/01/2013 08:00:00'
          LatF = 2.111
          LonF = 4.333
        end
        item
          Lat = '2.234'
          Lon = '4.123'
          Ele = '125'
          Time = '02:01/2013 08:00:05'
          LatF = 2.234
          LonF = 4.123
        end>
      TrackName = 'Track 1 -sendero-'
      PaintWidth = 3
      PaintColor = 255
    end
    item
      TrackPoints = <
        item
          Lat = '2.90'
          Lon = '4.55'
          Ele = '320'
        end
        item
          Lat = '2.333'
          Lon = '4.444'
          Ele = '180'
          Time = '11/01/2013 07:00:00'
          LatF = 2.333
          LonF = 4.444
        end
        item
          Lat = '2.666'
          Lon = '4.666'
          Ele = '185'
          Time = '12:01/2013 07:00:05'
          LatF = 2.666
          LonF = 4.666
        end>
      TrackName = 'Track 2 -pista forestal-'
      PaintWidth = 3
      PaintColor = 32768
    end>
  WayPointList = <
    item
      Lat = '2.12'
      Lon = '4.23'
      Nombre = 'waypoint1'
      Desc = 'Waypoint 1 -Inicio-'
    end
    item
      Lat = '2.65'
      Lon = '4.45'
      Nombre = 'waypoint2'
      Desc = 'Waypoint 2 -Final-'
    end>
end
//---------------------------------------------------------------------------------------------------


Nada desdeñable, para haberlo hecho sin ninguna línea de código en las clases.

LA CONCLUSIÓN: Bueno, a parte de que me daba "mandra" generar la implementación (no era esa la verdadera razón de no hacerlo) pues he satisfecho la curiosidad de ver que se podían realizar esta operaciones sin ninguna línea de código extra en las clases. También es importante conocer que el hecho de añadir nuevas propiedades a las clases (si se hace correctamente) implica que el procedimiento sigue funcionando sin ningún cambio, lo que hace que sea un método totalmente flexible.

Otra cuestión a discutir sería si vale la pena la modificación de las clases o la sobrecarga que aporta a estas, el hecho de cambiar la estructura, pero como digo, eso es otro tema a discutir.

Un saludo.

Al González 08-03-2013 18:41:53

Enhorabuena, Germán. :)

Cita:

Empezado por Neftali (Mensaje 456282)
  • La clase principal deriva ahora de TComponent para poder añadirle “persistencia”.

Una buena razón para derivar de TComponent. ;)

Y creo que aumenta un poco el gran valor de esta solución que nos compartes, si añadimos que ya desde Delphi 7 (o quizá desde versiones anteriores) es posible implementarlo.

Un saludo.

roman 08-03-2013 18:49:40

Cita:

Empezado por Neftali (Mensaje 456282)
Utilizando un par de procedimientos extraídos de la propia ayuda de embarcadero, que los muestra como código de ejemplo de la clase TMemoryStream, me ha bastado para realizar las dos accciones (Guardar y Restaurar).

Bueno, bueno, que en casa también hay de dónde cortar tela ¿eh? :p

Una pregunta. ¿Por qué conviertes a string? ¿No podrías guardar directamente a disco usando un FileStream?

// Saludos

Neftali [Germán.Estévez] 08-03-2013 19:08:11

Cita:

Empezado por Al González (Mensaje 456286)
Enhorabuena, Germán. :)
...
Y creo que aumenta un poco el gran valor de esta solución que nos compartes, si añadimos que ya desde Delphi 7 (o quizá desde versiones anteriores) es posible implementarlo.

Gracias.

En concreto esta está realizada en Delphi 6.

Neftali [Germán.Estévez] 08-03-2013 19:10:58

Cita:

Empezado por roman (Mensaje 456287)
Una pregunta. ¿Por qué conviertes a string? ¿No podrías guardar directamente a disco usando un FileStream?

Seguro que sí; En este caso he empezado ya convirtiendo a string desde el principio, porque me interesaba mucho ver la salida e ir comprobando.
Una vez testeado y comprobado, se puede eliminar ese paso a la hora de guardar a disco (salgo que interese que la salida sea "visible" imagino).

roman 08-03-2013 19:20:09

El pasarlo a string deja, además, abierta la puerta para guardar estas serializaciones en una base de datos.

// Saludos

Al González 09-03-2013 18:59:14

Sin demérito de lo ya expuesto, me permito una sosa observación. :)

Noto que declaras varios parámetros String sin usar Const, y es probable que algunos de ellos no sean modificados dentro de la rutina, o pasados por referencia desde ahí. Seguramente ya lo sabías, pero no está de más recordar que eso le genera cierta carga adicional al programa, muy ligera, pero al fin instrucciones de más que pueden ser evitadas con solo declarar dichos parámetros como constantes. Básicamente, si no lo hacemos así, el compilador añade instrucciones máquina para incrementar los contadores de referencias de las cadenas de caracteres.

Veo incluso que la función StringToComponentProc que tomaste de muestra, al igual que su original StringToComponent que está en la ayuda de Delphi, también declara el parámetro Value sin usar Const, siendo que éste no es modificado ni pasado por referencia dentro de ella. Es algo muy raro de ver en otros métodos o rutinas de Borland / Embarcadero, como los que conforman la VCL, por ejemplo.

Saludos.

Neftali [Germán.Estévez] 09-03-2013 20:09:12

Gracias por la observación, Al.


La franja horaria es GMT +2. Ahora son las 18:21:47.

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