Delphi-PRAXiS
Seite 1 von 3  1 23      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Kniffliges Problem beim Serialisieren (https://www.delphipraxis.net/85737-kniffliges-problem-beim-serialisieren.html)

SMALLID 4. Feb 2007 09:20


Kniffliges Problem beim Serialisieren
 
Liste der Anhänge anzeigen (Anzahl: 1)
Hello party people,

also ich bin da auf ein sehr interessantes, wenn auch frustrierendes, und zugleich möglicherweise sogar noch logisches Problem gestoßen, an dem sich jeder gerne versuchen darf :D

Hintergrund: Entwickelt wurde eine MDI-Anwendung. In den jeweiligen MDI-Children werden in zahlreichen Controls Eingaben gemacht, die in ihrer Gesamtheit gespeichert und später wieder geladen werden sollen. Damit ich das nicht in der Manier:

"Schreibe KomponenteX.EigenschaftA in TextDatei t, gehe eine Zeile weiter, Schreibe KomponenteY.EigenschaftB in TextDatei t, gehe ..."

machen muss, habe ich das (auch dank einiger Beispiele von DelphiPraxis :D) so gelöst:

Speichern:
Delphi-Quellcode:
procedure TMainForm.FileSaveAs1Execute(Sender: TObject);
var
  FileStream : TFilestream;
begin
  if SaveDialog1.Execute then
  begin
    FileStream:=TFileStream.Create(SaveDialog1.Filename, fmCreate);
    Try
      FileStream.WriteComponent(MainForm.ActiveMDIChild);
    Finally
      FileStream.Free;
    end;
  end;
end;
Laden:
Delphi-Quellcode:
procedure TMainForm.ToolButton12Click(Sender: TObject);
var
  Child: TForm;
  FileStream : TFileStream;
begin
  if OpenDialog.Execute then
  begin
    FileStream:=TFileStream.Create(OpenDialog.Filename, fmOpenRead);
    Try
      //Eine Instanz eines Unterfensters wird erzeugt
      Child := TMDIChild.Create(MainForm);
      //Alle Enthaltenen Komponenten darin müssen aber erst gelöscht werden,
      //da beim Laden sonst eine Komponente mit demselben Namen schon existieren
      //würde. Ist quick'n'dirty, aber es funktioniert erstmal.
      While Child.ComponentCount > 0 do
      begin
        Child.Components[0].Destroy;
      end;
      //Der Stream mit allen Eigenschaften und Unterkomponenten wird nun in Child hineingelesen
      FileStream.ReadComponent(Child);
    Finally
      FileStream.Free;
    end;
  end;
end;
So das funktioniert auch soweit schon ganz gut.
Nun aber das Problem:
Aus den gesetzten Eigenschaften werden zur Laufzeit (eigene) Komponenten erzeugt (wen's interessiert: es handelt sich dabei um simple Software-Agenten, die miteinander auf einem simulierten Markt handeln sollen, sowie "Agenten-Pools", dem die Agenten angehören.)
Kurz gesagt werden die dynamisch erzeugten Komponenten weder komplett geschrieben (das ist aber nur ein educated guess, weil ich mir die Datei mal im Wordpad angeschaut hab) noch geladen. Wenn ich das Teil wieder lade, wird nicht mehr "gehandelt".
Nähere Infos:
Es wird zuerst ein Agenten-Pool (auch: "MarketCoordinator") erzeugt. Das ist eine simple Spezialisierung von TComponent mit einigen zusätzlichen Eigenschaften. Der Owner dieser Komponente ist das MDI-Child. Dann werden die Agenten erzeugt. Auch sie sind Spezialisierungen von TComponent mit einigen eigenschaften und der methode "handeln()". Der Owner eines jeden Agenten ist der angegebene Agenten-Pool.
Der Name und der Typ des Agent-Pools stehen in der gespeicherten Datei an letzter Stelle (siehe Anhang). Mehr aber nicht. Die Agenten tauchen so wie ich das sehe gar nicht auf.

Meine Vermutung:
Ohne euch auf eine falsche Spur zu kriegen! Kann es evtl. daran liegen, dass der Typ TMDIChild zwar mit den einzelnen Controls definiert ist (die stehen ja auch zur Entwurfszeit alle drin in der Definition/Formulardatei), aber nicht mit den dynamisch erzeugten Komponenten? Kann mir das nicht so richtig vorstellen, denn der Agenten-Pool wird ja zumindest mit seinem Namen auch mitgespeichert und der wurde ja dynamisch erzeugt.

Habe auch schon versucht, mit der procedure "setSubComponent" was zu reißen, aber das scheint nichts zu bringen - ist wohl für die Entwurfszeit gedacht :(

SMALLID 5. Feb 2007 14:52

Re: Kniffliges Problem beim Serialisieren
 
Update: Nur zur Info: Meine Vermutung ist vermutlich falsch ;). Es liegt an etwas anderem. Wenn ich nur eine Unterkomponente speichere (z.B. sage ich myStream.WriteComponent(AgentPool1)) werden deren Eigenschaften (eigentlich nur name und tag und sowas gespeichert, aber nicht die in "AgentPool1.Components[]" enthaltenen Komponenten. Die Kinder werden also einfach nicht geschrieben. Bleibt die Frage, warum?

Wenn jemand sich der Sache mal annehmen will aber mehr infos braucht, besorg ich die euch gerne. Naja, es ist nun nicht so, als ob das ein Riesenproblem ist. Ist ja eine grundlegende Sache, wenn das nicht richtig gestreamt wird. Aber halt eine schwerwiegende :(

marabu 6. Feb 2007 08:09

Re: Kniffliges Problem beim Serialisieren
 
Hallo,

Zitat:

Zitat von SMALLID
Ist ja eine grundlegende Sache, wenn das nicht richtig gestreamt wird. Aber halt eine schwerwiegende

ich glaube schon, dass "richtig gestreamt" wird - vielleicht hast du eine falsche Erwartungshaltung? Deine Agents und AgentPools verstellen nur den Blick auf das Wesentliche. Erstelle mal ein Mini-Projekt, welches dein Streaming-Problem aufweist, hänge es hier rein - dann werden wir das Rätsel schon lösen. Automatisches streaming findet laut Dokumentation nur mit published properties statt - wenn ich mich recht erinnere.

Grüße vom marabu

SMALLID 6. Feb 2007 18:00

Re: Kniffliges Problem beim Serialisieren
 
Liste der Anhänge anzeigen (Anzahl: 1)
Ich habe mal ein einfachst gehaltenes Projekt mit wenigen Zeilen erstellt, welches das Problem verdeutlicht:

(siehe Anhang)
Dabei wird über den ersten Button eine Komponente created, deren Owner das Fenster ist. Der zweite Button erzeugt dann eine KOmponente, deren Owner die erste Komponente ist. Der dritte Button speichert das Ganze. Wenn ihr euch dann mal die gespeicherten Dateien anseht (am besten die Textform), seht ihr, dass am Ende die erste Komponente ("ComponentA") mit gespeichert wurde. Die zweite wird aber dann nicht mitgespeichert.
Meine Frage ist nun, warum das so ist? Ich versuche schon massig Workarounds zu basteln. Bisher mit mäßigem Erfolg.

Code:
object Form1: TForm1
  Left = 46
  Top = 46
  ActiveControl = Button3
  Caption = 'Form1'
  ClientHeight = 204
  ClientWidth = 470
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'Tahoma'
  Font.Style = []
  OldCreateOrder = False
  Position = poDesigned
  Visible = True
  PixelsPerInch = 96
  TextHeight = 13
  object Button1: TButton
    Left = 32
    Top = 8
    Width = 154
    Height = 25
    Caption = 'ComponentA.Create(Form1)'
    TabOrder = 0
    OnClick = Button1Click
  end
  object Button2: TButton
    Left = 32
    Top = 56
    Width = 209
    Height = 25
    Caption = 'ComponentB.Create(ComponentA)'
    TabOrder = 1
    OnClick = Button2Click
  end
  object Button3: TButton
    Left = 32
    Top = 104
    Width = 89
    Height = 25
    Caption = 'Speichern'
    TabOrder = 2
    OnClick = Button3Click
  end
  object ComponentA: TComponent
  end
end
Delphi-Quellcode:
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    procedure Button3Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
  ComponentA : TComponent;
begin
  ComponentA := TComponent.Create(self);
  ComponentA.Name := 'ComponentA';
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  ComponentB : TComponent;
begin
  ComponentB := TComponent.Create(FindComponent('ComponentA'));
  ComponentB.Name := 'ComponentB';
//  ComponentB.setSubComponent(true);  
end;

procedure TForm1.Button3Click(Sender: TObject);
var
  FileStream : TFilestream;
  StringStream : TStringStream;
  StringList : TStringList;
begin
    FileStream:=TFileStream.Create('speichern-binaer.dat',fmCreate);
    StringStream := TStringStream.Create('');  
    Try
      FileStream.WriteComponent(Form1);
      FileStream.Position := 0;
      ObjectBinaryToText(FileStream, StringStream);
      StringList := TStringList.Create;
      StringList.add(StringStream.DataString);
      StringList.SaveToFile('speichern-textform.txt');
    Finally
      FileStream.Free;
      StringStream.Free;
    end;
end;
end.

oki 6. Feb 2007 19:23

Re: Kniffliges Problem beim Serialisieren
 
Hi,

hast du es schon mal mit Kompos zur Designzeit probiert?

In der Regel reicht beim Create von Compos zur Laufzeit der Owner im Create nicht aus. Es muß explizit der Parent gesetzt werden.

Delphi-Quellcode:
procedure TForm1.Button2Click(Sender: TObject);
var
  ComponentB : TComponent;
begin
  ComponentB := TComponent.Create(FindComponent('ComponentA'));
  ComponentB.Parent := ComponentA;
  ComponentB.Name := 'ComponentB';
//  ComponentB.setSubComponent(true);  
end;
Dabei ist wir aber noch nicht ganz klar warum ComponentA der Owner sein soll. Ich gehe mal davon aus, dass alle compos auf Form1 plaziert sein sollen. Dann würde ich immer Form1 als Owner und Parent halten. Für deine Hirarchie halte ich es für besser einen eigenen Member (Property) anzulegen.

Das mit dem Parent ist aber nur eine Vermutung.

Die OH sagt auch folgendes
Zitat:

WriteComponent erstellt ein Writer-Objekt und ruft dessen Methode WriteRootComponent auf, um die in Instance angegebene Komponente und deren untergeordnete Objekte in den Stream zu schreiben.
Da steht nichts von Rekursion! Die einzige Untercompo von Form1 ist ComponenteA. ComponenteB ehört nicht mehr dazu. Wenn du das gleiche mit ComponentA machst, wird sicher ComponentB zu sehen sein, aber keine weiteren Unterkomponenten.

P.S. Ich denke das mit dem Parent war nicht.

Gruß oki

SMALLID 6. Feb 2007 19:52

Re: Kniffliges Problem beim Serialisieren
 
Infos:
- Die ComponentA soll deshalb ein Besitzer von ComponentB sein, weil sich sozusagen um eine Komposition bzw. Aggregation handelt. Die Agenten sollen einem AgentenPool angehören. Beim starten der Tätigkeiten des jeweiligen Agentenpools braucht der dann nur noch seine Kinder durchzugehen (for COmponents[i]) und die jeweils was machen zu lassen. Das wäre umständlicher, wenn die Agenten alle dem Formular gehören würden und jeder Pool sich erst wieder seine Agenten suchen müsste.

- Die Eigenschaft Parent gibt es meines Wissens nach erst ab TControl

- SetSubComponent sollte das mit der Rekursion eigentlich erledigen, hatte ich gehofft. Dem ist aber nicht so. Das ist das was mich stutzig macht. Beim Speichern eines Formulars in Delphi geht sowas doch normalerweise auch.

- Was mich noch stutziger macht, ist, dass eben der Aufruf "WriteComponent(FindComponent('ComponentA'))" statt "WriteComponent(Form1)" beim Speichern nicht darin resultiert das nun ComponentA und dann ComponentB als deren Kind geschrieben wird, sondern ebenfalls nur ComponentA in der Datei steht.

Danke für eure Hilfe! Würde mich freuen, wenn wir das Problem lösen können.

oki 6. Feb 2007 20:07

Re: Kniffliges Problem beim Serialisieren
 
Zitat:

Zitat von SMALLID
Infos:
- Die Eigenschaft Parent gibt es meines Wissens nach erst ab TControl

Jooop! Da hab ich nicht aufgepasst. Das mit den Ownern war mir im Nachgang schon klar. Macht auch Sinn.
Dann bleibt dir wohl nur einen eigenen Algotythmus zu schreiben.

Gruß oki

SMALLID 6. Feb 2007 20:33

Re: Kniffliges Problem beim Serialisieren
 
Naja können wir das jetzt als Bug bezeichnen und melden es CodeGear? Ich meine, probiert das mit dem Speichern von ComponentA statt Form1 ruhig mal aus. Da wird keine Unterkomponente gespeichert, auch wenn das so in der Onlinehilfe steht. Bei Form1 werden sie aber gespeichert (dann eben nur 1 Level). Ein bissl inkonsistent ... oder ich hab' wirklich was übersehen.

Ich werd das jetzt Workaroundmäßig mal versuchen, so zu lösen:
Ich rufe WriteComponent(FormX) auf. Dann schaue ich hartcodiert nach ob in FormX Komponenten vom Typ "ComponentA" enthalten sind (Also genauer: AgentPools). Dann mache ich für jeden AgentPool eine neue Datei auf und gebe ihr den Namen "[Name des Agentpools].dat". Da rein werden dann die Kinder des AgentPools, also die Agenten bzw. im vereinfachten Bsp. alle ComponentB's geschrieben.
Beim Lesen läuft es dann analog. Erst wird die Form geladen. Dann wird nach ComponentA's bzw. "AgentPools" geschaut, deren Name+'.dat' dann wieder den Zugriff auf den Stream mit den ComponentB's, sprich: Agents, ermöglicht.
Meiner Meinung nach eine sehr unelegante und unschöne Lösung, aber ein Workaround muss ja offenbar her. :(

marabu 7. Feb 2007 15:47

Re: Kniffliges Problem beim Serialisieren
 
Hallo,

Zitat:

Zitat von SMALLID
Ein bissl inkonsistent ... oder ich hab' wirklich was übersehen.

dass nur ComponentA gespeichert wird, entspricht völlig den Regeln. Damit Sub-Komponenten gespeichert werden, musst du laut Dokumentation die Methode GetChildren() überschreiben, welche bei der Basisklasse TComponent einfach gar nichts macht.

Grüße vom marabu

SMALLID 7. Feb 2007 17:52

Re: Kniffliges Problem beim Serialisieren
 
Dann hab' ich das tatsächlich übersehen und mir die Workaround-Arbeit wahrscheinlich umsonst gemacht. Ich würde das mit dem override von GetChildren aber dennoch noch einmal probieren. Kannst du mir dazu nochmal das Hilfethema sagen, wo das stand?


Alle Zeitangaben in WEZ +1. Es ist jetzt 08:57 Uhr.
Seite 1 von 3  1 23      

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz