我在您的单独数据结构代码中看到的第一个问题是它错误地使类别拥有users。这意味着,如果同一个人属于两个类别,那么它将在您的数据结构中出现,就好像有两个完全不同的人一样。你会在项目的整个生命周期中后悔那个决定。
你需要一个用户列表,你需要一个类别列表。一个用户可以包含一个它所属的类别列表,或者一个类别可以包含一个属于它的用户列表。也许两者兼而有之。
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;
注意这段代码没有提到树控件。联系人列表不拥有树控件。如果是这样,那么您将回到几个月前开始的地方,在那里您遇到了如何在多个树控件中显示用户的问题。还要注意,用户可以出现在多个类别中,但没有一个类别拥有该用户。相反,联系人列表拥有用户,并授予类别权限以引用他们,反之亦然。
当您开始这个项目时,您遇到的第一个问题是如何检测树控件中的重复元素。现在这个问题要容易得多,因为根本没有树控制。您只需在平面用户列表中查找重复项。如果您一开始不向该列表添加重复项,那么您不必再担心在更复杂的 GUI 控件中查找重复项。
请注意,数据结构不是树。这是两个列表,列表中的每个项目都可以引用对面列表中的任意数量的项目。从这个意义上说,它实际上是一个图表。您只需将数据显示为树,否则很难将其可视化。
那么,既然您有一个独立于树控件的数据结构,那么如何将树链接到它应该显示的数据?每个节点都应该持有对它所代表的TUser
or的引用。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;
就这样。
树的事件将负责初始化阶段的其余部分。如果您希望从一开始就扩展某些类别节点,那么您所要做的就是扩展它们。树的事件会问你每个节点有多少孩子,你可以实现事件来回答。一旦树为它们创建了节点,它会询问如何初始化它们,您可以在那时将相应的用户或类别对象分配给节点。树会在需要更多信息时告诉您。你不必给它比它要求的更多。