1. Computing

Need More Images For A Virtual Tree View Node?

When Images, StateImages, CheckImages Are Not Enough For Your Virtual TreeView

By

3 Images for a treeview Node

3 Images for a treeview Node

The Virtual Treeview component is one of the GEMS of the 3rd party Delphi controls world you can use in your project.

When you need to present a tree-like data structures in your Delphi application, you can go with the out-of-the-box TTreeView component.

When your users (and you) need more speed and more feature-rich tree view look and feel - you would certainly want to take a look and try what Virtual Treeview has to offer. Hack, it is even used in the Delphi IDE.

Virtual Treeview is hosted on Google Code.

Nodes And Images

While this article is not meant to be a tutorial in using the TVirtualStringTree (the name as it appears in the component palette once installed), I'll present the basic steps in having your data appear in the tree view.

Many Delphi controls, displaying some items, expose "Images", "StateImages" and alike properties (TListView, TTreeView).

When using the TVirtualStringTree, each tree node can have several images. One is the check image which is supplied by internal image lists or a special external list (CustomCheckImages property). Another one is the state image and yet another one the normal/selected image.

Therefore each node in the tree can also display (beside the text) 2 (or 3) different images.

Think of folders and files and their display in the Windows Explorer. The state image for a folder can be used to differentiate between collapsed and expanded folders, the node image can display the file type icon.

In one of my applications, where TVirtualStringTree is heavily used, I have a similar situation: using both the state images and the normal/selected node images.

For some special nodes I needed one more image (beside state/normal) to even further individualize a single node appearance.

CustomNodeImages

So, the question is "how to have 3 (or even more) images per single node in a virtual tree view?

The funny thing is that the answer is relatively simple once you know what events to look for and how are paint cycles and paint stages for a node and the entire tree handled.

The event I am up to is "OnAfterCellPaint" - and this is where I need to draw my "special node" custom image. But, one step at a time.

A Very Short TVirtualStringTree 101 "Tutorial"

I'll suppose you know how to install the virtual tree view into the IDE and make sure it appears on the Tool Palette so that you can drop one on a Delphi form. Also, I'll presume you've read the Virtual Treeview Help.

Let's see how to have some nodes added to the tree:

1. Each node in a virtual tree view caries some custom node data. Here's what I have (for this sample, really simple):

type
  TNodeData = record
    DisplayText : string;
    ImageIndex : integer;
    StateIndex : integer;
    SpecialIndex : integer;
    IsSpecialNode : boolean;
  end;

2. Form's OnCreate is used to add some nodes to the tree. The form also hosts 3 TImageList components (filled in with 16x16 bitmap images).

procedure TVTVForm.FormCreate(Sender: TObject);
var
  i : integer;
  nd : ^TNodeData;
  node : PVirtualNode;
begin
  VirtualStringTree1.Images := SmallImageList;
  VirtualStringTree1.StateImages := StateImageList;

  VirtualStringTree1.NodeDataSize := SizeOf(TNodeData);

  for i := 0 to 4 do
  begin
    node := VirtualStringTree1.AddChild(nil);

    nd := VirtualStringTree1.GetNodeData(Node);

    nd.ImageIndex := i;
    nd.StateIndex := i;
    nd.CustomIndex := i;
    nd.IsSpecialNode := (i AND 1) = 1; //even nodes (bitwise operation)
    if nd.IsSpecialNode then
      nd.DisplayText := Format('special node %d', [i])
    else
      nd.DisplayText := Format('node %d', [i]);
  end;
end;

3. The OnGetText event is where you specify what text to appear for each node:

procedure TVTVForm.VirtualStringTree1GetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
var
  nd : ^TNodeData;
begin
  nd := Sender.GetNodeData(Node);

  CellText := nd.DisplayText;

  //leave room for my custom 3rd image
  if nd.IsSpecialNode then CellText := '      ' + CellText;
end;

4. The OnGetImageIndex event is where you specify what image to appear for each node:

procedure TVTVForm.VirtualStringTree1GetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex; var Ghosted: Boolean; var ImageIndex: Integer);
var
  nd : ^TNodeData;
begin
  nd := Sender.GetNodeData(Node);

  if Kind in [ikState] then
  begin
    ImageIndex := nd.StateIndex
  end
  else if Kind in [ikNormal, ikSelected] then
  begin
    //selected or not selected - same image
    ImageIndex := nd.ImageIndex;
  end;
end;

5. Since the TNodeData has one (wide) string field, I must ensure the data is freed from memory when nodes are freed by the tree. The OnFreeNode is the event to handle:

procedure TVTVForm.VirtualStringTree1FreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
var
  nd : ^TNodeData;
begin
  nd := Sender.GetNodeData(Node);
  Finalize(nd^);
end;
And that's it. To have your nodes display some text and some images - this is a "hello world" type of application for Virtual Treeview usage.

Note that you would certainly want to have knowledge on pointers, records, enumerations before you start developing your first virtual treeview driven application.

What Was That Question? How to have 3 (or more) images for a node!

The TNodeData's IsSpecialNode determines if this node is a special one and if a third image is needed for such nodes.

Since Images and StateImages are already used, to have the third image drawn we handle the AfterCellPaint event:

procedure TVTVForm.VirtualStringTree1AfterCellPaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; CellRect: TRect);
var
  r : TRect;
  nd : ^TNodeData;
begin
  nd := Sender.GetNodeData(Node);

  if (nd.IsSpecialNode) then
  begin
    r := Sender.GetDisplayRect(Node, Column, true);

    StateImageList.Draw(TargetCanvas, r.Left, CellRect.Top, nd.SpecialIndex);
  end;
end;
This looks more easy that it sounded at the "question time" :)

Do you see the 3 images on the screen shot from my real-world application? The first one is the image for expanded folder, the second represents that this code can be duplicated and the last one represents the country.

©2014 About.com. All rights reserved.