如何在使用TreeNode.MoveTo时修复TTreeView错误?

时间:2014-09-11 05:34:45

标签: delphi delphi-xe5

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

大部分时间都有效,有些时候没有。

大多数情况下'访问模块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到项目,添加树视图集树视图dragmode dmAutomatic和multiselect true。

然后

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

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

或使用控制键按以下顺序选择三个节点: - first' child 1'然后'孩子2'然后'孩子0'最后通过选择' Child 0'

来拖放节点

3 个答案:

答案 0 :(得分:5)

一个明显的问题是,当您致电MoveTo时,您会使for循环失效。

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

致电MoveTo后,您会发现SelectionCount不再是您进入循环时的状态。例如,我在这里查看一个案例,其中SelectionCount在循环开始时为3,但在第一次调用1后为MoveTo。这意味着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. 将每个节点移动到新目标位置,移动后,将该移动的节点添加到选择中。

答案 1 :(得分:3)

这是您不需要采取行动的公共控件库抛出的第一个机会异常。它可能是一个bug或一个故意的例外,在任何一种情况下都没有什么可追求的,例外由图书馆本身处理得很好。

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

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

答案 2 :(得分:1)

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

from sklearn.externals import joblib
joblib.dump(model, 'sentiment_model.pkl')