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.
Contents
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):
[code language=”delphi”]
const
TreeNodeChecked = 1;
TreeNodeUnChecked = 2;
[/code]
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:
[code language=”delphi”]
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;
[/code]
Mouse events
And two event that will handle mouse click on a checkbox and button press (space) on an item, to togle checked state:
[code language=”delphi”]
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;
[/code]
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:
[code language=”delphi”]
folderCount := 0;
Node := TreeView1.Items.GetFirstNode;
while Assigned(Node) do
begin
if Node.StateIndex = TreeNodeChecked then Inc(folderCount);
Node := Node.GetNext;
end;
[/code]
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:
[code language=”delphi”]
for a := 0 to Node.Count – 1 do Node.Item[a].StateIndex := Node.StateIndex;
[/code]
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):
[code language=”delphi”]
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;
[/code]
Showing temporal block
If your application will work on large structures, you may consider adding following code in the beginning of mentioned function:
[code language=”delphi”]
Screen.Cursor := crHourglass;
MainForm.Enabled := False;
[/code]
And this in the end:
[code language=”delphi”]
MainForm.Enabled := True;
Screen.Cursor := crDefault;
[/code]
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.
can i have the project example? i still did not get the ‘click’ on your explanation
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.
bastante pbre su aporte, ¿donde esta el link de sus ejemplos?
See my previous comment and visit Defunct Delphi GitHub repository — https://github.com/defunctdelphi — for examples. Please, use English only as I don’t speak other languages and sometimes Google Translate can translate really rubbish! :>