Skip to main content

A Windows-native, tristate checkbox TTreeView control

Recently I had to create a Delphi VCL form with a tree-like control. It should be a piece-of-cake with Delphi: just dropped a TTreeView control on the form and I was almost there. But there was one gotcha: I wanted to have checkboxes in each node. Worse: checkboxes that could hold three different states (checked, unchecked, partial).

CheckTreeViewSample

It’s very rare that I have to build complex GUI applications (lucky me) thus I had hope that in most recent VCL all I had to use was to enable some kind of property in the TTreeView component. To my disappointment, there is not such support for checkbox in the tree view.

Since I didn’t want to use a 3rd party control in this project, I had to find a way to do that manually. I googled for it, and all I could find was the same old way of solving things: create images for the checkbox states, and use StateIndex property to read/write the checkbox state. I just couldn’t believe this is still the way to do it in 2021.

After more research I found out that Windows Vista and on (sorry, XP folks, we have to move forward eventually) provides an extended style to the native tree view control that allows tristate checkboxes. That’s exactly what I wanted. I was really annoyed that I’d had to “create” (find somewhere) images for checked state, unchecked state, partial state… Worse, I’d have to find normal-dpi and high-dpi versions. Using a Windows-native tristate checkbox was the way to go for me.

Enabling tristate checkboxes is as simple as using one line of code to modify the extended style of the Windows control, adding the TVS_EX_PARTIALCHECKBOXES style:

    TreeView_SetExtendedStyle(TreeView1.Handle, TVS_EX_PARTIALCHECKBOXES, TVS_EX_PARTIALCHECKBOXES);

But since I needed more of that – inspect and modify the checkbox state, automatically select child nodes, etc., I wrapped everything it under a TTreeView helper and made it available in the CheckTreeView GitHub repository.

Another advantage is that this not a different component, so no need to install any package and use a different class at runtime. Just use the regular TTreeView component.

Enabling tristate checkboxes

Since it’s just a class helper, to use it, just add the unit CheckTreeView to your form unit:

    uses {...}, CheckTreeView;

Then just call EnableTristateCheckboxes method:

    TreeView1.EnableTristateCheckboxes;

This is enough to add checkboxes to all nodes, and change the node check state when users clicks the checkbox or press space key when a node is selected.

Reading or changing the check state of a node

You can use CheckState property of a node to read or modify the state of the checkbox:

    Node := TreeView1.Selected;
case Node.CheckState of
csUnchecked: Node.Text := 'unchecked';
csChecked: Node.Text := 'checked';
csPartial: Node.Text := 'partial';
end;
    TreeView1.Selected.CheckState := csChecked;

Automatic check states

If you want the TTreeView control to perform these operations automatically when the user checks/unchecked a node:

  1. Check/uncheck all child items of the modified node;
  2. Update the parent node check state based on the check state of the child nodes;
  3. Allow just check/uncheck state for the modified node.

Then add OnMouseDown and OnKeyPress event handlers for your TTreeView component and add the following code:

procedure TForm6.TreeView1KeyPress(Sender: TObject; var Key: Char);
begin
TreeView1.HandleKeyPress(Key);
end;

procedure TForm6.TreeView1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
TreeView1.HandleMouseDown(Button, Shift, X, Y);
end;

That’s it, I hope you enjoy it. Grab it from GitHub: https://github.com/landgraf-dev/CheckTreeView and let me know what you think by adding your comment below!

Discuss about this article in our forum!