Delphi TreeView with checkboxes

Delphi’s TListView supports checkboxes out-of-the-box. You just have to set Checkboxes to True and iterate through TListView.Items to find, if particular item have Checked property set to True. You can even enable your own OnCheckboxClick event (not available by default) — I wrote about it here. But very similar to TListView‘s TTreeView does not support checkboxes by default at all.

So, you have to craft entire solution yourself.

You can enable checkboxes using subclassing and message interception techniques (low-level Windows programming). Traces of such solution can be found in this StacOverflow’s question or in many other places in the Internet. I decided to go with completely different approach — mimic checkboxes with additional images drawn next to each node. Since it uses StateImages property, it does not interfere with normal images, you would like to use for your tree view.

For my solution I used this article as a base. But since it is very poorly coded and uses some strange techniques, my post is actually entirely rewritten.

The image list

First of all, you need an image list containing images of checked and unchecked state.

There are some issues, with position 0 in Delphi 7, so place them and index 1 and 2.

State changing using constants

Then declare two constants in the begining of you unit (so they will be available to all procedures):

const
    TreeNodeChecked = 1;
    TreeNodeUnChecked = 2;

If you’re using an existing image list and keep your checked / unchecked images on different positions (indexes), then adjust above values.

Now, we need a simple procedure:

procedure TMainForm.ToggleTreeNodeCheckbox(Node: TTreeNode);
begin
    if Assigned(Node) then
    begin
        if Node.StateIndex = TreeNodeChecked then
            Node.StateIndex := TreeNodeUnChecked
        else
            Node.StateIndex := TreeNodeChecked
    end;
end;

Mouse events

And two event that will handle mouse click on a checkbox and button press (space) on an item, to togle checked state:

procedure TMainForm.TreeView1MouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
    if Assigned(TreeView1.Selected) and (htOnStateIcon in TreeView1.GetHitTestInfoAt(X, Y)) then
        ToggleTreeViewCheckBoxe(TreeView1.Selected);
end;

procedure TMainForm.TreeView1KeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
    if (Key = VK_SPACE) and Assigned(TreeView1.Selected) then
        ToggleTreeViewCheckBoxe(TreeView1.Selected);
end;

If you want to check, which tree nodes are checked with checkbox, you can iterate over all Items to check, what is each item’s StateIndex property value:

folderCount := 0;

Node := TreeView1.Items.GetFirstNode;

while Assigned(Node) do
begin
    if Node.StateIndex = TreeNodeChecked then Inc(folderCount);

    Node := Node.GetNext;
end;

And that should be all as for building a tree view with checkbox support.

Handling child nodes

If you add following code to ToggleTreeNodeCheckbox procedure:

for a := 0 to Node.Count - 1 do Node.Item[a].StateIndex := Node.StateIndex;

You’ll get functionality that changing checkbox state of particular item will also change checkboxes of its child nodes. Since both Count and Item works only for immediate children, and not their descendants, this will be only one-level functionality. You need to implement recurrence for this to work for all descendants.

It could look like that for example (modification of ToggleTreeNodeCheckbox procedure):

procedure TMainForm.ToggleTreeNodeCheckbox(Node: TTreeNode);

    procedure DoItInRecurence(Node: TTreeNode);
    var
        a: Integer;
    begin
        for a := 0 to Node.Count - 1 do
        begin
            Node.Item[a].StateIndex := Node.StateIndex;
            
            if Node.Item[a].HasChildren then DoItInRecurence(Node.Item[a]);
        end;
    end;
  
begin
    if Assigned(Node) then
    begin
        if Node.StateIndex = TreeNodeChecked then
            Node.StateIndex := TreeNodeUnChecked
        else
            Node.StateIndex := TreeNodeChecked
    end;
    
    DoItInRecurence(Node);
end;

Showing temporal block

If your application will work on large structures, you may consider adding following code in the beginning of mentioned function:

Screen.Cursor := crHourglass;
MainForm.Enabled := False;

And this in the end:

MainForm.Enabled := True;
Screen.Cursor := crDefault;

VirtualTreeView as an alternative

But, if your application is about to work with a really large tree-like structures, then you should strongly consider re-implementing entire solution basing on VirtualTreeView, because TTreeView is slow. Well… it is much slower.

Actually, it is pretty much very, very slow, depending on what you understand by “slow“.

I’ve got 363 folder / 11 157 files / 88,27 GB data searched and added to TTreeView in 3 seconds (2933 ms) on dual core CPU with 4 GB or RAM and Windows 7 on board.

I wouldn’t call that slow! Actually, that’s quite fast as for me. But I know that there are many out there, who just thinks, that TTreeView sucks, because it sucks. So, I warned you…

Conclusion

Even though an article, on which I based my post, is pretty ugly written, you should consult it, if you’re looking for a solution of tree view with radio buttons (only one children can be selected). As I don’t discuss this matters here.

4 comments on “Delphi TreeView with checkboxes

    1. admin

      Please, visit my Defunct Delphi GitHub repository. At least three projects out of there (namely: dj-small-machine, filenames-cleaner and folder-tree-builder) are using TreeView. You can take them as working example to answer all your questions.

Leave a Reply