HelloUser
HelloUser

Reputation: 21

Delphi Loop a tcxtreeList for check and uncheck nodes depending on key values from string

I am pretty new to delphi development, I have a custom CheckTreeList component inherited from cxTreeList Devexpress component. When i check some nodes in a list, Those values are stored to a string in a format as shown below String Format for selected nodes as image The issue is I am not able to check the nodes of the checktreelist by looping through the nodes and values in the string. I have tried the below code for saving and loading the checked and unchecked nodes. Saving the checked node key values to string is working, But Loading the nodes and checking them is not working. The below is the component source code

unit DXCheckTreelist;

interface

uses
  System.Classes, cxTL, cxLookAndFeelPainters;

type

  TdxUnboundTreeListNode = class(TcxUnboundTreeListNode)

  protected
    procedure SetCheckState(AValue: TcxCheckBoxState); override;
  end;

  TdxCheckTreeList = class(TcxTreeList)
  Private
    FEnableStdTreebehaviour : Boolean;
  protected
    function CreateNode: TcxTreeListNode; override;
  Published
    Property EnableStdTreebehaviour: Boolean read FEnableStdTreebehaviour write FEnableStdTreebehaviour default False;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('DX Components', [TdxCheckTreeList]);
end;
{ TdxCheckTreeList }    
function TdxCheckTreeList.CreateNode: TcxTreeListNode;
begin
  Result := TdxUnboundTreeListNode.Create(Self);
  Changes := Changes + [tcStructure];
end;

{ TdxUnboundTreeListNode }

procedure TdxUnboundTreeListNode.SetCheckState(AValue: TcxCheckBoxState);
var
  ParentNode : TdxUnboundTreeListNode;
  PrevCheckState: TcxCheckBoxState;
const
  AState: array[TcxCheckBoxState] of TcxTreeListNodeCheckInfos = ([], [nciChecked], [nciGrayed]);
  AParentCheckState: array[Boolean] of TcxCheckBoxState = (cbsGrayed, cbsChecked);
begin

  if  TdxCheckTreeList(TreeList).FEnableStdTreebehaviour then
  begin
    inherited;
    Exit;
  end;

  if not CanChecked then
  begin
    State := State - [nsCheckStateInvalid];
    Exit;
  end;

  PrevCheckState := CheckState;
  CheckInfo := CheckInfo - [nciChecked, nciGrayed] + AState[AValue] + [nciChangeCheck];


  try
    if (CheckState in [cbsChecked, cbsUnchecked]) and HasChildren then
    begin
      LoadChildren;

      if AValue = cbsUnchecked then
        SetChildrenCheckState(CheckState, nil);
    end;

    ParentNode := TdxUnboundTreeListNode(Parent);

    if ParentNode <> nil then
    begin
      if ParentNode.IsRadioGroup and Checked then
        ParentNode.SetChildrenCheckState(cbsUnchecked, Self);

      if not (nciChangeCheck in ParentNode.CheckInfo) and (ParentNode <> Root) then
        ParentNode.CheckState := cbsChecked;
    end;
  finally
    CheckInfo := CheckInfo - [nciChangeCheck];
    State := State - [nsCheckStateInvalid];

    if CanChecked then
      Repaint(True);

    if (PrevCheckState <> CheckState) and Assigned(TcxTreeList(TreeList).OnNodeCheckChanged) then
      TcxTreeList(TreeList).OnNodeCheckChanged(TreeList, Self, CheckState);
  end;
end;
end.

In my case the property EnableStdTreebehaviour is set to true.

Code for saving selected node key values is

procedure TfrmTreeList.btnSaveDataClick(Sender: TObject);
var
  I, J: Integer;
  node, cnode: TcxTreeListNode;
  Result: String;
begin
  result:= '';
  for i := 0 to ctvMandatory.Count - 1 do
  begin
    node := TcxTreeListNode(ctvMandatory.Items[i]);
    if ctvMandatory.Items[i].CheckState in [cbsChecked, cbsGrayed] then
    begin
      if node.Level = 0 then Result:= Result + '[' + node.Values[1] + ']' + ',';

      for J := 0 to ctvMandatory.Items[i].Count - 1 do
      begin
        cnode := ctvMandatory.Items[i].Items[J];
        if (cnode.Checked) and (cnode.Level = 1) then
        begin
          Result:= Result + cnode.Values[2] + ',';
        end;
      end;
    end;
  end;

  if (Result <> '') and (Result[Length(result)] = ',') then
        result:= Copy(Result, 1, length(Result) -1 );
  Memo.Clear;

  if result <> '' then
  begin
    Memo.Lines.Add(Trim(Result));
    csv := result;
  end;
  for i := 0 to ctvMandatory.count - 1 do
  begin
    node := TcxTreeListNode(ctvMandatory.Items[i]);
    ctvMandatory.Items[i].Checked := False;
  end;
end;

Code i tried for loading and check node depend on keyvalue from string is

procedure TfrmTreeList.btnLoadDataClick(Sender: TObject);
var
  i, j, X: integer;
  node, cnode: TcxTreeListNode;
  sl,s2: TStringList;
  str: string;
  key, value, val: string;
begin
  chbAll.Checked:= csv = 'All';
  ctvMandatory.BeginUpdate;
  if chbAll.Checked then
  begin
  for i:= 0 to ctvMandatory.AbsoluteCount - 1 do
    ctvMandatory.Items[I].Checked := True;
    ctvMandatory.EndUpdate;
    SetMandatoryText;
    Exit;
  end;

  for i:= 0 to ctvMandatory.Count - 1 do
    ctvMandatory.Items[I].Checked := False;

  if csv = 'All' then
  begin
    for i:= 0 to ctvMandatory.AbsoluteCount - 1 do
      ctvMandatory.Items[I].Checked := True;
  end
  else
  if (length(csv) > 0) and (Pos(']', csv) = 0) then
  begin
    for i := 0 to ctvMandatory.Count - 1 do
    begin
      node:= TcxTreeListNode(ctvMandatory.Items[i]);
      if node.Level = 0 then
        ctvMandatory.Items[i].Checked:= True
      else
      if (node.Level = 1) and IsValueInCSV(csv, node.Values[1])  then
      begin
        ctvMandatory.Items[i].Checked := True;
      end;
    end;
  end
  else
  begin
   sl:= TStringList.Create;
   sl.Delimiter:= ',';
   sl.DelimitedText:= csv;
   node:= nil;
   s2:= TStringList.Create;
   s2.Delimiter:= ',';
   for str in sl do
   begin
     if (pos('[', str) > 0) then
     begin
       if (value <> '') and (value[Length(value)] = ',') then
          value := Copy(value, 1, length(value) -1);
       s2.DelimitedText:= value;
       if (node <> nil) and (value <> '') and (node.HasChildren) then
       begin
         for I := 0 to ctvMandatory.Count - 1 do
          begin
           while Node <> Nil do
           begin
             node:= TcxTreeListNode(ctvMandatory.Items[I]);
             node:= node.getFirstChild;
             if not node.Checked then
             begin
               val := '';
               for val in s2 do
               begin
                 node.Checked := true;
                 node.getNextSibling;
               end;
             end;
             s2.Clear;
           end;
          end;
       end;
       value:= '';
       val := '';
       key:= ReplaceStr(str, '[', '');
       key:= ReplaceStr(key, ']', '');
       for I := 0 to ctvMandatory.Count - 1 do
       begin
         if (TcxTreeListNode(ctvMandatory.Items[i]).Values[1] = key) and ((ctvMandatory.Items[i]).Level = 0) then
         begin
           node:= TcxTreeListNode(ctvMandatory.Items[i]);
           Break;
         end;
       end;
     end
     else
     begin
       value:= value + str + ',';
     end;
   end;
   if (value <> '') and (value[Length(value)] = ',') then
      value := Copy(value, 1, length(value) -1);
   s2.DelimitedText:= value;
   if (node <> nil) and (value <> '') and (node.HasChildren) then
   begin
    for I := 0 to ctvMandatory.Count - 1 do
    begin
     while Node <> Nil do
     begin
       node:= TcxTreeListNode(ctvMandatory.Items[I]);
       node:= node.getFirstChild;
       if not node.Checked then
       begin
         val := '';
         for val in s2 do
         begin
           node.Checked := true;
           node.getNextSibling;
         end;
       end;
       s2.Clear;
     end;
    end;
   end;
   sl.Free;
   s2.Free;
  end;
  ctvMandatory.EndUpdate;
  SetMandatoryText;
end;

function TfrmTreeList.IsValueInCSV(const CSV, Value: string): Boolean;
begin
  Result := IsValueInCSV(CSV, Value, False);
end;

function TfrmTreeList.IsValueInCSV(const CSV, Value: string; ResultIfBothEmpty: Boolean): Boolean;
begin
  if Trim(CSV) = Trim(Value) then
  begin
    if Trim(Value) = '' then
      Result := ResultIfBothEmpty
    else
      Result := True;
  end
  else
    Result := MatchStr(Value, SplitString(CSV, ','));
end;

Can some please check and help me on this issue?

Upvotes: 1

Views: 1804

Answers (1)

MartynA
MartynA

Reputation: 30715

Update I've updated this answer to provide a complete & self-contained example of saving the check marks of the TcxTreeList to a string (or TStringList) and then re-load them, both using the string format in the Q's screenshot. I've ignored the code in the Q and written it all from scratch, because that was easier than trying to guess what exactly you are intending to do in your code - if I was doing this myself, I wouldn't use the Q's method but instead save the tree nodes' states to a TClientDataSet or the Devex equivalent.

The example requires only a few utility routines to do its job and these all take a TcxTreeList or TcxTreeListNode as an input parameter so these could be moved to another unit and re-used by other forms.

These routines are as follows:

function RootNodeToString(RootNode : TcxTreeListNode) : String;
//  This saves a Root node and its subkeys in the format show in the Q's screenshot
//  Note:  This does NOT save the RootNode's checked state because the q did not define
//  whether it should

function TreeListNodesToString(TreeList : TcxTreeList) : String;
//  This saves all the TreeList's Root nodes and their subkeys
//  in the format show in the Q's screenshot

function RootNodeFromName(TreeList : TcxTreeList; AName : String) : TcxTreeListNode;
//  Finds the RootNode having a given name or NIL if not found

function ChildNodeFromName(RootNode : TcxTreeListNode; const AName : String) : TcxTreeListNode;
//  Finds the ChildNode (of a RootNode) having a given name or NIL if not found

function TreeListNodesToString(TreeList : TcxTreeList) : String;
//  This saves all the TreeList's Root nodes and their subkeys
//  in the format show in the Q's screenshot

function RootNodeFromName(TreeList : TcxTreeList; AName : String) : TcxTreeListNode;
//  Finds the RootNode having a given name or NIL if not found

function ChildNodeFromName(RootNode : TcxTreeListNode; const AName : String) : TcxTreeListNode;
//  Finds the ChildNode (of a RootNode) having a given name or NIL if not found

procedure ClearChecks(TreeList : TcxTreeList; ClearChildren : Boolean);
//  Clears all the checkmark in a cxTreeList

Hopefully, these are all self-explanatory. The Implementation section of the example is

const
  iCheckCol = 0;  //  the number of the checkbox column
  iNameCol  = 1;  //  the number of the name column

function RootNodeToString(RootNode : TcxTreeListNode) : String;
//  This saves a Root node and its subkeys in the format show in the Q's screenshot
//  Note:  This does NOT save the RootNode's checked state because the q did not define
//  whether it should
var
  j : Integer;
  ANode : TcxTreeListNode;
begin
  Result := '[' + RootNode.Values[iNameCol] + ']';
  for j := 0 to RootNode.Count - 1 do begin
     ANode := RootNode.Items[j];
     if ANode.Values[iCheckCol] then
       Result := Result + ',' + ANode.Values[iNameCol];
  end;
end;

function TreeListNodesToString(TreeList : TcxTreeList) : String;
//  This saves all the TreeList's Root nodes and their subkeys
//  in the format show in the Q's screenshot
var
  i : Integer;
begin
  Result := '';
  for i := 0 to TreeList.Count - 1 do begin
    if Result <> '' then
      Result := Result + ',';
    Result := Result + RootNodeToString(TreeList.Items[i]);
  end;
end;

function RootNodeFromName(TreeList : TcxTreeList; AName : String) : TcxTreeListNode;
//  Finds the RootNode having a given name or NIL if not found
var
  i : Integer;
begin
  //  First remove the square brackets, if any
  if AName[1] = '[' then
    Delete(AName, 1, 1);
  if AName[Length(AName)] = ']' then
    Delete(AName, Length(AName), 1);
  //  Next, look for AName in TreeList
  for i := 0 to TreeList.Count - 1 do begin
    Result := TreeList.Items[i];
    if CompareText(Result.Values[iNameCol], AName) = 0 then exit; //CompareText is case-insensitive
  end;
  Result := Nil; // if we get to here,  we didn't find it
end;

function ChildNodeFromName(RootNode : TcxTreeListNode; const AName : String) : TcxTreeListNode;
//  Finds the ChildNode (of a RootNode) having a given name or NIL if not found
var
  i : Integer;
begin
  for i := 0 to RootNode.Count - 1 do begin
    Result := RootNode.Items[i];
    if CompareText(Result.Values[iNameCol], AName) = 0 then exit; //CompareText is case-insensitive
  end;
  Result := Nil; // if we get to here,  we didn't find it
end;

procedure ClearChecks(TreeList : TcxTreeList; ClearChildren : Boolean);
//  Clears all the checkmark in a cxTreeList
var
  i,
  j : Integer;
  RootNode,
  ANode : TcxTreeListNode;
begin
  //  This clears the checkmarks from all the Root nodes and, optionally,
  //  their children
  TreeList.BeginUpdate;
  try
    for i := 0 to TreeList.Count - 1 do begin
      RootNode := TreeList.Items[i];
      RootNode.Values[iCheckCol] := False;
      for j := 0 to RootNode.Count - 1 do begin
        ANode := RootNode.Items[j];
        ANode.Values[iCheckCol] := False;
      end;
    end;
  finally
    TreeList.EndUpdate;
  end;
end;

procedure LoadTreeListChecksFromString(TreeList : TcxTreeList; const Input : String);
//  This clears the TreeList's checkmarks and then sets the checkmarks
//  from the Input string.
var
  RootKey,
  SubKey : String;
  RootNode,
  ChildNode : TcxTreeListNode;
  TL : TStringList;
  i : Integer;
begin
  TreeList.BeginUpdate;
  try
    //  First, clear the treelist's checkmarks
    ClearChecks(TreeList, True);

    //  Next load the Input string into a TStringList to split it into a series
    //  of Root keys and Child keys
    TL := TStringList.Create;
    try
      TL.CommaText := Input;

      //  The i variable will be used to iterate through the contents of the  StringList
      i := 0;
      while i <= TL.Count - 1 do begin
        //  The first string in TL should be  Root key
        RootKey := TL[i];
        RootNode := RootNodeFromName(TreeList, RootKey);
        Assert(RootNode <> Nil);  // will raise exception if RootNode not found
        //  The question does not say what should happen about the checkmark on the root nodes
        Inc(i);

        //  Now, scan down the entries below the Root key and process retrive each if its sub-keys;
        //  stop when we get to the next Root key or reach the end of the Stringlist
        while (i <= TL.Count - 1) and (Pos('[', TL[i]) <> 1) do begin
          SubKey := TL[i];
          ChildNode := ChildNodeFromName(RootNode, SubKey);
          ChildNode.Values[iCheckCol] := True;
          Inc(i);
        end;
      end;
    finally
      TL.Free;
    end;
  finally
    TreeList.EndUpdate;
  end;
end;

procedure TForm1.SetUpTreeList;
//  This sets up the form' cxTreeList with some Root nodes and Child nodes
//  Some of the ChildNode's checkmarks are set to save having to click around
//  to set things up manually
var
  i,
  j : Integer;
  RootNode,
  ANode : TcxTreeListNode;
begin
  for i := 0 to 3 do begin
    RootNode := cxTreeList1.Add;
    RootNode.AssignValues([Odd(i), 'RT' + IntToStr(i + 1)]);
    for j := 0 to 4 do begin
      ANode := RootNode.AddChild;
      ANode.AssignValues([Odd(i + j), Char(j + Ord('A'))]);
    end;
    RootNode.Expand(True);
  end;
  edSavedKeys.Text := TreeListNodesToString(cxTreeList1);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  SetUpTreeList;
end;

procedure TForm1.btnClearClick(Sender: TObject);
begin
  ClearChecks(cxTreeList1, True);
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  SetNodeChecked(cxTreeList1.FocusedNode, not cxTreeList1.FocusedNode.Values[iCheckCol]);
end;

procedure TForm1.SetNodeChecked(Node : TcxTreeListNode; Value : Boolean);
begin
   if Node = Nil then exit;  // do nothing
   Node.Values[iCheckCol] := Value;
end;

procedure TForm1.btnLoadClick(Sender: TObject);
begin
  ClearChecks(cxTreeList1, True);
  LoadTreeListChecksFromString(cxTreeList1, edSavedKeys.Text);
end;

end.

Original answer

The easiest way afaik to set the checkbox column of an unbound cxTreeList is simply to set the value in that column to True or False. So, assuming the CheckBox column of your cxTreeList is column 0, you can simply do this

procedure TForm1.SetNodeChecked(Node : TcxTreeListNode; Value : Boolean);
begin
   if Node = Nil then exit;  // do nothing
   Node.Values[0] := Value;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  //  toggle the checkbox of the focused node using code
  SetNodeChecked(cxTreeList1.FocusedNode, not cxTreeList1.FocusedNode.Values[0]);
end;

I assume you can weave this into your existing code. I haven't really studied it, but suspect that you could simplify it quite a lot.

Upvotes: 2

Related Questions