Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Sonstige Fragen zu Delphi (https://www.delphipraxis.net/19-sonstige-fragen-zu-delphi/)
-   -   Delphi XML-Struktur in Objekte verwalten (https://www.delphipraxis.net/119288-xml-struktur-objekte-verwalten.html)

Yheeky 23. Aug 2008 19:25


XML-Struktur in Objekte verwalten
 
Hi,

ich habe hier bereits Hilfe bekommen, wie ich eine XML-Struktur in einer TreeView darstellen kann.
Nun möchte ich diese Struktur gerne objektorientiert umsetzen (mit TObject und TObjectlist).
Mein Problem ist hier, dass ich wie bei der TreeView ein TreeNode habe, der unendlich viele Nodes besitzen kann und DER wiederum auch unendlich viele besitzen kann.
Wie kann ich das objektorientiert umsetzen, hat jemand eine Idee?

Yheeky 23. Aug 2008 20:56

Re: XML-Struktur in Objekte verwalten
 
Will noch ein kleines Beispiel nachschieben, damit meine Frage noch deutlicher ist ;-)

Ich habe ein Objekt...
Delphi-Quellcode:
type
  TOrdner = class(TObject)
    Ordnername : String;
    Expanded: Boolean;
  end;
...dennoch möchte ich, dass DIESES Objekt wiederum Objekte vom Typ TOrdner enthalten kann (Ordner können ja Unterordner enthalten und diese wiederum Unterordner usw.).
Diese ganzen Ordner, möchte ich dann in einem einzelnen Objekt (z.B. TOrdnerSammlung vom Typen TObjectList) ablegen und speichern.

So, nun hoffe ich, dass es klarer ist - denke, dass man mit meinem ersten Post nicht soviel anfangen konnte ;-)

Pfoto 23. Aug 2008 21:37

Re: XML-Struktur in Objekte verwalten
 
Hallo Yheeky,

ich selbst habe auch so ein Problem gehabt (es ging aber
um eine Website mit deren Unterseiten) und habe letztendlich
jedem Node einfach den Pointer eines erstellten Objektes
mitgegeben.

Damit hast du die Struktur in einem Baum, aber die Inhalte
in den einzelnen Objekten.
Sobald du auf einen Node zugreifst, kannst du dann das
drangehängte Objekte auslesen.


Grüße
Jürgen

Yheeky 23. Aug 2008 22:50

Re: XML-Struktur in Objekte verwalten
 
Hallo Jürgen,

klingt gut, aber verstehe ich leider nicht :-(
Kannst du das vielleicht einem Laien nochmal erklären :-)
Vielleicht auch mit Code, dann ist´s vielleicht eindeutiger.

Danke!

Dax 23. Aug 2008 22:58

Re: XML-Struktur in Objekte verwalten
 
Delphi-Quellcode:
type
  TOrdner = class(TObject)
    Ordnername : String;
    Expanded: Boolean;
    Unterordner: TOrdnerSammlung;
  end;
Allerdings sollte das ganze schon ordentlich gekapselt sein (ohne Setter...)

Yheeky 23. Aug 2008 23:21

Re: XML-Struktur in Objekte verwalten
 
Ja, sowas in der Art dachte ich auch, allerdings sagt mit Delphi, wenn ich die beiden Klassen (TOrdner und TOrdnerSammlung) in zwei Units auslagere, dass ich "keine Zirkuläre Unit-Referenz" haben darf - und diese brauchte ich ja, weil man sonst die Objekte nicht findet :-(

sx2008 23. Aug 2008 23:22

Re: XML-Struktur in Objekte verwalten
 
Zitat:

Zitat von Yheeky
...dennoch möchte ich, dass DIESES Objekt wiederum Objekte vom Typ TOrdner enthalten kann (Ordner können ja Unterordner enthalten und diese wiederum Unterordner usw.).

Dafür wäre das Design Pattern Composite das Richtige.
Die Klasse TComponent ist übrigens ein Composite.
Mit Hilfe des Property ComponentCount und dem Array-Property Components[] kann man ganze Baumstrukturen aufbauen.
Das Property Owner ist ebenfalls wichtig, damit jedes Objekt seinen Vorfahren in der Hierarchie kennt.
Du kannst dir die Sache am Anfang einfach machen und die Klasse TOrder von TComponent ableiten.
Später willst du aber vielleicht von TComponent weggehen, da diese Klasse doch etwas Overhead mit sich rumträgt.

Yheeky 23. Aug 2008 23:38

Re: XML-Struktur in Objekte verwalten
 
Mit TComponent möchte ich ungern arbeiten. Ich würde gerne meine eigene Klasse entwickeln, weil ich - wie du schon vermutet hast - den ganzen Overhead nicht mit drinhaben möchte.
Ist das so schwer, solch eine Klasse zu entwickeln?

omata 24. Aug 2008 00:00

Re: XML-Struktur in Objekte verwalten
 
Zitat:

Zitat von Yheeky
...wenn ich die beiden Klassen ... in zwei Units auslagere, dass ich "keine Zirkuläre Unit-Referenz" haben darf ...

NodeTypeU:
Delphi-Quellcode:
unit NodeTypeU;

interface

type
  TNodeCustom = class
  end;

implementation

end.
NodeListU:
Delphi-Quellcode:
unit NodeListU;

interface

uses Classes, NodeTypeU;

type
  TNodeList = class
  private
    FNodes: TList;
  public
    constructor create;
    destructor destroy; override;
    procedure Clear;
    procedure addNode(Node:TNodeCustom);
  end;

implementation

{ TNodeList }

constructor TNodeList.create;
begin
  FNodes:=TList.Create;
end;

destructor TNodeList.destroy;
begin
  Clear;
  FNodes.free;
  inherited;
end;

procedure TNodeList.Clear;
var i:integer;
    ItemObject:TObject;
begin
  for i:=1 to FNodes.Count do begin
    ItemObject:=FNodes[i-1];
    ItemObject.free;
  end;
  FNodes.Clear;
end;

procedure TNodeList.addNode(Node: TNodeCustom);
begin
  FNodes.Add(Node);
end;

end.
NodeU:
Delphi-Quellcode:
unit NodeU;

interface

uses NodeTypeU, NodeListU;

type
  TNode = class(TNodeCustom)
  private
    FChildNodes:TNodeList;
  public
    constructor create;
    destructor destroy; override;
    procedure addChild(Node:TNode);
  end;

implementation

{ TNode }

constructor TNode.create;
begin
  FChildNodes:=TNodeList.create;
end;

destructor TNode.destroy;
begin
  FChildNodes.free;
  inherited;
end;

procedure TNode.addChild(Node: TNode);
begin
  FChildNodes.addNode(Node);
end;

end.

Yheeky 24. Aug 2008 00:24

Re: XML-Struktur in Objekte verwalten
 
Ich habe schon gelesen, dass man das umgehen kann, aber anhand deines Beispieles könnte ich jetzt nicht sehen, was da genau TOrdner und TOrdnerSammlung ist.
Sorry, aber das versteh ich noch nicht :cry:

Hansa 24. Aug 2008 01:14

Re: XML-Struktur in Objekte verwalten
 
Guck doch mal in Richtung "Objects", wie beim Stringgrid. Zumindest der VST kann das auch. Verstehe zwar jetzt das konkrete Problem nicht, aber unterbringen kann man da alles.

Pfoto 24. Aug 2008 09:18

Re: XML-Struktur in Objekte verwalten
 
Hallo Christian,

ich frage nochmal nach, was genau der Zweck dieses objektorientierten Aufbaus ist.

Nehmen wir an, diese Infos soll Dein Objekt halten:
Delphi-Quellcode:
type
  TOrdner = class(TObject)
    Ordnername : String;
    Expanded: Boolean;
  end;
In dem Fall würden ja alleine die Infos im gefüllten TreeView
aussreichen, d.h. du bräuchtest eigentlich nicht noch Objekte anlegen,
sondern könntest bei Bedarf direkt auf den Node des TreeViews zugreifen.


Falls das Objekt größer werden soll, hier noch mal eine genauere
Beschreibung meiner Umsetzung:

Dem TreeView kannst du anstatt nur einer Beschreibung auch eine
Beschreibung mit einem Pointer mit auf den Weg geben, also so:
Delphi-Quellcode:
NewTreeNode:= aTreeView.Items.AddChildObject(aParent, 'Nodename', Pointer);
Du könntest also beim Füllen des TreeViews ein Ordner-Objekt
erstellen und dessen Pointer an den Knoten dranhängen, etwa so:

Delphi-Quellcode:
     
      { Ordner-Objekt erstellen }
      NewFolder:= TFolder.Create(aXmlNode.Nodes[i].AttributeByName['Name']);
      { Pointer des Ordner-Objektes dem erstellten Knoten übergeben, sowie
        den oben bereits übergebenen Ordnernamen }
      NewTreeNode:= aTreeView.Items.AddChildObject(aParent, NewFolder.Foldername, NewFolder);
Dabei habe ich das Ordner-Objekt so deklariert:

Delphi-Quellcode:
type
  TFolder = class(TObject)
  private
    fFoldername : String;
    fExpanded: Boolean;
  public
    constructor Create(const aFoldername: string);
    property FolderName: string read fFoldername write fFoldername;
  end;

Ganz wichtig zu sagen ist jedoch, dass der TreeView nicht automatisch
auch die Freigabe dieser erstellen Ordner-Objekte durchführt.
Löschst du also einen Knoten aus dem Baum, bist du auch für die
Freigabe des Ordner-Objektes verantwortlich.

Es gibt eine stark erweiterte Tree-Komponente, die sich TVirtualStringTree
nennt (oft auch unter VST hier im Forum zu finden). Dieser ist von
Grund auf neu programmiert und besitzt u.a. auch eine automatische
Speicherverwaltung der übergebenen Objekte.


Nun zum Abrufen des Knotens mit Objekt:

Delphi-Quellcode:
 
  { Normaler Text des Knotens }
  ShowMessage(TreeView1.Selected.Text);
  { Über casten mit TFolder kannst du den Pointer des Knotens
    gezielt ansprechen und die Eigenschaft Foldername abfragen }
  ShowMessage(TFolder(TreeView1.Selected.Data).FolderName);
Im obigen Fall würde jetzt beides mal das gleiche angezeigt,
aber du kannst ja dein Objekt wie gewünscht aufbauen.



Grüße
Jürgen

Yheeky 24. Aug 2008 15:54

Re: XML-Struktur in Objekte verwalten
 
Hallo Jürgen,
danke für den Code - hast dir richtig Mühe gegeben! Ich habe deinen Ansatz nun auch verstanden, ABER ;-)

Zitat:

Zitat von Pfoto
Nehmen wir an, diese Infos soll Dein Objekt halten:
Delphi-Quellcode:
type
  TOrdner = class(TObject)
    Ordnername : String;
    Expanded: Boolean;
  end;
In dem Fall würden ja alleine die Infos im gefüllten TreeView
aussreichen, d.h. du bräuchtest eigentlich nicht noch Objekte anlegen,
sondern könntest bei Bedarf direkt auf den Node des TreeViews zugreifen.

Stimmt, bei diesem Aufbau nicht, aber nach genauerer Überlegung müsste der Aufbau ja wiefolgt sein:

Delphi-Quellcode:
type
  TOrdner = class(TObject)
    Ordnername : String;
    Expanded: Boolean;
    Unterordner : TOrdner;
    Dateien : TDatei;
  end;
Die Dateien müssen ja noch bedacht werden und deswegen glaube ich wäre ein objektorientierter Ansatz nicht schlecht.
Im nächsten Schritt müsste man sich Gedanken machen, wie man die einzelnen Dateien mit einem bestimmten Ordner verknüpft. Nur anhand des Ordnernamens funktioniert da ja nicht, weil ein Ordnername ja mehrfach vorkommen kann (wenn auch nicht innerhalb der gleichen Struktur).
Beispiel:

Zitat:

+ Fahrzeug
-- Auto
---- Verkäufe
-- Fahrrad
---- Verkäufe
Soweit mein Problem - vielleicht hast du oder auch jemand anderes noch eine Idee für die Umsetzung (egal ob objektorientiert oder nicht).

Wäre super!

Pfoto 24. Aug 2008 17:12

Re: XML-Struktur in Objekte verwalten
 
Hallo Christian,

Ich versuche, dir zur folgen, aber ganz komme ich nicht mit.

Alle diese Eigenschaften des Objektes...
Delphi-Quellcode:
type
  TOrdner = class(TObject)
    Ordnername : String;
    Expanded: Boolean;
    Unterordner : TOrdner;
    Dateien : TDatei;
  end;
... wären ja nicht nötig, wenn du die Struktur, also die
Art der Gliederung von Ordnern und Dateien separat verwaltest,
also etwa im TreeView selbst.

Das Verknüpfen der Dateien zu einem Ordner wäre zu vergleichen
mit Blättern an einem Ast. Welches Blatt zu welchem Ast gehört,
ist ja letztendlich in deiner Datei gespeichert.
Würdest du es in eine Datenbank legen, wäre die Vergabe einer
ID und einer ParentID nötig, um den Baum wieder korrekt zusammenzusetzen.

Auch das Zuordnen bzw. Unterscheiden, ob es sich um eine
Datei oder einen Ordner handelt, könntest du unkompliziert
innerhalb einer einzigen Klasse mittels eines Flags händeln.

Delphi-Quellcode:
type
  TFile = class(TObject)
    Name : String;
    Expanded: Boolean;
    IsFolder: Boolean;
  end;
Soweit ich weiß, sind in Windows eigentlich ja alles
nur Dateien -- auch die Ordner. Diese Datein werden nur als
Ordner gekennzeichnet und entsprechend gehandhabt.

Mit der obigen Klasse kannst du also wie ich schon im Thread
davor beschrieben habe, das Objekt TFile erstellen und
ihm zum Zeitpunkt der Erstellung sagen, ob es als Ordner
oder Datei fungieren soll.
Einzelne Eigenschaften könntest du mit festen Integer-Werten
belegen und separat zuordnen.

Oder willst du generell eine real existierende Ordnerstuktur
einlesen? In Deinem Beispiel mit Fahrzeug / Auto / Verkäufe etc.
wird für mich noch nicht deutlich, wo und warum es sich um
Ordner und Dateien handelt. Für mich ist dass nur eine
Liste von Kategorieren, oder verstehe ich dich da falsch?


Grüße
Jürgen

thkerkmann 24. Aug 2008 18:22

Re: XML-Struktur in Objekte verwalten
 
Hi,

ich glaube Yheeky will das nicht in der Treeview speichern, sondern unabhängig.

@Yheeky
Delphi-Quellcode:
type
  TOrdner = class(TObject)
    Ordnername : String;
    Expanded: Boolean;
    Unterordner : TOrdner;
    Dateien : TDatei;
  end;
aber das ist nicht richtig, denn dein Unterordner ist ja nicht nur einer, sondern eine Liste, genauso wie deine Dateien.

also
Delphi-Quellcode:
type
  TOrdner = class(TObject)
    Ordnername : String;
    Expanded: Boolean;
    Unterordner : TOrdnerListe;
    Dateien : TDateiListe;
  end;
d.h. Du hast für die Ordnergeschichten 2 Objekte nötig

Delphi-Quellcode:
type
  TOrdnerliste = class;

  TOrdner = class
    ...
    UnterOrdner: tOrdnerliste;
  end;

  TOrdnerListe = class
    ...
  end;
dann musst Du die TOrdnerliste als Klasse bekanntmachen, dann kannst Du sie erst mal im TOrdner verwenden. Das ist eine Forward-Deklaration. Nach der Definition von TOrdner kommt dann die vollständige Definition von TOrdnerliste.
Damit das klappt, müssen allerdings beide Klassen in einer Unit untergebracht sein. Sie gehören ja auch irgendwie zusammen, und sollten sich daher nicht soweit voneinander weg bewegen ;-)

Gruss

Yheeky 25. Aug 2008 08:18

Re: XML-Struktur in Objekte verwalten
 
Zitat:

Zitat von thkerkmann
Hi,

ich glaube Yheeky will das nicht in der Treeview speichern, sondern unabhängig.

Richtig! Ich möchte die Nachrichten dann in einer ListView anzeigen lassen.

Habe jetzt gerade mal deinen Code getestet und bin zu folgendem Ergebnis gekommen:

Delphi-Quellcode:
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, JvSimpleXml, StdCtrls, ComCtrls, JclSimpleXml, Contnrs;

type
  TOrdnerSammlung = class;

  TOrdner = class(TObject)
  public
    Ordnername,
      Icon: string;
    Expanded: Boolean;
    Unterordner: TOrdnerSammlung;
    constructor Create; overload;

  end;

  TOrdnerSammlung = class(TObjectlist)
    function GetItems: TOrdner;
  public
    property Items: TOrdner read getItems;
  end;

type
  TForm1 = class(TForm)
    TreeView1: TTreeView;
    XML: TJvSimpleXML;
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
  public
    OrdnerListe: TOrdnerSammlung;
    procedure WriteNode(Node: TTreeNode; Liste: TOrdnerSammlung);
    procedure ReadInObjects(XMLText: TJclSimpleXMLElem; OrdnerSammlung:
      TOrdnerSammlung);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

function TOrdnerSammlung.GetItems: TOrdner;
begin

end;

constructor TOrdner.Create;
begin
  Unterordner := TOrdnersammlung.Create(True);
end;

procedure TForm1.WriteNode(Node: TTreeNode; Liste: TOrdnerSammlung);
var
  I: Integer;
  newTreeNode: TTreeNode;
begin
  if Liste.Count > 0 then
    for I := 0 to Liste.Count - 1 do
    begin
      newTreeNode := TreeView1.Items.AddChild(Node,
        TOrdner(Liste[I]).Ordnername);
      newTreeNode.ImageIndex := StrToInt(TOrdner(Liste[I]).Icon);

      if TOrdner(Liste[I]).Unterordner.Count > 0 then
        WriteNode(newTreeNode, TOrdner(Liste[I]).Unterordner);
    end;
end;

procedure TForm1.ReadInObjects(XMLText: TJclSimpleXMLElem; OrdnerSammlung:
  TOrdnerSammlung);
var
  I, J: Integer;
  newFolder: TOrdner;
begin
  // Wenn die XML-Struktur noch Daten enthält
  if XMLText.Items.Count > 0 then
    for I := 0 to XMLText.Items.Count - 1 do
    begin
      // Wenn es sich um einen Ordner handelt
      if XMLText.Items[I].Name = 'Ordner' then
      begin
        if XMLText.Items[I].Properties.Count > 0 then
        begin
          // Neues Ordnerobjekt erstellen
          newFolder := TOrdner.Create;

          for J := 0 to XMLText.Items[I].Properties.Count - 1 do
          begin
            // Der Ordnername wird ausgelesen
            if XMLText.Items[I].Properties[J].Name = 'Name' then
              newFolder.Ordnername := XMLText.Items[I].Properties[J].Value;
            if XMLText.Items[I].Properties[J].Name = 'Expanded' then
              if XMLText.Items[I].Properties[J].Value = 'True' then
                newFolder.Expanded := True
              else
                newFolder.Expanded := False;
            if XMLText.Items[I].Properties[J].Name = 'Icon' then
              newFolder.Icon := XMLText.Items[I].Properties[J].Value;
          end;
        end;
      end;

      // Fügt der Liste das Ordnerobjekt hinzu
      OrdnerSammlung.Add(TOrdner(newFolder));

      // Auf Unterordner prüfen
      if XMLText.Items[I].Items.Count > 0 then
        // Wenn es sich um einen Ordner handelt
        if XMLText.Items[I].Name = 'Ordner' then
          ReadInObjects(XMLText.Items[I], newFolder.Unterordner);
    end;

end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  XML.LoadFromFile(ExtractFilePath(Application.ExeName) + 'Test.xml');

  Ordnerliste := TOrdnerSammlung.Create(True);

  ReadInObjects(XML.Root, Ordnerliste);
  Button2.Enabled := True;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  TreeView1.Items[1].DeleteChildren;
  WriteNode(TreeView1.Items[1], Ordnerliste);
end;

end.
Funktioniert alles soweit. Mein Beispiel-XML-Code war folgender (und das hat funktioniert):

XML-Code:
<?xml version="1.0" encoding="ISO-8859-1"?>
<Daten>
   <Ordner Name="Autos" Expanded="True" Icon="15">
      <Ordner Name="Meine Autos" Icon="16"/>
      <Ordner Name="Andere Autos" Icon="17"/>
   </Ordner>    
</Daten>
Ich frage mich gerade nur, warum das schon funktioniert, obwohl ich in GetItems noch garnichts stehen habe? :gruebel: Wofür brauche ich es dann bzw. warum erwartet property Items ein read?


Alle Zeitangaben in WEZ +1. Es ist jetzt 05:49 Uhr.

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