TListView supports checkboxes out-of-the-box. You just have to set
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
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
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;
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
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
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
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
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...
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.