Virtual Treeview是一套Delphi下优秀的VCL控件,代码质量高,使用灵活、功能强大、性能非常好,可以用于表达Treeview和表格类数据。它的代码现在托管在google code上。
这套控件使用了很久了,非常满意其表现,之前一直使用V4版,现在V5正式发布了,新版花了几年时间进行重构, 代码结构更加合理,去除了老版大量的小缺陷,很多功能进行了重新设计,参与维护的人也更多了。
Virtual Treeview的设计思路与官方的Treeview完全不一样,VT是基于高性能和更丰富的表现而开发的,在上手方面比Treeview要慢一些,但一旦上手,就会发现非常好用。
在使用VT之前,就应该设计好以什么方式展示数据,每个节点应该有些什么属性。在构思好后,就可以按以下步骤来实现VT的使用了。
1、节点数据
在实际使用Virtual Treeview之前,应该先设计好一个节点数据的结构体,用于方便每个节点的展示。
比如一个典型的设计如下:
PTreeData = ^TTreeData;
TTreeData = record
Level: integer; // 节点级别:0根;1节点;2参数
NodeType: integer; // 节点类型
Id: Integer; // 数据id
Caption: String; // 节点标题
obj : TObject; // 节点对应的对象或空
end;
其中Caption用于控制节点显示,Id用于方便节点对应数据的快速定位,obj用于把节点与相关的对象关联在一起,其它数据也是用于方便进行数据处理的。
2、列的处理
一般情况下,如果是只当成普通Treeview,无需考虑列(Column)的处理,直接使用即可。如果一行有多列数据需要显示,则得先在属性Header-Columns中增加所需的列,并设置合适的列宽。缺省列(Column)设置为空,列索引(TColumnIndex)应该为-1,在设置了列后,第一列的列索引为0,第二列的列索引为1。
3、第一个节点
在树中的所有节点中,只有根节点的父节点为空(nil)。创建根节点的示例代码如下:
var
Data : PTreeData;
Node: PVirtualNode;
RootNode: PVirtualNode;
vst.Clear; // 清除所有节点
vst.NodeDataSize := Sizeof(TTreeData); // 设置节点数据大小
RootNode := vst.AddChild(nil); // 增加一个根节点
vst.ValidateNode(RootNode, false);
Data := vst.GetNodeData(RootNode); // 获取节点数据
Data.Caption := '根节点1'; // 设置将要显示在节点的文本信息
4、多级节点
创建完全根节点后,就可以在根节点后增加各级子节点了。
Node := vst.AddChild(RootNode); // 增加一级子节点
data := vst.GetNodeData(Node); // 获取节点数据
data.Level := vst.GetNodeLevel(Node); // 设置节点级别,方便后继处理
Node := vst.AddChild(Node); // 增加二级子节点
data := vst.GetNodeData(Node);
data.Level := vst.GetNodeLevel(Node);
Node := vst.AddChild(RootNode); // 再增加一个一级子节点
data := vst.GetNodeData(Node);
data.Level := vst.GetNodeLevel(Node);
// 当然这里可以批量产生多个子节点,比如
// for i:=0 to 99 do
// begin Node := vst.addchild(rootNode); ... end;
vst.FullExpand(RootNode); // 把根节点下的所有子节点打开
上面只是简单示例,完整示例中需要把节点数据都设置完整。
这里还有一种方式进行更高速的创建子节点,比如:
vst.ChildCount[RootNode] := 100;
这样,就可以一下给根节点设置100个一级子节点。当然,这些节点的数据都还是缺省状态,我们可以在适当的时候和适当的位置再进行设置。
如果我们需要一次插入多个节点,为了提高显示效率,我们应该在插入前调用BeginUpdate,在插入完成后再调用EndUpdate。
5、显示节点内容
运行后,可以发现树的确出来了,但所有节点显示的都是似乎"Node"字样的东西,怎么样把我们需要显示的内容显示出来?
我们需要设置Virtual Treeview的GetText事件,VT在显示节点信息时会调用GetText来获取要显示的内容。我们可以增加类似下面的代码:
procedure TfrmMain.vstGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
var
Data : PTreeData;
begin
CellText := '';
Data := vst.GetNodeData(Node);
if not Assigned(Data) then
exit;
Case Data.Level of
0:
begin
CellText := Data.Caption;
end;
1:
begin
CellText := format('一级子节点',[]);
end;
2:
begin
CellText := format('二级子节点',[]);
end;
end;
end;
当然,实际代码要复杂的多。而且这个代码并没有处理多列的情况,如果有多列就得针对各列再加一级case语句就可以了。也就是说类似这样的代码,假设我们设置了一个节点有三列:
procedure TfrmMain.vstGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
var
Data : PTreeData;
begin
CellText := '';
Data := vst.GetNodeData(Node);
if not Assigned(Data) then
exit;
Case Column of
0: // 第一列的处理
begin
Case Data.Level of
0:
begin
CellText := Data.Caption;
end;
1:
begin
CellText := format('一级子节点',[]);
end;
2:
begin
CellText := format('二级子节点',[]);
end;
end;
end;
1: // 第二列的处理
begin
// 一般是取Data中obj对象,再取其中的数据用于处理显示
end;
2: // 第三列的处理
begin
// 一般是取Data中obj对象,再取其中的数据用于处理显示
end;
end;
end;
也就是说vst各节点的内容,其实都是通过代码来控制并设置的。无论是Treeview形式还是表格形式或两者综合体,数据的显示都是这么处理的。
5、节点图标
经常我们需要在每个节点前加个小图标,可用于表达这个节点的状态、类型或纯美观。Virtual Treeview显示图标一样也是要与TImageList配合使用的。我们需要先在一个ImageList中加载上合适的所有图标,然后在GetImageIndex事件中进行判断处理每个节点应该使用的图标的索引来加载ImageList中的图标。
比如:
procedure TfrmMain.vstGetImageIndex(Sender: TBaseVirtualTree;
Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex;
var Ghosted: Boolean; var ImageIndex: Integer);
var
Data : PTreeData;
begin
Data := vst.GetNodeData(Node);
if not Assigned(Data) then
exit;
Case Data.Level of
0:
begin
ImageIndex := 2;
end;
1:
begin
ImageIndex := 0;
end;
2:
begin
ImageIndex := 0;
end;
end;
end;
6、节点数据空间的清理
在Virutal Treeview组件释放时,我们应该同时清理掉每个节点对应的数据空间。我们应该在FreeNode事件中写上以下类似的代码来完成这个工作:
procedure TfrmMain.vstFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode); var Data : PTreeData; begin Data := vst.GetNodeData(Node); if Assigned(Data) then begin Data.Caption := ''; // String must be cleaned manual, otherwise there is memory leak // 节点数据记录中对应的数据对象,不一定得在这释放,可以在其它地方统一处理 //FreeAndNil(Data.obj);
end; end;
节点数据空间不需要我们释放,在前面对vst初始化时写过这么一句代码:
vst.NodeDataSize := Sizeof(TTreeData); // 设置节点数据大小
这里就是初始化节点数据记录空间的,这个空间会被vst自动释放。但这个记录体中的类似字符串或指针所指向的内存空间或对像是不会被自动释放的。所以,这里我们把Data.Caption字符串清空,以避免内存泄漏。