【问题标题】:how to fix TTreeView bug when using TreeNode.MoveTo?使用 TreeNode.MoveTo 时如何修复 TTreeView 错误?
【发布时间】:2014-09-11 05:34:45
【问题描述】:

使用 TreeNode.MoveTo(...) 方法有时无法正常工作并引发“访问冲突”异常。

大多数时候有效,有些时候无效。

大多数时候'模块 COMCTL32.DLL 中的访问冲突。读取地址 FEEEFEFA' 和程序崩溃/冻结。

这是我的代码。

procedure TForm1.FormShow(Sender: TObject);
var
  I, sectioncount: Integer;
  parent, child: TTreeNode;
begin
  sectioncount := 0;
  for I := 0 to 19 do
  begin

    if I mod 5 = 0 then
    begin
      parent := TreeView1.Items.AddChild(nil, 'Section: ' + IntToStr(sectioncount));
      Inc(sectioncount);
    end;

    child := TreeView1.Items.AddChild(parent, 'Child: ' + IntToStr(I));

  end;

  TreeView1.Items[0].Expand(True);
end;

procedure TForm1.TreeView1DragDrop(Sender, Source: TObject; X, Y: Integer);
var src, dst : TTreeNode;
I : Integer;
begin
   dst := TreeView1.DropTarget;

   for I := 0 to TreeView1.SelectionCount - 1 do
   begin
     src := TreeView1.Selections[I];

     src.MoveTo(dst,naInsert);
   end;

end;

procedure TForm1.TreeView1DragOver(Sender, Source: TObject; X, Y: Integer;
  State: TDragState; var Accept: Boolean);
begin
Accept := true;
end;

添加 from 到项目,添加树视图 set tree view dragmode dmAutomatic 和 multiselect true。

然后

使用 ctrl 选择以下顺序的 3 个连续节点。 选择中间节点, 选择底部节点, 选择顶部节点。 并将节点按第一个节点拖动到另一个可以看到 AV 错误的地方。

或从上到下选择三个节点并从底部节点拖动AV出现。

或使用控制键按以下顺序选择三个节点:-首先是“child 1”,然后是“child 2”,然后是“child 0”,最后通过选择“Child 0”拖放节点

【问题讨论】:

  • 错误可能在您的代码中。我们如何重现故障?哪个版本的 Delphi?
  • 德尔福 XE5。为了重现它,大多数时候它可以正常工作,但有时它会引发 AV 错误。
  • 我怀疑代码是否足以触发问题。您编写问题的方式表明您认为树视图 vcl 代码中存在错误。毫无疑问存在错误,但我怀疑 MoveTo 已损坏。更有可能问题出在我们看不到的代码中。无论如何,调试器告诉你什么?引发错误时的堆栈跟踪是什么。这是调查的第一步。
  • 该地址不在 comctl32 中。所以这不是它真正所说的。什么是调用堆栈?
  • 请显示调用堆栈。你知道什么是调用栈吗?如果不是,我可以解释一下吗?

标签: delphi delphi-xe5


【解决方案1】:

一个明显的问题是,当您调用MoveTo 时,会使for 循环无效。

 for I := 0 to TreeView1.SelectionCount - 1 do
 begin
   src := TreeView1.Selections[I];
   src.MoveTo(dst,naInsert);
 end;

在调用MoveTo 之后,你会发现SelectionCount 不再是你进入循环时的样子了。例如,我在这里看到一个例子,当循环开始时SelectionCount3,但在第一次调用MoveTo 之后是1。这意味着Selections[I] 的子序列使用超出了范围。

您需要先记下所选节点,然后移动它们来解决问题。

procedure TForm1.TreeView1DragDrop(Sender, Source: TObject; X, Y: Integer);
var
  i: Integer;
  src, dst: TTreeNode;
  nodesToMove: TArray<TTreeNode>;
begin
  dst := TreeView1.DropTarget;
  SetLength(nodesToMove, TreeView1.SelectionCount);
  for i := 0 to high(nodesToMove) do
    nodesToMove[i] := TreeView1.Selections[i];
  for src in nodesToMove do
    src.MoveTo(dst, naInsert);
end;

除了这个问题,我可以重现访问冲突。似乎需要以非常特殊的顺序移动这些项目。似乎您需要先移动底部节点,然后移动下一个前一个节点,最后移动顶部节点。这段代码似乎可以解决这个问题:

procedure TForm1.TreeView1DragDrop(Sender, Source: TObject; X, Y: Integer);
var
  i: Integer;
  src, dst: TTreeNode;
  nodesToMove: TList<TTreeNode>;
begin
  dst := TreeView1.DropTarget;
  nodesToMove := TList<TTreeNode>.Create;
  try
    for i := TreeView1.Items.Count-1 downto 0 do
      if TreeView1.Items[i].Selected then
        nodesToMove.Add(TreeView1.Items[i]);
    for src in nodesToMove do
      src.MoveTo(dst, naInsert);
  finally
    nodesToMove.Free;
  end;
end;

虽然不是很令人满意,很明显我还没有理解这里发生了什么。

我现在无法进一步研究这个问题,但我会在这里留下答案,因为我认为这将有助于其他回答者更深入地挖掘。希望有人能够解释 AV 发生了什么。


好的,我已经挖得更深了。该问题似乎与MoveTo 中的代码有关,该代码试图保持被移动节点的选择状态。我还没有深入研究问题是什么,但在我看来,除了接管选择保留的实施之外,您似乎无法从外部做很多事情来避免这个问题。

因此,我提出了以下解决方法,这是我想出的最好的解决方法:

procedure TForm1.TreeView1DragDrop(Sender, Source: TObject; X, Y: Integer);
var
  i: Integer;
  src, dst: TTreeNode;
  nodesToMove: TArray<TTreeNode>;
begin
  dst := TreeView1.DropTarget;
  SetLength(nodesToMove, TreeView1.SelectionCount);
  for i := 0 to high(nodesToMove) do
    nodesToMove[i] := TreeView1.Selections[i];
  TreeView1.ClearSelection;
  for src in nodesToMove do
  begin
    src.MoveTo(dst, naInsert);
    TreeView1.Select(src, [ssCtrl]);
  end;
end;

在这里我们执行以下操作:

  1. 记下选定的节点,需要移动的节点。
  2. 清除选择。
  3. 将每个节点移动到新的目的地,移动后,将移动的节点添加到选择中。

【讨论】:

  • 仍然有 AV 错误请按照我提到的顺序选择并移动节点
  • 我的示例中没有看到 AV,从问题中的代码开始。我怀疑您正在运行与我不同的程序。您是否仅使用 Q 中的代码运行简单的 vanilla 应用程序,仅此而已?
  • 我在我的 XE5 中运行了一个简单的表单。使用控制键按以下顺序选择三个节点:-首先是“child 1”,然后是“child 2”,然后是“child 0”,最后通过选择“Child 0”拖放节点
  • 好的,我现在可以看到了。让我考虑一下。
  • 顺便说一句,如果您之前的评论清楚地出现在问题中,那将真的很有帮助。所以毫无疑问如何复制。另外,非常好地回答了我的问题并使您的问题变得更好,我真的很感动。
【解决方案2】:

这是由公共控件库引发的第一次机会异常,您不需要对其进行操作。它可能是一个错误或故意的异常,在任何一种情况下都没有什么可追求的,异常由库本身处理得很好。

Delphi 调试器在处理异常时可能有问题。在我的 XE2 测试中,当我在“调试器异常通知”中选择“继续”时,我希望程序能够继续正常运行。但是,异常对话框会中断程序执行。但是在调试器之外运行没有问题,您不会看到任何中断移动操作的对话框。

请注意,这仅与您提供的复制步骤之一(最后一个)相关。对于其他人,RTL 引发了一个“列表越界”异常,这是由您的代码引起的,您需要更正。

【讨论】:

  • +1 我忘了检查这个,但无论如何你写一个单独的答案是对的
【解决方案3】:

我修复了在 Delphi 10.4.2 中按住 Shift 拖放多个节点时出现的错误

// get all selected nodes 
SrcTreeView.GetSelections(SelectedNodesList); 

 SrcTreeView.ClearSelection(True);
// if you drag&drop several nodes with pressed keyboard Shift Node.MoveTo() will raise exception because
// when you moves nodes other several nodes can be autoselected
 SrcTreeView.MultiSelect := False; // it will fix error
 
// work with selected nodes
for var CurNode: TTreeNode in SelectedNodesList do
  CurNode.MoveTo(Parent, TNodeAttachMode.naAddChild);
 

SrcTreeView.MultiSelect := True; // restore
SrcTreeView.Select(SelectedNodesList);

【讨论】:

    猜你喜欢
    • 2021-05-29
    • 2022-12-13
    • 2021-12-31
    • 2021-11-17
    • 2018-07-07
    • 2019-09-21
    • 2020-10-03
    • 2021-08-08
    • 1970-01-01
    相关资源
    最近更新 更多