4

Using TreeNode.MoveTo(...) method sometimes does not work properly and raises an "Access Violation" exception.

mosttimes works and some time not.

Mosttimes 'Access Violation in module COMCTL32.DLL. Read of address FEEEFEFA' and program crash/freeze.

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 from to a project, add a tree view set tree view dragmode dmAutomatic and multiselect true.

and then

select 3 consecutive node with following order with ctrl. select middle node, select bottom node, select top node. and drag the nodes by first node to another place you can see the AV error.

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

or select three node in following order with control key :- first 'child 1' then 'child 2' then 'child 0' finally drag and drop node by selecting 'Child 0'

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
Azad
  • 5,144
  • 4
  • 28
  • 56
  • The bug is probably in your code. How do we reproduce the fault? Which version of Delphi? – David Heffernan Sep 11 '14 at 06:04
  • delphi XE5. for reproduce it, most times it works fine but in some times it raise an AV error. – Azad Sep 11 '14 at 06:07
  • OK, I fixed the tags. How are we going to find the bug in your code without your code? – David Heffernan Sep 11 '14 at 06:07
  • i added code in my question. – Azad Sep 11 '14 at 06:10
  • 1
    I doubt that code is enough to trigger the problem. The way you've written the question suggest you believe there's a bug in the tree view vcl code. No doubt bugs exist but I doubt MoveTo is broken. More likely the problem is in the code we cannot see. Anyway, what does the debugger tell you? What is the stack trace when the error is raised. That's step 1 of investigating. – David Heffernan Sep 11 '14 at 06:14
  • exception message "access violation at address 73BC in module 'comctl32.dll'. Read of address FEEEFEFA" – Azad Sep 11 '14 at 06:26
  • 1
    That address isn't in comctl32. So that's not what it really says. What is the call stack? – David Heffernan Sep 11 '14 at 06:32
  • 77BF1423 C6058873C17700 mov byte ptr [$77c17388],$00 – Azad Sep 11 '14 at 06:40
  • 1
    Please show the call stack. Do you know what a call stack is? If not, I can explain? – David Heffernan Sep 11 '14 at 06:40
  • I Edited my code and explain under how to produce the error please see – Azad Sep 11 '14 at 07:29

3 Answers3

5

One clear problem is that when you call MoveTo, you invalidate the for loop.

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

After the call to MoveTo, you will find that SelectionCount is no longer what is was when you entered the loop. For instance, I'm looking at a case here where SelectionCount is 3 when the loop begins, but is 1 after the first call to MoveTo. That means that the subsequence use of Selections[I] are out of bounds.

You'll need to solve the problem by making a note of the selected nodes first, 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;

Beyond that problem, I can reproduce the access violation. It seems that the items needs to be moved in a very particular order. It seems that you need to move the bottom node first, then the next preceding node, and the top node last. This code seems to workaround that problem:

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 satisfactory though, and it's clear that I've not yet understood what's going on here.

I cannot look into this any further right now, but I'll leave the answer here as I think it will help other answerers dig deeper. Hopefully somebody will be able to explain what's going on with the AV.


OK, I've dug a bit deeper. The problem appears to be related to the code inside MoveTo that tries to maintain the selection state of the node being moved. I've not yet dug into what the problem is, but it seems to me that you won't be able to do much from the outside to avoid the problem, beyond taking over implementation of selection preserving.

Accordingly I propose the following workaround as the best that I have come up with yet:

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:

  1. Make a note of the selected nodes, the nodes that need to move.
  2. Clear the selection.
  3. Move each node to its new destination, and once moved, add that moved node to the selection.
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • still has AV error please select and move node as i mentioned order – Azad Sep 11 '14 at 08:23
  • I don't see an AV in my example, starting from the code in the question. I suspect that you are running a different program from me. Are you running the simple vanilla app with just the code in the Q, and nothing more? – David Heffernan Sep 11 '14 at 08:24
  • i run a simple form in my XE5. select three node in following order with control key :- first 'child 1' then 'child 2' then 'child 0' finally drag and drop node by selecting 'Child 0' – Azad Sep 11 '14 at 08:29
  • OK, I can see that now. Let me think about that. – David Heffernan Sep 11 '14 at 08:32
  • Btw, it would really help if your previous comment appeared clearly in the question. So there was no doubt as to how to repro. Also, very well done for responding to my questions and making your question better, I'm really impressed. – David Heffernan Sep 11 '14 at 08:50
  • thanks and Btw is there any solution to over come this problem – Azad Sep 11 '14 at 08:52
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/61047/discussion-between-azzi-and-david-heffernan). – Azad Sep 11 '14 at 11:00
  • Looks like an exception that's handled fine by the common controls library to me. Can you reproduce the AV outside the debugger? – Sertac Akyuz Sep 11 '14 at 20:52
  • @Sertac I've not tried. I will do. Likely tomorrow now. – David Heffernan Sep 11 '14 at 20:58
  • the last code overcome this issue thanks @DavidHeffernan – Azad Sep 12 '14 at 03:43
3

This is a first chance exception thrown by the common controls library that you do not need to act upon. It can be a bug or a deliberate exception, in either case there's nothing to pursue, the exception is handled fine by the library itself.

The Delphi debugger may have a problem with handling the exception though. With my XE2 test, when I choose "continue" in the "debugger exception notification", I'd expect the program to continue running as normal. However, an exception dialog interrupts program execution. There's no problem running outside the debugger though, you will not see any kind of dialog that interrupts the move operation.

Note that this is only relevant with one of the duplication steps you present (the last one). With others, there's a 'list out of bounds" exception thrown by the RTL which is caused by your code, which you need to correct.

Sertac Akyuz
  • 54,131
  • 4
  • 102
  • 169
1

I fixed error when drag&drop several nodes with pressed and hold Shift for Delphi 10.4.2

// 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);