什么数据结构最适合VirtualStringTree?

7
我猜使用过Delphi的VirtualStringTree的人都会认为它是一个很棒的控件。它是一个“虚拟”控件(您的数据必须在别处保存),所以我在思考什么样的数据结构最适合这样的任务?在我看来,这种数据结构必须支持层次结构,必须快速且易于扩展。最简单的实现方法是使用记录,这就是大多数可以找到的文档所建议的。但是,如果需要进行一些快速查找、计算总数等操作,应该使用哪种数据结构与VirtualStringTree一起使用呢?
编辑1:我正在使用Delphi 2010。
好的,我将尝试提供更多关于我的要求的细节。数据大小可能变化很大,从1个到数千个项目不等。每个项目可以包含多个字符串、整数值。我需要随机访问,我的数据在应用程序生命周期内可能会多次更改。良好的性能非常可取。我还需要数据保存和重新加载。

编辑2:已经收到1个答案,所以我会尝试评论我的观点。感谢Dorin的回答,但我认为你的结构不是很方便。 1)它没有处理层次结构。 2)为每个节点分别使用TStringList或TList不是非常有效的方法。通过这种实现,我只能查找当前节点的数据,但不能在整个树中进行有效的搜索。

我认为这种数据结构必须像一棵树。它必须有具有添加子项功能的节点。然后我只需在OnInitNode事件中获取节点数据,检查我的节点是否有子项,如果有,则设置ivsHasChildren标志,然后在OnInitChildren事件中设置正确的子项计数。稍后在OnGetText事件中,我只需从我的节点结构中获取所需的数据,并根据列索引将其设置为CellText。我的想法是拥有一个单独的数据结构,并在不需要使用VirtualStringTree的情况下执行所有必要的操作。希望有人理解我的观点 :)

编辑3:我发现了一个非常有趣的JclTrees单元,乍一看似乎可以用来实现我正在寻找的内容。它属于JCL库。缺乏体面的文档使得快速调查其功能变得困难。可能在我有更多时间时会深入研究它。


@Linas,您能告诉我们更多关于您希望存储的数据吗?它有多大?在您的过程生命周期中如何变化?您需要随机访问吗?有哪些性能限制?目前我觉得您的问题太笼统了,无法提供有意义的建议。 - David Heffernan
1
@Linas 随机访问意味着你需要一个数组。 - David Heffernan
@dorin TList是一个数组。 - David Heffernan
@Dorin,TList<your type> 是一个 <your type> 数组。只有当 <your type> 是指针时,它才是指针数组 :) - Cosmin Prund
@Linas 我明白你的意思。我一直都懂。当你说你想要随机访问时,你是什么意思?你能详细说明一下吗?从技术上讲,随机访问意味着O(1)访问,通常意味着一个数组。你真的是这个意思吗? - David Heffernan
显示剩余7条评论
3个回答

5

好的,因为给出的答案没有解决我的问题,我编写了自己的树形数据结构,模仿了TVirtualStringTree,并处理了我在问题中提到的所有问题。现在我可以选择性地使用我的数据结构,所有对它的更改都会自动更新VirtualStringTree。我想我会稍后上传源代码并在这里发布链接。感谢所有的回答。

编辑:我已经将源代码上传到Google代码:svTrees。有一个小演示展示它是如何工作的。


1
很好,你已经解决了它,但我要评论的是,你没有得到令你满意的答案的一个重要原因是你的问题有些含糊不清。 - David Heffernan
@David 也许你是对的,但解决方案并不容易实现。也许正因为这种复杂性,我很难描述我的问题。 - Linas
可能在这里尝试解释问题有助于您更好地理解它!通常情况下都是这样的。 - David Heffernan

3

您没有说明您的Delphi版本,因此:

我建议使用记录(我不确定在哪个Delphi版本中添加了记录方法,我从D7移动到了D2010),这样您就可以拥有类似以下内容的东西:

type
  TMyRecordWithMethods = record
    function GetMeAResult: Integer;
    procedure DoSomething(const AInParam: Integer; var AOutParam: Integer);
  end;

如果您的Delphi版本不支持带有方法的记录,并且确实需要节点方法,则必须使用对象来实现此操作,同时还需查看泛型。
由于您只需要容纳几千个项目,我建议使用泛型(在我看来,无需重新发明轮子),即。
uses ..., Generics.Collections;

type
  TMyNode = class(TObject)// you can leave this out if you like
    MyIntList: TList<Integer>; // you can do lookups, you have to implement your own saving/loading methods
    MyStringList: TStringList or TList<string>; // you can do lookups in both cases, use TStringList for save/load of data
  end;

现在我假设您想要存储虚拟树中的所有项并稍后加载它们,您可以通过定义自己的文件结构来实现此目的,即:

type
  TMyFileHeader = record
    CountItems: Integer; // number of items in the tree
    ...
  end;

const
  szMyFileHeader = SizeOf(TMyFileHeader);

type
  TMyItemEntry = record
    CountInt: Integer; // number of integer values
    ...
  end;

const
  szMyItemEntry = SizeOf(TMyItemEntry);

现在您需要实现加载和保存,我建议使用TFileStream进行保存和加载 -- 非常简单,
伪代码,抱歉没有时间提供部分代码 :-\
a) 保存内容:
- 将项目数量保存到TMyFileHeader变量中并写入文件 - 对于树中的每个项目,保存整数列表,保存字符串列表
b) 加载内容:
- 读取文件头 -- 这样您就知道需要从文件中读取多少个项目 - 使用 for Index := 0 to Count -1 从文件中读取项目
注意:您可以直接将每个项目的字符串列表保存到文件流的当前位置,但最好使用以下方法直接保存它:
FileStream.WriteBuffer(PChar(AStringList.Text)^, Length(AStringList.Text) * SizeOf(Char));

我希望这可以帮到你,代码的实际实现取决于你自己,祝你玩得愉快!

1
这并没有解决层次结构的问题。 - David Heffernan
@Dorin 我并没有提到对象。说实话,我认为记录很好。话虽如此,对象不会使用更多内存-TObject没有成员字段。我的观点是问题涉及层次结构,而不是如何在节点中存储信息。 - David Heffernan
@David,别太防御了啊 :) 但是关于“话说,对象不会使用更多的内存”——为什么会有人使用没有字段的对象呢?记录也是一样,但如果您在对象中添加一个整数字段和在记录中添加一个整数字段,当您查看.InstanceSize时,您将会看到差异。现在,乘以项(和子项)的数量,就会浪费大量的内存 + 创建和释放过程需要相当多的CPU周期(如果有许多项)。 - user497849
@Linas,你仍然没有提供关于你的最终目标太多的细节,即你希望展示多少项,为什么需要子类,记住你也可以在记录上使用辅助方法,或者也许,只是也许,对象是你需要干净快速完成工作的东西。 - user497849
@Dorin 他没有提到子类化。他想要有关与树视图并行存在的数据结构的建议。但是你说得对,这个问题缺乏细节,使得回答不够准确。 - David Heffernan
显示剩余8条评论

1
你可以使用TXMLDocument。
如果你想更好地控制你要放进去的内容,我建议你创建一个描述你想要的结构的xsd,并使用XML数据绑定向导生成Delphi代码,以便你使用。
这个模式。

alt text

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
    <xs:complexType name="itemType">
        <xs:sequence>
            <xs:element name="id" type="xs:int"/>
            <xs:element name="name" type="xs:string"/>
            <xs:element name="itemlist" type="itemlistType" minOccurs="0"/>
        </xs:sequence>
    </xs:complexType>
    <xs:complexType name="itemlistType">
        <xs:sequence>
            <xs:element name="item" type="itemType" minOccurs="0" maxOccurs="unbounded"/>
        </xs:sequence>
    </xs:complexType>
    <xs:element name="root">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="itemlist" type="itemlistType"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
</xs:schema>

将为您提供这些 Delphi 接口以便使用

  IXMLRoot = interface(IXMLNode)
    ['{16C6C960-58B7-400C-9E46-7ACC7BEF276F}']
    { Property Accessors }
    function Get_Itemlist: IXMLItemlistType;
    { Methods & Properties }
    property Itemlist: IXMLItemlistType read Get_Itemlist;
  end;

{ IXMLItemlistType }

  IXMLItemlistType = interface(IXMLNodeCollection)
    ['{59F80BAC-887E-48DF-8288-95276BF9DCE7}']
    { Property Accessors }
    function Get_Item(Index: Integer): IXMLItemType;
    { Methods & Properties }
    function Add: IXMLItemType;
    function Insert(const Index: Integer): IXMLItemType;
    property Item[Index: Integer]: IXMLItemType read Get_Item; default;
  end;

{ IXMLItemType }

  IXMLItemType = interface(IXMLNode)
    ['{1218DD35-C3EF-40E6-831A-1A4AA0782C36}']
    { Property Accessors }
    function Get_Id: Integer;
    function Get_Name: WideString;
    function Get_Itemlist: IXMLItemlistType;
    procedure Set_Id(Value: Integer);
    procedure Set_Name(Value: WideString);
    { Methods & Properties }
    property Id: Integer read Get_Id write Set_Id;
    property Name: WideString read Get_Name write Set_Name;
    property Itemlist: IXMLItemlistType read Get_Itemlist;
  end;

有趣的解决方案,但我不是TXMLDocument的忠实粉丝,因为它有外部依赖性。 - Linas
XML仍然是一种很好的树形结构,因此如果您不喜欢TXMLDocument,可以使用其他东西来访问和修改XML数据结构。 - Mikael Eriksson
我甚至不想想象这个解决方案的缓慢程度。 - Johan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接