how to fix TTreeView bug when using TreeNode.MoveTo?

后端 未结 2 702
攒了一身酷
攒了一身酷 2021-01-06 03:55

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

mosttimes works and some time not.

Mosttime

相关标签:
2条回答
  • 2021-01-06 04:26

    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.

    0 讨论(0)
  • 2021-01-06 04:27

    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.
    0 讨论(0)
提交回复
热议问题