You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何保存含所有节点及关联记录对象的TreeView并快速加载?

解决Delphi TreeView节点关联记录的高效保存与加载问题

核心问题分析

直接将TreeView保存到Blob时,仅能存储节点的UI属性(文本、层级、图像索引等),但节点Data属性关联的TMyRec指针是进程内的内存地址,无法跨运行时复用;逐项加载4000+节点时,频繁的内存分配和文件IO操作会导致加载速度极慢。

可行实现思路与示例

思路1:序列化TreeView结构+所有记录数据到流

将节点的结构信息(父节点索引、文本、图像索引等)和TMyRec的实际数据一起写入流,加载时先为所有记录分配内存,再重建TreeView节点并关联对应记录指针。

步骤与代码示例

  1. 定义自定义记录及序列化辅助方法
type
  TMyRec = record
    ID: Integer;
    Name: string;
    Value: Double;
    // 其他自定义字段
  end;

// 将TMyRec序列化到流
procedure WriteMyRecToStream(Stream: TStream; const Rec: TMyRec);
begin
  Stream.WriteBuffer(Rec.ID, SizeOf(Rec.ID));
  // 单独序列化字符串:先写长度再写内容
  var StrLen := Length(Rec.Name);
  Stream.WriteBuffer(StrLen, SizeOf(StrLen));
  if StrLen > 0 then
    Stream.WriteBuffer(Rec.Name[1], StrLen * SizeOf(Char));
  Stream.WriteBuffer(Rec.Value, SizeOf(Rec.Value));
end;

// 从流反序列化TMyRec
procedure ReadMyRecFromStream(Stream: TStream; out Rec: TMyRec);
var
  StrLen: Integer;
begin
  Stream.ReadBuffer(Rec.ID, SizeOf(Rec.ID));
  Stream.ReadBuffer(StrLen, SizeOf(StrLen));
  SetLength(Rec.Name, StrLen);
  if StrLen > 0 then
    Stream.ReadBuffer(Rec.Name[1], StrLen * SizeOf(Char));
  Stream.ReadBuffer(Rec.Value, SizeOf(Rec.Value));
end;
  1. 保存TreeView及关联记录到流
procedure SaveTreeViewWithRecs(TreeView: TTreeView; Stream: TStream);
var
  I: Integer;
  Node: TTreeNode;
  Rec: TMyRec;
  HasRec: Boolean;
begin
  // 先写入节点总数
  Stream.WriteBuffer(TreeView.Items.Count, SizeOf(Integer));
  
  for I := 0 to TreeView.Items.Count - 1 do
  begin
    Node := TreeView.Items[I];
    // 写入节点基础信息:父节点索引(无父节点则为-1)、文本、图像索引、选中图像索引
    Stream.WriteBuffer(Integer(Node.Parent.Index), SizeOf(Integer));
    var StrLen := Length(Node.Text);
    Stream.WriteBuffer(StrLen, SizeOf(StrLen));
    if StrLen > 0 then
      Stream.WriteBuffer(Node.Text[1], StrLen * SizeOf(Char));
    Stream.WriteBuffer(Node.ImageIndex, SizeOf(Integer));
    Stream.WriteBuffer(Node.SelectedIndex, SizeOf(Integer));
    
    // 写入关联的TMyRec数据
    HasRec := Assigned(Node.Data);
    Stream.WriteBuffer(HasRec, SizeOf(Boolean));
    if HasRec then
    begin
      Rec := TMyRec(Node.Data^);
      WriteMyRecToStream(Stream, Rec);
    end;
  end;
end;
  1. 从流加载TreeView及关联记录
procedure LoadTreeViewWithRecs(TreeView: TTreeView; Stream: TStream);
var
  NodeCount, I, ParentIdx, ImgIdx, SelIdx, StrLen: Integer;
  NodeText: string;
  ParentNode: TTreeNode;
  PRec: ^TMyRec;
  Rec: TMyRec;
  HasRec: Boolean;
begin
  TreeView.Items.BeginUpdate;
  try
    TreeView.Items.Clear;
    // 读取节点总数
    Stream.ReadBuffer(NodeCount, SizeOf(Integer));
    
    for I := 0 to NodeCount - 1 do
    begin
      // 读取节点基础信息
      Stream.ReadBuffer(ParentIdx, SizeOf(Integer));
      Stream.ReadBuffer(StrLen, SizeOf(StrLen));
      SetLength(NodeText, StrLen);
      if StrLen > 0 then
        Stream.ReadBuffer(NodeText[1], StrLen * SizeOf(Char));
      Stream.ReadBuffer(ImgIdx, SizeOf(Integer));
      Stream.ReadBuffer(SelIdx, SizeOf(Integer));
      
      // 创建节点
      if ParentIdx = -1 then
        ParentNode := nil
      else
        ParentNode := TreeView.Items[ParentIdx];
      ParentNode := TreeView.Items.AddChild(ParentNode, NodeText);
      ParentNode.ImageIndex := ImgIdx;
      ParentNode.SelectedIndex := SelIdx;
      
      // 读取并关联TMyRec
      Stream.ReadBuffer(HasRec, SizeOf(Boolean));
      if HasRec then
      begin
        GetMem(PRec, SizeOf(TMyRec));
        ReadMyRecFromStream(Stream, PRec^);
        ParentNode.Data := PRec;
      end;
    end;
  finally
    TreeView.Items.EndUpdate;
  end;
end;

思路2:用索引替代指针关联记录

将所有TMyRec存储在TList<TMyRec>中,节点Data属性存储该记录在列表中的索引(转为Pointer类型)。加载时先恢复列表,点击节点时通过索引从列表中获取记录。

代码示例

  1. 保存逻辑
var
  RecList: TList<TMyRec>;

// 初始化记录列表,将节点记录转存为索引关联
procedure InitRecList(TreeView: TTreeView);
var
  Node: TTreeNode;
begin
  RecList := TList<TMyRec>.Create;
  for Node in TreeView.Items do
  begin
    if Assigned(Node.Data) then
    begin
      RecList.Add(TMyRec(Node.Data^));
      Node.Data := Pointer(RecList.Count - 1); // 存储索引
    end;
  end;
end;

// 保存记录列表和TreeView到流
procedure SaveTreeViewAndRecList(TreeView: TTreeView; Stream: TStream);
begin
  // 先保存记录总数
  Stream.WriteBuffer(RecList.Count, SizeOf(Integer));
  for var Rec in RecList do
    WriteMyRecToStream(Stream, Rec);
  
  // 保存TreeView结构(此时节点Data是索引,可直接序列化)
  TreeView.SaveToStream(Stream);
end;
  1. 加载逻辑
procedure LoadTreeViewAndRecList(TreeView: TTreeView; Stream: TStream);
var
  RecCount, I: Integer;
  Rec: TMyRec;
  Node: TTreeNode;
begin
  TreeView.Items.BeginUpdate;
  try
    // 加载记录列表
    Stream.ReadBuffer(RecCount, SizeOf(Integer));
    RecList := TList<TMyRec>.Create;
    for I := 0 to RecCount - 1 do
    begin
      ReadMyRecFromStream(Stream, Rec);
      RecList.Add(Rec);
    end;
    
    // 加载TreeView结构
    TreeView.LoadFromStream(Stream);
    
    // 重新关联节点与记录
    for Node in TreeView.Items do
    begin
      if Node.Data <> nil then
      begin
        var Idx := Integer(Node.Data);
        // 直接关联列表中记录的地址(若需修改记录,可分配内存拷贝)
        Node.Data := @RecList[Idx];
      end;
    end;
  finally
    TreeView.Items.EndUpdate;
  end;
end;

// 节点点击事件处理
procedure TForm1.TreeView1Click(Sender: TObject);
var
  Node: TTreeNode;
  Idx: Integer;
  Rec: TMyRec;
begin
  Node := TreeView1.Selected;
  if Assigned(Node) and Assigned(Node.Data) then
  begin
    Idx := Integer(Node.Data);
    Rec := RecList[Idx];
    // 使用记录数据
    ShowMessageFmt('ID: %d, Name: %s', [Rec.ID, Rec.Name]);
  end;
end;

关键注意事项

  • 加载TreeView时必须用BeginUpdate和EndUpdate包裹操作,避免频繁刷新UI导致卡顿。
  • 若TMyRec包含动态内容(如字符串、动态数组),不能直接用WriteBuffer序列化整个记录,需单独处理动态字段。
  • 若使用指针关联记录,需在TreeView节点销毁时释放内存(如在TreeView1Deletion事件中调用FreeMem(Node.Data)),避免内存泄漏。

内容的提问来源于stack exchange,提问作者Zink

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 04:57:01