为了测试这个,我创建了相同的测试应用程序,但我会将数据存储在我的PVirtualNodes中.
当添加1000个根,每个1000个子节点时,单独的数据结构使用大约208 MB,而PVirtualNode只使用大约160 MB,并且它也快一点.
我认为使用单独的数据结构应该使用更少的内存,并且更快,但我想这是代价?
以下是“在PVirtualNode中存储数据”的来源:http://pastebin.com/j6L2eHJt
以下是“在单独数据结构中存储数据”的来源:http://pastebin.com/iSwR0hW1
解决方法
您需要一个用户列表,您需要一个类别列表.用户可以包含它所属的类别列表,或者类别可以包含属于它的用户列表.也许两者.
type TUser = class; TCategory = class; TContactList = class private FUsers: TObjectList<TUser>; FCategories: TObjectList<TCategory>; end; TUser = class private FCategories: TObjectList<TCategory>; public constructor Create(const DisplayName: string; SkypeID: Integer); property DisplayName: string; property SkypeID: Integer; property Categories: TObjectList<TCategory> read FCategories; end; TCategory = class private FUsers: TObjectList<TUser>; public constructor Create(const Name: string; ID: Integer); property Name: string; property ID: Integer; property Users: TObjectList<TUser> read FUsers; end; constructor TContactList.Create; begin // The contact list is the single master list of all contacts; it // owns the user objects,so set OwnsObjects = True FUsers := TObjectList<TUser>.Create(True); FCategories := TObjectList<TCategory>.Create(True); end; constructor TUser.Create; begin // A user does not own its categories; set OwnsObjects = False FCategories := TObjectList<TCategory>.Create(False); end; constructor TCategory.Create; begin // A category does not own its members; set OwnsObjects = False FUsers := TObjectList<TUser>.Create(False); end;
注意这段代码没有提到树控件.联系人列表不拥有树控件.如果确实如此,那么你就会回到几个月前开始的地方,那里有the problem of how to display users in multiple tree controls.并且还注意到用户可以出现在多个类别中,但没有一个类别拥有该用户.相反,联系人列表拥有用户,并授予类别权限以引用它们,反之亦然.
启动此项目时,您首先遇到的问题之一是树形控件中的how to detect duplicate elements.现在问题变得容易了,因为根本没有树控制.您只需在平面用户列表中找到重复项.如果您不首先在该列表中添加重复项,那么您不必担心在更复杂的GUI控件中查找重复项.
请注意,数据结构不是树.这是两个列表,列表中的每个项目都可以引用相反列表中的任意数量的项目.从这个意义上说,它实际上是一个图表.您只是将数据显示为树,否则很难将其可视化.
那么,既然你有一个独立于树控件的数据结构,你如何将树链接到它应该显示的数据?每个节点都应该保存对它所代表的TUser或TCategory的引用.节点的数据记录可以这样定义:
type PNodeData = ^TNodeData; TNodeData = record case Integer of 0: Obj: TObject; 1: User: TUser; 2: Category: TCategory; end;
您可以使用它来实现树的OnGetText事件,如下所示:
procedure TJeffForm.TreeGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; var CellText: string); var Data: PNodeData; begin if TextType = ttStatic then Exit; Data := Sender.GetNodeData(Node); Assert(Assigned(Data),'Node wasn''t initialized properly'); Assert(Assigned(Data.Obj),'Node has no object'); if Data.Obj is TUser then CellText := Data.User.DisplayName else if Data.Obj is TCategory then CellText := Data.Category.Name else CellText := Format('Unknown node type %s',[Data.Obj.ClassName]); end;
也就是说,每个节点将包含用户或类别.数组的第一个元素将包含一个属于这些类型的值,但由于您事先并不知道它将是什么,因此您有第三种类型可以安全地用作TObject.在记录的第一个字段中包含所需数据非常重要,因为这是您在调用AddNewNode时允许设置的字段,即使在节点完全初始化之前也是如此.
避免长节点创建时间的秘诀之一是避免创建不需要的节点.如果顶级节点已折叠,则实际上您不需要创建任何子节点.只需在顶部节点上设置标志,指示它有子节点,它将使用“”按钮正确绘制自己.如果用户稍后单击按钮以展开节点,则树控件将询问您有多少个子节点,此时它将创建它们.即使这样,它也只会初始化需要立即绘制的节点.延迟工作直到必要.拥有一百万个联系人的人可能永远不会想要同时看到所有这些联系人,因此不需要在GUI控件中创建一百万个项目.
程序启动时,首先需要加载用户和类别列表,然后设置树的类别计数:
Tree.RootNodeCount := ContactList.Categories.Count;
就这样.
树的事件将处理初始化阶段的其余部分.如果您希望从一开始就扩展某些类别节点,那么您所要做的就是展开它们.树的事件将询问每个节点有多少个孩子,并且您可以实现该事件来回答.一旦树为它们创建了节点,它将询问如何初始化它们,并且您可以在那时将相应的用户或类别对象分配给节点.树将告诉您何时需要更多信息.你不必再给它了.