4

笔记

很抱歉,这篇长篇文章,我认为最好尽可能多地提供信息,而不是在需要时填补空白。

请注意,尽管我也将其标记为 Delphi 并且拥有并仍在使用 Delphi XE 我现在使用 Lazarus 作为我的主要 IDE,但我根本买不起较新的 Delphi 版本,现在 Lazarus 变得更加稳定,这对我来说很有意义切换到拉撒路。

对于这个问题,我已经包含了一个带有项目源的 zip 附件,虽然它是用 Lazarus 编写的,但它确实有助于解决我的问题,因此第一段中的评论。


概述

关于这个问题,我有一个拥有多个类作为 TLists 的对象。

我在 Treeview 中表示这些数据,并且无法知道树中将存在多少级别和节点,因为它们是在运行时动态创建的。我设置的一个限制是顶级节点将被固定,这意味着它们不能被删除或重命名——这些就是我所说的 RootGroups。

Treeview 将填充项目和组,添加到 Treeview 的每个节点都将有自己的 Object 分配给数据以正确识别每个项目。我现在将展示一个示例屏幕截图,以便在继续之前给出一个更好的主意:

在此处输入图像描述

如您所见,我有两个最顶层的节点,Object1RootObject2Root。如果您注意到右侧的按钮,它们允许将组和项目添加到 Treeview,但如果它们不属于 Treeview 的该部分,它们将被禁用。例如,您不能在Object1Root下添加Object2GroupObject2Item

基本上,树视图中的所有内容都有自己的指向对象的指针。我从基础对象派生的每个对象。此基础对象具有存储它在 Treeview 中的位置的属性,如下所示:

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;

然后我可以从基础对象派生我的其他类,如下所示:

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; 

包含所有这些类的主对象如下所示:

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;

当我将主对象保存到 XML 时,我首先迭代整个 TreeView,然后为每个对象分配节点数据,例如父级、级别、索引等。基于第一个图像的输出 XML 文件如下所示:

在此处输入图像描述

注意: SomeVal 部分并不重要,因为我从来没有费心向对象写任何东西。

我真正应该做的是保存到 XML,就像树视图一样。我对 XML 不太熟悉,因为我仍在掌握它,但我认为输出应该如下所示:(用记事本编写)

<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>

然后我可以从 XML 加载 TreeView。问题是我只知道如何像现在一样保存 XML,我知道需要某种递归等,这就是我要努力的地方,特别是从 XML 文件重建树。

附件

我花了几个小时将我的实际项目代码精简为一个更易于阅读和理解的示例,它是用 Lazarus 编写的并使用OmniXML 库,我只包含源单元没有项目文件。

在这里下载(密码是stackoverflow):http ://www34.zippyshare.com/v/16401041/file.html

最终我的问题是:

  • 如何以正确的层次结构保存到 XML。
  • 如何加载 XML 并将 Treeview 重建为保存前的样子。

非常感谢。

4

2 回答 2

5

作为进一步开发的原始草案。

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.

示例调用

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;

使用的 XML 如下所示:

<?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>
于 2013-09-02T22:33:49.267 回答
0

我自己的尝试。使用 MSXML 6.0 类型库。不是太花哨,但似乎可以完成这项工作。

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.

快速示例 XML 输出:

- <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" /> 
于 2013-09-03T05:23:17.683 回答