Saving and loading Treeview using XML

Note

Sorry for the long post in advance, although I'd rather put as much information as possible, rather than fill in the blanks when necessary.

Notice that I also marked this as Delphi, and I own and still use Delphi XE. Now I use Lazarus as my main IDE, I just can not afford to buy newer versions of Delphi, and now Lazarus is becoming more stable, which makes me need to switch to Lazarus.

For this question, I turned on the zip application with the project source, although it is written in Lazarus, and this will really help with the question I have, so the comments are in the first paragraph.


Overview

In the question, I have an object that owns several classes as TLists.

I present this data in Treeview, and there is no way to know how many levels and nodes will be present in the tree, because they are dynamically created at runtime. One limitation that I set is that the top-level nodes will be fixed, that is, they cannot be deleted or renamed - this is what I will call RootGroups.

The Treeview will be populated with elements and groups, each node added to the Treeview will have its own object assigned to the data to correctly identify each element. I am going to show an example screenshot now to give a better idea before continuing:

enter image description here

As you can see, I have the top two most nodes, Object1Root and Object2Root. If you notice the buttons on the right, they allow you to add a group and elements to the Treeview, but they become disabled if they do not belong to that part of the Treeview. For example, you cannot add Object2Group or Object2Item to Object1Root.

Basically, everything in Treeview has its own pointer to an object. Each object I get from the base object. This base object has properties for storing the position where it is in the Treeview, for example:

type TBaseObject = class private FName: string; FGroup: string; FNodeLevel: Integer; FNodeIndex: Integer; public constructor Create(AName: string); destructor Destroy; override; published property Name: string read FName write FName; property Group: string read FGroup write FGroup; property NodeLevel: Integer read FNodeLevel write FNodeLevel; property NodeIndex: Integer read FNodeIndex write FNodeIndex; end; 

Then I can get other classes from the base object, for example:

 type TObject1RootGroup = class(TBaseObject) public constructor Create(AName: string); destructor Destroy; override; procedure ToSave(const XMLDoc: IXMLDocument; var Root, Node: IXMLNode); end; TObject1Group = class(TBaseObject) public constructor Create(AName: string); destructor Destroy; override; procedure ToSave(const XMLDoc: IXMLDocument; var Root, Node: IXMLNode); end; TObject1Item = class(TBaseObject) private FSomeVal1: string; FSomeVal2: string; public constructor Create(AName: string); destructor Destroy; override; procedure ToSave(const XMLDoc: IXMLDocument; var Root, Node: IXMLNode); published property SomeVal1: string read FSomeVal1 write FSomeVal1; property SomeVal2: string read FSomeVal2 write FSomeVal2; end; 

The main object that contains all these classes is as follows:

 type TMyObject = class(TObject) private FName: string; FObject1Groups: TList; FObject1Items: TList; FObject2Groups: TList; FObject2Items: TList; protected procedure FreeObjects; public constructor Create(AName: string); destructor Destroy; override; procedure Save(FileName: string); function Load(Filename: string): Boolean; published property Name: string read FName write FName; property Object1Groups: TList read FObject1Groups; property Object1Items: TList read FObject1Items; property Object2Groups: TList read FObject2Groups; property Object2Items: TList read FObject2Items; end; 

When I save the main object in XML, I first iterate over all the TreeView and then assign node data to each object, such as Parent, Level, Index, etc. The output XML file based on the first image will look like this:

enter image description here

Note. Parts of SomeVal are not important, as I never worried about writing anything to objects.

Indeed, what I should do is Save to XML in the same way as Treeview shows. I'm not too familiar with XML, because I can still handle it, but I think the result should look something like this: (written in Notepad)

 <XML Name="test.xml"> <Counts Object1Groups="3" Object1Items="5" Object2Groups="2" Object2Items="1" /> <TObject1RootGroup Name="Object1Root" Group="" NodeLevel="0" NodeIndex="0" <TObject1Item Name="Item1" Group="Object1Root" NodeLevel="1" NodeIndex="0" SomeVal1="" SomeVal2="" /> <TObject1Item Name="Item2" Group="Object1Root" NodeLevel="1" NodeIndex="1" SomeVal1="" SomeVal2="" /> <TObject1Group Name="Group1" Group="Object1Root" NodeLevel="1" NodeIndex="2" /> <TObject1Item Name="Item3" Group="Object1Root" NodeLevel="1" NodeIndex="3" SomeVal1="" SomeVal2="" /> <TObject1Group Name="Group2" Group="Object1Root" NodeLevel="1" NodeIndex="4" /> <TObject1Item Name="Item1" Group="Group2" NodeLevel="2" NodeIndex="0" SomeVal1="" SomeVal2="" /> <TObject1Group Name="Group1" Group="Group2" NodeLevel="2" NodeIndex="1" /> <TObject1Item Name="Item1" Group="Group1" NodeLevel="3" NodeIndex="0" SomeVal1="" SomeVal2="" /> <TObject2RootGroup Name="Object2Root" Group="" NodeLevel="0" NodeIndex="1" <TObject2Group Name="Group1" Group="Object2Root" NodeLevel="1" NodeIndex="0" /> <TObject2Group Name="Group2" Group="Object2Root" NodeLevel="1" NodeIndex="1" /> <TObject2Item Name="Item1" Group="Group2" NodeLevel="2" NodeIndex="0" SomeVal1="" SomeVal2="" /> </XML> 

Then I could load the TreeView from XML. The problem is that I really know how to save XML, as I currently have, I know some kind of recursion, etc. Necessary, and it is here that I will fight and, in particular, rebuild the tree from an XML file.

application

It took me several hours to break my actual project code into an example that is easier to read and understand, it is written in Lazarus and uses the OmniXML library, I included only the original units that do not have a project file.

Download it here (password is stackoverflow): http://www34.zippyshare.com/v/16401041/file.html

Ultimately my question is:

  • How to save XML in the correct hierarchical structure.
  • How to load XML and rebuild Treeview as it was before saving.

Thank you very much.

+4
source share
2 answers

As an initial project for further development.

 unit TreeXML; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, xmldom, XMLIntf, msxmldom, XMLDoc, ActiveX, ComObj, ComCtrls; Type TTreeToXML = Class private FDOC: TXMLDocument; FRootNode: IXMLNode; FTree: TTreeView; procedure IterateRoot; procedure WriteNode(N: TTreeNode; ParentXN: IXMLNode); Public Constructor Create(Tree: TTreeView); Procedure SaveToFile(const fn: String); Destructor Destroy; override; End; TXMLToTree = Class private FTree: TTreeView; procedure IterateNodes(xn: IXMLNode; ParentNode: TTreeNode); Public Procedure XMLToTree(Tree: TTreeView; Const FileName: String); End; implementation { TTreeToXML } constructor TTreeToXML.Create(Tree: TTreeView); begin FTree := Tree; FDOC := TXMLDocument.Create(nil); FDOC.Options := FDOC.Options + [doNodeAutoIndent]; FDOC.Active := true; FDOC.Encoding := 'UTF-8'; FRootNode := FDOC.CreateElement('Treeview', ''); FDOC.DocumentElement := FRootNode; IterateRoot; end; Procedure TTreeToXML.WriteNode(N: TTreeNode; ParentXN: IXMLNode); var CurrNode: IXMLNode; Child: TTreeNode; begin CurrNode := ParentXN.AddChild(N.Text); CurrNode.Attributes['NodeLevel'] := N.Level; CurrNode.Attributes['Index'] := N.Index; Child := N.getFirstChild; while Assigned(Child) do begin WriteNode(Child, CurrNode); Child := Child.getNextSibling; end; end; Procedure TTreeToXML.IterateRoot; var N: TTreeNode; begin N := FTree.Items[0]; while Assigned(N) do begin WriteNode(N, FRootNode); N := N.getNextSibling; end; end; procedure TTreeToXML.SaveToFile(const fn: String); begin FDOC.SaveToFile(fn); end; destructor TTreeToXML.Destroy; begin if Assigned(FDOC) then FDOC.Free; inherited; end; { TXMLToFree } Procedure TXMLToTree.XMLToTree(Tree: TTreeView; const FileName: String); var Doc: TXMLDocument; begin FTree := Tree; Doc := TXMLDocument.Create(Application); try Doc.LoadFromFile(FileName); Doc.Active := true; IterateNodes(Doc.DocumentElement, NIL); finally Doc.Free; end; end; Procedure TXMLToTree.IterateNodes(xn: IXMLNode; ParentNode: TTreeNode); var ChildTreeNode: TTreeNode; i: Integer; begin For i := 0 to xn.ChildNodes.Count - 1 do begin ChildTreeNode := FTree.Items.AddChild(ParentNode, xn.ChildNodes[i].NodeName); IterateNodes(xn.ChildNodes[i], ChildTreeNode); end; end; end. 

Call example

 procedure TForm1.Button1Click(Sender: TObject); begin With TTreeToXML.Create(TreeView1) do try SaveToFile('C:\temp\test.xml'); finally Free; end; end; procedure TForm1.Button2Click(Sender: TObject); begin With TXMLToTree.Create do try XMLToTree(TreeView2, 'C:\temp\test.xml') finally Free; end; end; 

The XML used will look like this:

 <?xml version="1.0" encoding="UTF-8"?> <Treeview> <Object1Root NodeLevel="0" Index="0"> <Item1 NodeLevel="1" Index="0"/> <Item2 NodeLevel="1" Index="1"/> <Group1 NodeLevel="1" Index="2"/> <Group2 NodeLevel="1" Index="3"> <Item1 NodeLevel="2" Index="0"/> <Group1 NodeLevel="2" Index="1"> <Item1 NodeLevel="3" Index="0"/> </Group1> </Group2> </Object1Root> <Object2Root NodeLevel="0" Index="1"> <Group1 NodeLevel="1" Index="0"/> <Group2 NodeLevel="1" Index="1"> <Item1 NodeLevel="2" Index="0"/> </Group2> </Object2Root> </Treeview> 
+5
source

My attempt. Uses the MSXML 6.0 type library. Not too fancy, but seems to do the job.

 unit ttreexml; // treeview to XML, XML to treeview by Glenn1234, // may be used with proper credit given interface uses msxml2_tlb, comctrls, dialogs, sysutils; type // saves TTreeView as XML file. TTreeViewToXML = class private doc: IXMLDOMDocument; FTree: TTreeView; procedure XMLPopulate(BaseNode: TTreeNode; DataItem: IXMLDOMelement); Public Constructor Create(Tree: TTreeView); procedure SaveToFile(filename: string); end; // loads TTreeView from XML file TXMLToTreeView = class private doc: IXMLDOMDocument; FTree: TTreeView; procedure XMLLoad(BaseItem: TTreeNode; DataItem: IXMLDOMNode); Public Procedure XMLToTree(Tree: TTreeView; Const FileName: String); end; implementation constructor TTreeViewToXML.Create(Tree: TTreeView); begin FTree := Tree; end; procedure TTreeViewToXML.XMLPopulate(BaseNode: TTreeNode; DataItem: IXMLDOMelement); var SubItem: IXMLDOMElement; selnode: TTreeNode; begin SelNode := BaseNode; while selnode <> nil do begin if SelNode.HasChildren then begin SubItem := doc.CreateElement('Group'); SubItem.setAttribute('Value', SelNode.Text); DataItem.AppendChild(SubItem); XMLPopulate(SelNode.GetFirstChild, SubItem); end else begin SubItem := doc.CreateElement('Item'); SubItem.setAttribute('Value', SelNode.Text); DataItem.AppendChild(SubItem); end; SelNode := SelNode.GetNextChild(SelNode); end; end; procedure TTreeViewToXML.SaveToFile(filename: string); var topnode: IXMLDOMElement; selnode: TTreeNode; begin //create DOM document instance doc := CoDOMDocument.Create; doc.async := false; //------------------------------------------------------------------------------ topnode := doc.createElement('TreeView'); doc.appendChild(topnode); selnode := FTree.Items.GetFirstNode; XMLPopulate(SelNode, topnode); doc.save(FileName); end; procedure TXMLToTreeView.XMLLoad(BaseItem: TTreeNode; DataItem: IXMLDOMNode); var item1, item2: IXMLDOMNode; attr: IXMLDOMNamedNodeMap; CurrItem: TTreeNode; begin Item1 := DataItem; CurrItem := nil; // compiler complains if I don't do this while Item1 <> nil do begin attr := item1.attributes; item2 := attr.nextNode; while item2 <> nil do begin CurrItem := FTree.Items.AddChild(BaseItem, Item2.NodeValue); item2 := attr.nextNode; end; if item1.nodename = 'Group' then XMLLoad(CurrItem, Item1.Get_firstChild); Item1 := Item1.Get_nextSibling; end; end; Procedure TXMLToTreeView.XMLToTree(Tree: TTreeView; Const FileName: String); var item1: IXMLDOMNode; begin //create DOM document instance doc := CoDOMDocument.Create; doc.async := false; FTree := Tree; //------------------------------------------------------------------------------ if doc.load(FileName) then begin FTree.Items.BeginUpdate; FTree.Items.Clear; Item1 := doc.documentElement.Get_firstChild; XMLLoad(nil, Item1); FTree.Items.EndUpdate; end else begin MessageDlg(Format ('Error loading XML document.'#13 + 'Error number: %d'#13 + 'Reason: %s'#13 + 'Line: %d'#13 + 'Column: %d', [doc.parseError.errorCode, doc.parseError.reason, doc.parseError.line, doc.parseError.linePos]), mtError, [mbOK], 0); end; end; end. 

Quick XML output example:

 - <Group Value="Delphi 3"> - <Group Value="BIN"> <Item Value="BOWF520.DLL" /> <Item Value="BOWFVC.DLL" /> <Item Value="BRC32.EXE" /> <Item Value="BRCC32.EXE" /> . . . <Item Value="DELPHI32.EXE" /> <Item Value="DELPHIMM.DLL" /> . . . </Group> <Item Value="DeIsL1.isu" /> - <Group Value="Demos"> - <Group Value="ACTIVEX"> - <Group Value="DELCTRLS"> <Item Value="ABOUT1.DFM" /> 
0
source

Source: https://habr.com/ru/post/953054/


All Articles