delphicheckboxtreeview

Treeview with checkboxes in Delphi and use of csPartial


I want to achieve the following (using Delphi 12.2):

A TreeView with Checkboxes and two levels (each parent node has two or more child nodes, but no grandchild nodes).

For a node to be partially checked, I must select csPartial in CheckStyles. However, now each checkbox (both parent and child checkboxes) cycle through three states while being clicked, whereas they should alternate between checked and unchecked. I can programmatically change the state of a node to partially checked by setting its CheckState to ncsPartial, but this only works if CheckStyles is set to csPartial, and that results in the three-state cycle.

I realize that I will need to write some code to check or uncheck all child nodes when a parent is checked or unchecked. That is no problem. I just can't figure out how to make a parent node show as partially checked without including csPartial in CheckStyles.


Solution

  • I just can't figure out how to make a parent node show as partially checked without including csPartial in CheckStyles.

    Unfortunately, you cannot. csPartial is required.

    However, you can use a combination of the TTreeView.OnCheckStateChanging and TTreeView.OnCheckStateChanged events to achieve the effect that you want, eg:

    type
      TMyForm = class(TForm)
        TreeView1: TTreeView;
        procedure TreeView1CheckStateChanging(Sender: TCustomTreeView;
          Node: TTreeNode; NewCheckState, OldCheckState: TNodeCheckState;
          var AllowChange: Boolean);
        procedure TreeView1CheckStateChanged(Sender: TCustomTreeView;
          Node: TTreeNode; CheckState: TNodeCheckState);
      private
        { Private declarations }
        ChangingNodeStates: Boolean;
      public
        { Public declarations }
      end;
    
    ...
    
    procedure TMyForm.TreeView1CheckStateChanged(Sender: TCustomTreeView;
      Node: TTreeNode; CheckState: TNodeCheckState);
    begin
      if ChangingNodeStates then Exit;
      ChangingNodeStates := True;
      try
        case CheckState of
          ncsChecked, ncsUnchecked: begin
            if Node.Level = 0 then
            begin
              for var I := 0 to Node.Count-1 do
              begin
                Node.Item[I].CheckState := CheckState;
              end;
            end else
            begin
              for var I := 0 to Node.Parent.Count-1 do
              begin
                if Node.Parent.Item[I].CheckState <> CheckState then
                begin
                  Node.Parent.CheckState := ncsPartial;
                  Exit;
                end;
              end;
              Node.Parent.CheckState := CheckState;
            end;
          end;
        end;
      finally
        ChangingNodeStates := False;
      end;
    end;
    
    procedure TMyForm.TreeView1CheckStateChanging(Sender: TCustomTreeView;
      Node: TTreeNode; NewCheckState, OldCheckState: TNodeCheckState;
      var AllowChange: Boolean);
    begin
      if (NewCheckState = ncsPartial) and (not ChangingNodeStates) then
      begin
        AllowChange := False;
        Node.CheckState := ncsUnchecked;
      end;
    end;
    

    The idea being that if the user clicks on a checked or partial node then it will be forced to unchecked, while your code is allowed to set the states to whatever you want whenever a given node changes its state.