1. Technology
Send to a Friend via Email
TreeView with check boxes and radio buttons
Here's how to add check boxes and radio buttons to a TTreeView Delphi component. Give your applications a more professional and smoother look.
 Win prizes by sharing code!
Do you have some Delphi code you want to share? Are you interested in winning a prize for your work?
Delphi Programming Quickies Contest
 More of this Feature
• FULL SOURCE CODE
 Join the Discussion
"Post your views, comments, questions and doubts to this article."
Discuss!
 Related Resources
• TreeView to the Max (TTreeView related tutorials, tips and article)

Article originally written by Peter Thörnqvist. Sample application by Zarko Gajic

The TTreeView Delphi component (located on the "Win32" component palette tab) represents a window that displays a hierarchical list of items, such as the headings in a document, the entries in an index, or the files and directories on a disk.

Tree node with check box or radio button?
Delphi's TTreeview doesn't natively support checkboxes but the underlying WC_TREEVIEW control does. You can add checkboxes to the treeview by overriding the CreateParams procedure of the TTreeView, specifying the TVS_CHECKBOXES style for the control (see MSDN for more details). The result is that all nodes in the treeview will have checkboxes attached to them. In addition, the StateImages property can't be used anymore because the WC_TREEVIEW uses this imagelist internally to implement checkboxes. If you want to toggle the checkboxes, you will have to do that using SendMessage or the TreeView_SetItem / TreeView_GetItem macros from CommCtrl.pas. The WC_TREEVIEW only supports checkboxes, not radio buttons.

The approach you are to discover in this article is a lot more flexible: you can have check boxes and radio buttons mixed with other nodes any way you like without changing the TTreeview or create a new class from it to make this work. Also, you decide yourself what images to use for the checkboxes / radiobuttons simply by adding the proper images to the StateImages imagelist.

Tree view with check boxes and radio buttons
TreeNode with check box or radio button
Contrary to what you might believe, this is quite simple to accomplish in Delphi. Here are the steps to make it work:
  • Set up an image list (TImageList component on the "Win32" component palette tab) for the TTreeview.StateImages property containing the images for the checked and unchecked state(s) for check boxes and / or radio buttons.
  • Call the ToggleTreeViewCheckBoxes procedure (see below) in the OnClick and OnKeyDown events of the treeview. ToggleTreeViewCheckBoxes procedure alters the StateIndex of the selected node to reflect the current checked/unchecked state.

To make your treeview even more professional, you should check where a node is clicked before toggling the stateimages: by only toggling the node when the actual image is clicked, your users can still select the node without changing its state.

Additionally, if you don't want your users to expand / collapse the treeview, call the FullExpand procedure in the forms OnShow event and set AllowCollapse to false in the treeview's OnCollapsing event.

Here's the implementation of the ToggleTreeViewCheckBoxes procedure:

procedure ToggleTreeViewCheckBoxes(
   Node             :TTreeNode; 
   cUnChecked, 
   cChecked, 
   cRadioUnchecked, 
   cRadioChecked    :integer);
var
  tmp:TTreeNode;
begin
  if Assigned(Node) then
  begin
    if Node.StateIndex = cUnChecked then
      Node.StateIndex := cChecked
    else if Node.StateIndex = cChecked then
      Node.StateIndex := cUnChecked
    else if Node.StateIndex = cRadioUnChecked then
    begin
      tmp := Node.Parent;
      if not Assigned(tmp) then 
        tmp := TTreeView(Node.TreeView).Items.getFirstNode
      else
        tmp := tmp.getFirstChild;
      while Assigned(tmp) do
      begin
        if (tmp.StateIndex in 
                   [cRadioUnChecked,cRadioChecked]) then
          tmp.StateIndex := cRadioUnChecked;
        tmp := tmp.getNextSibling;
      end;
      Node.StateIndex := cRadioChecked;
    end; // if StateIndex = cRadioUnChecked
  end; // if Assigned(Node)
end; (*ToggleTreeViewCheckBoxes*)

As you can see from the code above, the procedure starts off by finding any checkbox nodes and just toggling them on or off. Next, if the node is an unchecked radiobutton, the procedure moves to the first node on the current level, sets all the nodes on that level to cRadioUnchecked (if they are cRadioUnChecked or cRadioChecked nodes) and finally toggles Node to cRadioChecked.

Notice how any already checked radio buttons are ignored. Obviously, this is because an already checked radio button would be toggled to unchecked, leaving the nodes in an undefined state. Hardly what you would want most of the time.

Here's how to make the code even more professional: in the OnClick event of the Treeview, write the following code to only toggle the checkboxes if the stateimage was clicked (the cFlatUnCheck,cFlatChecked etc constants are defined elsewhere as indexes into the StateImages image list):

procedure TForm1.TreeView1Click(Sender: TObject);
var
  P:TPoint;
begin
  GetCursorPos(P);
  P := TreeView1.ScreenToClient(P);
  if (htOnStateIcon in 
             TreeView1.GetHitTestInfoAt(P.X,P.Y)) then
    ToggleTreeViewCheckBoxes(
       TreeView1.Selected,
       cFlatUnCheck,
       cFlatChecked,
       cFlatRadioUnCheck,
       cFlatRadioChecked);
end; (*TreeView1Click*)

The code gets the current mouse position, converts to treeview coordinates and checks if the StateIcon was clicked by calling the GetHitTestInfoAt function. If it was, the toggling procedure is called.

Mostly, you would expect the spacebar to toggle check boxes or radio buttons, so here's how to write the TreeView OnKeyDown event using that standard:

procedure TForm1.TreeView1KeyDown(
  Sender: TObject; 
  var Key: Word; 
  Shift: TShiftState);
begin
  if (Key = VK_SPACE) and 
     Assigned(TreeView1.Selected) then
       ToggleTreeViewCheckBoxes(
          TreeView1.Selected,
          cFlatUnCheck,
          cFlatChecked,
          cFlatRadioUnCheck,
          cFlatRadioChecked);
end; (*TreeView1KeyDown*)

Finally, here's how the form's OnShow and the Treeview's OnChanging events could look like if you wanted to prevent collapsing of the treeview's nodes:

procedure TForm1.FormCreate(Sender: TObject);
begin
  TreeView1.FullExpand;
end; (*FormCreate*)

procedure TForm1.TreeView1Collapsing(
   Sender: TObject; 
   Node: TTreeNode; 
   var AllowCollapse: Boolean);
begin
  AllowCollapse := false;
end; (*TreeView1Collapsing*)

Finally, to check whether a node is checked you simply do the following comparison (in a Button's OnClick event handler, for example):

procedure TForm1.Button1Click(Sender: TObject);
var
  BoolResult:boolean;
  tn : TTreeNode;
begin
  if Assigned(TreeView1.Selected) then
  begin
    tn := TreeView1.Selected;
    BoolResult := tn.StateIndex in 
                  [cFlatChecked,cFlatRadioChecked];
    Memo1.Text := tn.Text + 
                  #13#10 + 
                  'Selected: ' + 
                  BoolToStr(BoolResult, True);
  end;
end; (*Button1Click*)

More complx tree view with check boxes and radio buttons Although this type of coding cannot be regarded as mission critical, it can give your applications a more professional and smoother look. Also, by using the checkboxes and radiobuttons judiciously, they can make your application easier to use. They sure will look good!

This image below was taken from a test app using the code described in this article. As you can see, you can freely mix nodes having checkboxes or radiobuttons with those that have none, although you shouldn't mix "empty" nodes with "checkbox" nodes (take a look at the radio buttons in the image) as this makes it very hard to see what nodes are related.

Download the sample aplication to explore the code.

©2014 About.com. All rights reserved.