Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Delphi treeview drag and drop between nodes

I want to allow the user to arrange a treeView's nodes as he likes, but I am running into a lot of problems. Like for example:

  • After I do a few drag and drops, the treeview stops responding and I have to kill the app. Also if I start dragging and give up (press cancel, or drop it back from where i Picked it) the application hangs... and I can't do anything
  • If a user wants to re-arange the order of 3 nodes that are child of the same main node, he can't do it like in other applications. Why? Isn't there any method to allow the user to drop his node in BETWEEN 2 other nodes? Delphi seems to allow the user to drop a nod only on top of another.

That's not good. I want a behavior like in this picture

enter image description here

So the destination of the Node3, should be between Node1 and Node2. Of course the drop ON TOP of a node should be available, but this BETWEEN NODES functionality should be available too.

My code so far is:

procedure TForm3.FormCreate(Sender: TObject);
begin
  tv.FullExpand;
end;

procedure TForm3.tvDragDrop(Sender, Source: TObject; X, Y: Integer);
var
  TargetNode, SourceNode : TTreeNode;
begin
  with TV do
  begin
    TargetNode := GetNodeAt(X,Y); // Get target node
    SourceNode := Selected;
    if (TargetNode = nil) then
    begin
      EndDrag(False);
      Exit;
    end;
    MoveNode(TargetNode,SourceNode);
    SourceNode.Free;
  end;
end;

procedure TForm3.tvDragOver(Sender, Source: TObject; X, Y: Integer;
  State: TDragState; var Accept: Boolean);
begin
  if (Sender = TV) then // If TRUE than accept the draged item
  begin
    Accept := True;
  end;
end;
procedure TForm3.tvEndDrag(Sender, Target: TObject; X, Y: Integer);
begin
  TV.Repaint;
end;

procedure TForm3.tvMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  tv.BeginDrag(false,-1);
end;

Procedure TForm3.MoveNode(TargetNode, SourceNode : TTreeNode);
var
  nodeTmp : TTreeNode;
  i : Integer;
begin
  with TV do
  begin
    nodeTmp := Items.AddChild(TargetNode,SourceNode.Text);
    for i := 0 to SourceNode.Count -1 do
    begin
      MoveNode(nodeTmp,SourceNode.Item[i]);
    end;
  end;
end;

where of course TV is a TTreeView.

like image 301
user1137313 Avatar asked Dec 11 '22 11:12

user1137313


1 Answers

First of all, throw away all the code that you currently have. It's got too many flaws to make it worthwhile discussing.

The minimum code needed for this is as follows:

procedure TMyForm.TreeView1DragDrop(Sender, Source: TObject; X, Y: Integer);
var
  Src, Dst: TTreeNode;
begin
  Src := TreeView1.Selected;
  Dst := TreeView1.GetNodeAt(X,Y);
  Src.MoveTo(Dst, naAdd);
end;

procedure TMyForm.TreeView1DragOver(Sender, Source: TObject; X, Y: Integer;
  State: TDragState; var Accept: Boolean);
var
  Src, Dst: TTreeNode;
begin
  Src := TreeView1.Selected;
  Dst := TreeView1.GetNodeAt(X,Y);
  Accept := Assigned(Dst) and (Src<>Dst);
end;

You need to set DragMode to dmAutomatic for the tree view. And you also need to hook up the two events to OnDragDrop and OnDragOver.

If you wish to implement different logic in the drop to handle adding as a sibling (before or after), or adding as a child, pass a different value for the second parameter of MoveTo.

The possibilities are listed here:

type
  TNodeAttachMode = (naAdd, naAddFirst, naAddChild, naAddChildFirst, naInsert);

These options are described in the documentation.

like image 54
David Heffernan Avatar answered Dec 24 '22 01:12

David Heffernan