Drag and drop TAdvTreeViewNode

Thank you for your attention.


How can I make to work drag and drop using TAdvTreeViewNode, not TAdvTreeViewVirtualNode?

I need to find a method similar to XYToNode(X, Y), this method only works for TAdvTreeViewVirtualNode.

I have the following code that works with a different TreeView control:

procedure TTree_AJ.TreeViewDragDrop(Sender, Source: TObject; X, Y: Integer);
var
  TargetNode, SourceNode: TFlyNode;
begin 
  TargetNode := AJ_Tree.GetNodeAt(X, Y);
  if ( TargetNode <> nil ) then begin
     SourceNode := AJ_Tree.Selected;
     SourceNode.MoveTo(TargetNode, naInsert);
     // naAdd    Adds the node to the end of the list.
     // naAddFirst Adds the node at the beginning of the list.
     // naAddChild Adds the node as a child of the destination at the end of the child list.
     // naAddChildFirst Adds the node as a child at the beginning of the child list of the destination.
     // naInsert        Insert the node before the destination node.
  end;
end;

procedure TTree_AJ.TreeViewDragOver(Sender, Source: TObject; X, Y: Integer;
  State: TDragState; var Accept: Boolean);
var
  TargetNode, SourceNode: TFlyNode;
begin 
  TargetNode := AJ_Tree.GetNodeAt(X, Y);
  // Accept dragging from itself
  if ( Source = Sender ) and ( TargetNode <> nil ) then begin
     Accept := True;
     // Determines source and target
     SourceNode := AJ_Tree.Selected;
     // Look up the target parent chain
     while ( TargetNode.Parent <> nil ) and
           ( TargetNode <> SourceNode ) do
       TargetNode := TargetNode.Parent;
     // If source is found
     if ( TargetNode = SourceNode ) then
       // Do not allow dragging over a child
       Accept := False;
  end // if ...
  else
    Accept := False;
end; 

The treeview manages internally virtual nodes TAdvTreeViewVirtualNode as it is virtual by default. If however you use it always with 'real' nodes, you get the real node for every virtual nodes via TAdvTreeViewVirtualNode.Node: TAdvTreeViewNode

Great Bruno, thank you for your help.


So, I changed the code to:

procedure TFrmTest_AdvTreeView_Main.AdvTreeViewDragDrop(Sender, Source: TObject;
  X, Y: Integer);
var
  TargetNode : TAdvTreeViewVirtualNode;
  SourceNode : TAdvTreeViewVirtualNode;
begin 
  TargetNode := AdvTreeView.XYToNode(X, Y);
  if ( TargetNode.Node <> nil ) then begin
     SourceNode := AdvTreeView.SelectedVirtualNode;
     SourceNode.Node.MoveTo(TargetNode.Node, 0);
  end; 
end; 

procedure TFrmTest_AdvTreeView_Main.AdvTreeViewDragOver(Sender, Source: TObject;
  X, Y: Integer; State: TDragState; var Accept: Boolean);
var
  TargetNode : TAdvTreeViewVirtualNode;
  SourceNode : TAdvTreeViewVirtualNode;
begin 
  TargetNode := AdvTreeView.XYToNode(X, Y);
  // Accept dragging from itself
  if ( Source = Sender ) and ( TargetNode <> nil ) then begin
     Accept := True;
     // Determines source and target
     SourceNode := AdvTreeView.SelectedVirtualNode;
     // Look up the target parent chain
     while ( TargetNode.GetParent <> nil ) and
           ( TargetNode <> SourceNode ) do
       TargetNode := TargetNode.GetParent;
     // If source is found
     if ( TargetNode = SourceNode ) then
       // Do not allow dragging over a child
       Accept := False;
  end // if ...
  else
    Accept := False;
end; 

The problem I have now is that I don't know which parameter I have to assign to AIndex in SourceNode.Node.MoveTo(TargetNode.Node,AIndex); if I want the dragged node to be moved before the destination node.

Could you please inform me how to use this AIndex parameter?

Kind regards,

Carlos Borrero


AIndex is the index of the child node under the DestinationNode where it should be inserted.

If you want to move it before the destination node, you would typically use the DestinationNode.ParentNode, DestinationNode.Index - 1

I tried:


SourceNode.Node.MoveTo(TargetNode.Node.GetParent, -1);

And

SourceNode.Node.MoveTo(TargetNode.ParentNode, -1);

but none of them work.

With the first attempt the node is pasted as last child of destination node, not before the destination node.

With the second attempt I get an error message because ParentNode is integer but has to be TAdvTreeViewNode.

Don't know what to do. Could you please help me once again?

Kind regards,

Carlos Borrero

Also tried:


SourceNode.Node.MoveTo(TargetNode.Node, TargetNode.Index - 1);

And

SourceNode.Node.MoveTo(TargetNode.Node.GetParent, TargetNode.Index - 1);

None of the attempts work.

I had to modify the following code on unit AdvTreeViedData.pas:


From:

procedure TAdvTreeViewData.MoveNode(ANode,
  ADestinationNode: TAdvTreeViewNode; AIndex: Integer);
var
  n, nw: TAdvTreeViewNode;
begin
  if ADestinationNode <> ANode then
  begin
    BeginUpdate;
    n := TAdvTreeViewNode.Create(nil);
    n.Assign(ANode);

    if Assigned(ADestinationNode) then
      nw := ADestinationNode.Nodes.Add
    else
      nw := Nodes.Add;

    nw.Assign(n);
    n.Free;
    RemoveNode(ANode);

    if Assigned(ADestinationNode) then
    begin
      if (AIndex >= 0) and (AIndex <= ADestinationNode.Nodes.Count - 1) then
        nw.Index := AIndex;
    end
    else
    begin
      if (AIndex >= 0) and (AIndex <= Nodes.Count - 1) then
        nw.Index := AIndex;
    end;

    EndUpdate;
  end;
end;

To:

procedure TAdvTreeViewData.MoveNode(ANode,
  ADestinationNode: TAdvTreeViewNode; AIndex: Integer);
var
  n, nw, np: TAdvTreeViewNode;
begin
  if ADestinationNode <> ANode then
  begin
    BeginUpdate;
    n := TAdvTreeViewNode.Create(nil);
    n.Assign(ANode);

    if ( AIndex <> -1 ) then begin
       if Assigned(ADestinationNode) then
         nw := ADestinationNode.Nodes.Add
       else
         nw := Nodes.Add;
    end // if ...
    else begin
       np := ADestinationNode.GetParent;
       if Assigned(np) then
         nw := ADestinationNode.GetParent.Nodes.Insert(ADestinationNode.Index)
       else
         nw := Nodes.Insert(ADestinationNode.Index);
    end; // else ...

    nw.Assign(n);
    n.Free;
    RemoveNode(ANode);

    if Assigned(ADestinationNode) then
    begin
      if (AIndex >= 0) and (AIndex <= ADestinationNode.Nodes.Count - 1) then
        nw.Index := AIndex;
    end
    else
    begin
      if (AIndex >= 0) and (AIndex <= Nodes.Count - 1) then
        nw.Index := AIndex;
    end;

    EndUpdate;
  end;
end;

And I use the following line to invoke it on AdvTreeViewDragDrop:

SourceNode.Node.MoveTo(TargetNode.Node, -1);

The problem I have know is that instructions: 

    nw.Assign(n);
    n.Free;

Take too much time (several seconds, sometimes more that a minute) when ANode includes a lot of child nodes.

I think you must improve this performance, with the treeview control I had before (RapidTreeView, discontinued) the drop action used to be instantaneous.

Could you please advise me if there is a way to make those instructions work faster?

Am I authorized to use your product with my fix?

Is it possible to have a new version that includes the fix I propose?

Kind regards,

Carlos Borrero

We will investigate this deeper and look for performance improvements and will report.
Meanwhile you can of course use the code with your improvements.