How to fix TTreeView error when using TreeNode.MoveTo?

Using the TreeNode.MoveTo (...) method sometimes does not work as expected and throws an Access Violation exception.

most often works, but not for a while.

Mosttimes access violation in COMCTL32.DLL module. Reading the FEEEFEFA address and crashing / freezing the program.

here is my code.

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;

      

add a project to a project, add a tree view of the dragmode dmAutomatic view tree and multiselect true.

and then

select 3 consecutive nodes with the following order with ctrl. select the middle node, select the bottom node, select the top node. and drag the nodes first node to a different location where you can see the AV error.

or select three node from top to bottom and drag from below node AV.

or select three nodes in the following order using the control key: - first "child 1" then "child 2" then "child 0" finally drag the node choosing "Child 0"

+3


source to share


2 answers


One glaring problem is that when called, MoveTo

you invalidate the loop for

.

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

      

Once called, MoveTo

you will find that it is SelectionCount

no longer what it was when you entered the loop. For example, I'm looking here for a case where SelectionCount

there is 3

when the loop starts but 1

after the first call MoveTo

. This means that using a subsequence is Selections[I]

out of scope.

You need to solve the problem by first taking note of the selected nodes and then moving them.

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;

      


Apart from this problem, I can reproduce the access violation. It seems that items need to be moved in a specific order. It seems that you need to move the bottom node first, then the next previous node, and the top node last. This code seems to work around this issue:

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;

      



It's not very satisfying though, and it's clear that I haven't figured out what's going on here yet.

I can't peek into this right now, but I'll leave an answer here as I think it will help other responders go deeper. Hopefully someone can explain what's going on with AV.


Okay, I went deep. The problem seems to be related to the code inside MoveTo

that is trying to save the selection state of the node being moved. I haven't figured out what the problem is yet, but it seems to me that you can't do much from the outside to avoid the problem without taking on the implementation of the preservation of choice.

Accordingly, I suggest the following workaround as the best I have come up with:

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;

      

Here we do the following:

  • Write down the selected nodes, the nodes to be moved.
  • Deselect the selection.
  • Move each node to its new destination, and after moving, add the moved node to the selection.
+3


source


This is the first random error generated by the shared controls library that you don't need to act on. It could be an error or a deliberate exception, in any case there is nothing to do, the exception is handled by the library itself.

The Delphi debugger may have a problem handling the exception. With my XE2 test, when I select "continue" in the "debugger exception notification", I expect the program to continue as usual. However, the exception dialog aborts the program execution. However, there are no problems running outside the debugger, you will not see any dialog box that interrupts the move operation.



Note that this only applies to one of the duplicate steps you described (the last one). With others, there is an out of bounds list exception thrown by RTL caused by your code that you need to fix.

+2


source







All Articles