Delphi 循环 tcxtreeList 以根据字符串中的键值检查和取消检查节点

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

我是 delphi 开发的新手,我有一个从 cxTreeList Devexpress 组件继承的自定义 CheckTreeList 组件。 当我检查列表中的某些节点时,这些值以如下所示的格式存储到字符串中 String Format for selected nodes as image 问题是我无法通过遍历字符串中的节点和值来检查 checktreelist 的节点。 我尝试了以下代码来保存和加载已检查和未检查的节点。 将检查的节点键值保存到字符串是有效的,但是加载节点并检查它们是行不通的。 下面是组件源码

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.

在我的例子中,属性 EnableStdTreebehaviour 设置为 true。

保存选定节点键值的代码是

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;

我尝试加载和检查节点的代码取决于字符串中的键值是

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;

有人可以检查并帮助我解决这个问题吗?

Update 我已经更新了这个答案以提供一个完整且独立的示例,将 TcxTreeList 的复选标记保存到字符串(或 TStringList),然后重新加载它们,都使用 Q 屏幕截图中的字符串格式。我忽略了 Q 中的代码并从头开始编写它,因为这比试图猜测您打算在代码中做什么更容易 - 如果我自己这样做,我不会使用 Q 的方法而是将树节点的状态保存到 TClientDataSet 或 Devex 等效项。

该示例只需要几个实用例程即可完成其工作,并且这些例程都将 TcxTreeList 或 TcxTreeListNode 作为输入参数,因此可以将它们移动到另一个单元并由其他表单重新使用。

这些套路如下:

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

希望这些都是不言自明的。示例的实现部分 是

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.

原回答

afaik 设置未绑定 cxTreeList 的复选框列的最简单方法是简单地 将该列中的值设置为 True 或 False。因此,假设您的 CheckBox 列 cxTreeList 是第 0 列,你可以简单地这样做

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;

我假设您可以将其编入您现有的代码中。我还没有真正研究过,但是 怀疑您可以大大简化它。