【问题标题】:Parent of RootNode of TVirtualStringTreeTVirtualStringTree 的 RootNode 的父级
【发布时间】:2020-09-17 22:22:29
【问题描述】:

根据文档,TVirtualStringTree 的属性 RootNode 是一个隐藏节点,是所有用户创建的节点的父节点。但是设置了 RootNode 的 Parent。我面临以下问题:

有时,当关闭其中包含 TVirtualStringTree 的表单时,我会收到访问冲突错误。调试,尝试访问组件(虚拟字符串树)的Name属性时发生错误。如果我尝试评估 Name 属性,我会得到“无法访问的值”。深挖问题,发现一个组件的Name属性cannot be changed in runtime

警告:在运行时更改名称会导致对旧名称的任何引用 名称变得未定义。任何使用旧名称的后续代码 会引发异常。

碰巧我没有在我的代码中更改它。使用 Name 属性中的数据断点,我看到它在 virtualstringtree 循环中发生变化,类似这样(我在示例应用程序中使用的示例代码来测试问题,但生产代码类似):

var
  N: PNode;
  P: PVirtualNode;
begin
  P := tree.FocusedNode;
  while Assigned(P) do
  begin
    N := tree.GetNodeData(P);
    P := P.Parent;
  end;

GetNodeData 如下:

 if (FNodeDataSize <= 0) or (Node = nil) or (Node = FRoot) then
    Result := nil
  else begin
    Result := PByte(@Node.Data) + FTotalInternalDataSize;
    Include(Node.States, vsOnFreeNodeCallRequired); // We now need to call OnFreeNode, see bug #323
  end;

假设我有以下树:

Node 1
  Node 2
    Node 3

When the Node 3 is selected, the loop is executed 5 times. 3 在我创建的节点中,然后是 RootNode(隐藏),然后是根的 Parent。只有父根节点的Parent 为nil。在GetNodeData方法中验证是否是根节点,返回nil。但是对于根的这个父节点,因为它不是根,所以输入 else 代码。现在问题来了:

virtualstrintree 的 Name 属性的地址是 $141B9768

根节点的父节点的States属性地址是$141B976A

在 Name 属性中放置数据断点时,此时会发生变化:

Include(Node.States, vsOnFreeNodeCallRequired);

并生成 AV。

我知道我可以更改循环,以检查根节点correctly,但我想了解这一点,所以我可能会修复组件(如果它是组件错误),而不是我的代码。

代码:

type
  TNode = record
    Text: string;
  end;
  PNode = ^TNode;
...
procedure TForm1.FormCreate(Sender: TObject);
var
  N1, N2, N3: TNode;
  TreeNode: PVirtualNode;
begin
  N1.Text := 'Node 1';
  TreeNode := tree.AddChild(nil, PNode(N1));

  N2.Text := 'Node 2';
  TreeNode := tree.AddChild(TreeNode, PNode(N2));

  N3.Text := 'Node 3';
  TreeNode := tree.AddChild(TreeNode, PNode(N3));
end;

在表单中放一个树和一个按钮,我把循环放在按钮的点击中。

【问题讨论】:

  • 你使用的是github.com/JAM-Software/Virtual-TreeView.git/trunk的最新版本吗?
  • 您谈论一个示例应用程序来测试问题。你能在某处提供它的源代码吗?
  • @fpiette 它只是一个带有树的项目,并在 FormCreate 中创建了 3 个节点。不值得让它可用,重要的是循环本身。使用版本 5.3.0
  • 这个想法是我可以复制您的代码来重现您的问题,而无需自己做太多工作。所以是的,值得让它可用。由于它很短 - 正如你所说 - 你可以编辑你的问题并添加该代码。
  • @fpiette 对不起,我编辑了帖子

标签: delphi tvirtualstringtree


【解决方案1】:

首先,文档:(例如) https://documentation.help/VirtualTreeview/TBaseVirtualTree_RootNode.html

说(强调我的)

属性根节点:PVirtualNode;

说明

用于锚定树 层次结构维护内部树节点,这主要是 像任何其他树节点一样,但有时处理方式不同。这 根节点总是展开和初始化。 其父会员积分 到节点所属的树视图及其 PreviousSibling 和 NextSibling 成员指向根节点本身使其 可以实际识别这个节点

注意事项

您不应该使用根节点来遍历树。它 只能公开访问,因为它是所有顶级的父级 节点,可用于测试节点是顶级节点还是 不是。

其次

在您的代码中

var
  N: PNode;
  P: PVirtualNode;
begin
  P := tree.FocusedNode;
  while Assigned(P) do
  begin
    N := tree.GetNodeData(P);
    P := P.Parent;
  end;

您无条件向上遍历内部根节点,因为您没有通过与tree.RootNode 比较(或与P.NextSibling 比较)来检查P 是否为内部根。

也许会改变

while Assigned(P) do

条件

while P <> tree.RootNode do

适合你

【讨论】:

  • 谢谢汤姆。就像我在帖子末尾所说的那样,我知道我的循环是错误的,因为不检查根,但就像我说的那样,即使如此我认为我也可以修改 GetNodeData 方法,因为它检查根节点(返回 nil ),但不是其父级,并更改内存中的不同空间。
  • 所以,我将修复循环,并更改 GetNodeData 以检查它是否不是树本身,以防其他循环存在。谢谢
  • 这是我必须跟踪的最困难的 AV,因为它在调用 Include 时更改了树的 Name 属性,而且非常断断续续。所以我认为添加这个检查是有意义的
  • 感谢@Tom 的帮助
  • 是的,@Rodrigo,我首先误读了您之前的评论,然后回答了,但将我的评论删除为无关紧要。我想这件事现在很清楚了。
【解决方案2】:

因此,感谢 Tom 的回答和组件的文档,RootNode 的父级实际上就是树本身。因此,当以Parent = nil 作为停止点进行循环时,当到达父级的根(树)时,将执行GetNodeData 中的代码Include,在我的情况下,当访问State 属性时,它的地址和树的Name地址很接近,因为“节点”的地址就是树。

所以,我修复了循环以检查 Node &lt;&gt; Tree.RootNode 并修复 GetNodeData 以检查节点是否为自身:or (Node = Pointer(Self)以防万一我的应用程序中的其他代码(200 万以上的遗留代码)代码)使用相同的错误循环

之前:

function TBaseVirtualTree.GetNodeData(Node: PVirtualNode): Pointer;
begin
  Assert(FNodeDataSize > 0, 'NodeDataSize not initialized.');
  if (FNodeDataSize <= 0) or (Node = nil) or (Node = FRoot) then
    Result := nil
  else begin
    Result := PByte(@Node.Data) + FTotalInternalDataSize;
    Include(Node.States, vsOnFreeNodeCallRequired); // We now need to call OnFreeNode, see bug #323
  end;
end;

之后:

function TBaseVirtualTree.GetNodeData(Node: PVirtualNode): Pointer;
begin
  Assert(FNodeDataSize > 0, 'NodeDataSize not initialized.');
  if (FNodeDataSize <= 0) or (Node = nil) or (Node = FRoot) or (Node = Pointer(Self)) then
    Result := nil
  else begin
    Result := PByte(@Node.Data) + FTotalInternalDataSize;
    Include(Node.States, vsOnFreeNodeCallRequired); // We now need to call OnFreeNode, see bug #323
  end;
end;

【讨论】:

  • 我强调了这个修改的原因,以防止将来有人认为它会为他们服务。
猜你喜欢
  • 1970-01-01
  • 2020-10-31
  • 2013-04-27
  • 1970-01-01
  • 2012-04-26
  • 1970-01-01
  • 2017-09-22
  • 2017-02-20
  • 2013-07-07
相关资源
最近更新 更多