I am using Delphi2010 and trying to wrap my head around using VirtualStringTree.
I've been trying to get it to work with objects and was having no luck until I followed Philipp Frenzel's Virtual TreeView tutorial which I found on the soft-gems.net web site. What I've come up with so far works but I don't think I'm handling the subnodes (i.e. childnodes) correctly.
The only thing I've been able to get working is to link the whole object in again for each child and then just display the field I need - but it just feels so wrong.
Suggestions/feedback much appreciated.
I have list of objects that I'm trying to hookup with VirtualStringTree where I'm trying to achieve something like this where one of the fields will act as the label to the parent and the rest of the fields show up as childnodes.
This how my class is set up.
type
PTreeData = ^TTreeData;
TTreeData = record
FObject : TObject;
end;
TCustomerNode = class(TObject)
private
fName: string;
fSex: string;
fAge: integer;
fHair: string;
//...
public
property Name: string read fName write fName;
//...
end;
Once I populate the objects I add them to another class (CustomerObjectList) based off of TList which is mentioned below.
Here is where I connect VirtualStringTree with my object list
procedure TfrmMain.btnLoadDataClick(Sender: TObject);
var
i, j : integer;
CustomerDataObject: TCustomerNode;
RootXNode, XNode: PVirtualNode;
Data: PTreeData;
begin
vstree.NodeDataSize := SizeOf( TTreeData );
vstree.BeginUpdate;
for i := 0 to CustomerObjectList.Count - 1 do
begin
CustomerDataObject := CustomerObjectList[i];
//load data for parent node
RootXNode := vstree.AddChild(nil);
Data := vstree.GetNodeData(RootXNode);
Data^.FObject:= PINodeSource;
//now add children for rest of fields
//Isn't there a better way to do this?
for j := 1 to NUMBERofFIELDS -1 do //first field is label for parent so -1
begin
XNode := vstree.AddChild(RootXNode);
Data := vstree.GetNodeData(XNode);
Data^.FObject:= PINodeSource;
end;
end;
vstree.EndUpdate;
end;
procedure TfrmMain.vstreeGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
var
Data : PTreeData;
begin
Data := vstObjects.GetNodeData(Node);
////if Node.ChildCount = 0 then //edit - oops typo!
if Node.ChildCount > 0 then
begin
CellText := TCustomerNode(Data.FObject).Name;
exit;
end;
//handle childnodes
case Node.Index of
0: CellText := TCustomerNode(Data.FObject).Sex;
1: CellText := IntToStr(TCustomerNode(Data.FObject).Age);
2: CellText := TCustomerNode(Data.FObject).Hair;
3: CellText := TCustomerNode(Data.FObject).City;
end;
end;
You don't need to load all the data into the tree. You can use the 'virtualness' of the tree. Here's how I would do it.
Set the RootNodeCount of the tree to the number of records in CustomerObjectList:
vstree.RootNodeCount := CustomerObjectList.Count;
Then, in the OnInitChildren event, set the child count of level 0 nodes to the number of properties you want to display:
procedure TfrmMain.vstreeInitNode(Sender: TBaseVirtualTree; ParentNode, Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates);
begin
if Sender.GetNodeLevel(Node) = 0 then
begin
Sender.ChildCount[Node] := 4;
// Comment this out if you don't want the nodes to be initially expanded
Sender.Expanded[Node] := TRUE;
end;
end;
Now, just retrieve the correct data in the OnGetText event.
procedure TfrmMain.vstreeGetText(Sender: TBaseVirtualTree;
Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType;
var CellText: string);
begin
if Column <= 0 then
begin
if Sender.GetNodeLevel(Node) = 0 then
CellText := CustomerObjectList[Node.Index].Name else
if Sender.GetNodeLevel(Node) = 1 then
begin
case Node.Index of
0: CellText := CustomerObjectList[Parent.Node.Index].Sex;
1: CellText := CustomerObjectList[Parent.Node.Index].Age;
2: CellText := CustomerObjectList[Parent.Node.Index].Hair;
3: CellText := CustomerObjectList[Parent.Node.Index].City;
end; // of case
end;
end;
You'll probably want to add in some more range checking, just in case.
Hope this helps.