Drag and Drop sample

From Lazarus wiki
Jump to navigationJump to search

Drag and Drop is a common operation, that makes the interface user friendly, since a user don't need to push a lot of controls, but make an operation(s) in a single mouse move.

The following sample explains basics of the drag and drop. For the detailed information you should refer to other articles in the wiki and reference documentation.

Please note, since LCL, is partially compatible with VCL, some articles/examples about Delphi drag-and-drop may also apply to LCL.

Drag and Drop

Despite of the operation's simplicity from user's point of view, it might give hard-times to a inexperienced developer.

For the code, Drag and Drop operation always consists of the 3 steps (or more):

  1. Some control starts drag-and-drop operation. It's to be known as the Source
  2. User drags the mouse cursor around, above other controls or the Source itself. It's time, when a dragged over control needs to decide, if its able to accept the dragged data.
  3. Drop happens if a control agrees to accept the dragged data. The accepting control can be known as the Sender.

To simplify drag-and-drop handling, LCL provides “automatic” mode. It doesn't mean, that LCL does the whole drag-and-drop for you, but it will handle low-level drag-object managing (that's not to be discussed in this article).

Example

DnDTest.PNG

The example covers automatic drag-and-drop feature between 2 controls (Edit->Treeview) as well as inside a single control (Treeview->Treeview)

  • Start the new application.
  • Put a TreeView component and Edit on the form.
  • Enabled Automatic drag-and-drop mode for TreeView and Edit in Object Inspector:

DragMode: dkAutomatic

You can launch the application now, and try to drag anything around. You'll should not get anything working for now. But, if you press left mouse button on the Treeview, you'll probably see the cursor icon changed, but still nothing happening on releasing the mouse .


Dragging between controls

Let's make a drag-and-drop operation between Edit and TreeView. There the content of Edit will be “dragged” to TreeView and a new tree node created.

To initiate the drag, controls has a special method: BeginDrag() Create OnMouseDown event for the Edit

procedure TForm1.Edit1MouseDown(Sender: TObject; Button: TMouseButton; 
  Shift: TShiftState; X, Y: Integer);
begin
  if Button = mbLeft then   {check if left mouse button was pressed}
    Edit1.BeginDrag(true);  {starting the drag operation}
end;

If you launch the application right now, and try drag and drop, you'll notice, that Edit starts the operation, but still nothing happens then you're trying to drop to TreeView.

It's because TreeView doesn't accept the data. None of the controls accept a data, by default, so you'll always need to provide the proper event handler.

Assign TreeView.OnDragOver operation:

procedure TForm1.TreeView1DragOver(Sender, Source: TObject; X, Y: Integer; 
  State: TDragState; var Accept: Boolean);
begin
  Accept := true;
end;

Of course in some cases, TreeView might deny droping (if Source or data is cannot be handled), but for now, TreeView always accepts the dragging.

Run application and test. Everything should be better know, though still nothing happens on drop.

Assign TreeView.OnDragDrop operation:

procedure TForm1.TreeView1DragDrop(Sender, Source: TObject; X, Y: Integer);
var
  tv     : TTreeView; 
  iNode  : TTreeNode;  
begin
  tv := TTreeView(Sender);      { Sender is TreeView where the data is being dropped  }
  iNode := tv.GetNodeAt(x,y);   { x,y are drop coordinates (relative to the Sender)   }   
                                {   sinse Sender is TreeView we can evaluate          }
                                {   a tree at the X,Y coordinates                     } 
                                
  { TreeView can also be a Source! So we must make sure }                                
  { that Source is TEdit, before getting its text }      
  if Source is TEdit then       
    tv.Items.AddChild(iNode, TEdit(Source).Text); {now, we can add a new node, with a text from Source }
end;

Run and test. It should be working now. Dragging a text from Edit to TreeView should create a new node.

Dragging within a controls

Sender and Source can be the same control! It's not prohibited in any way. Let's add an ability to a TextView, to change its nodes location. Since TextView is in automatic DragMode, you don't need to start the drag by using DragBegin() method. It's started automaticaly on mouse moved with left button hold.

Make sure you have “Accept:=true;” inside DragOver operation for TreeView source.

Modify DragDrop event, to the following:

procedure TForm1.TreeView1DragDrop(Sender, Source: TObject; X, Y: Integer);
var
  tv     : TTreeView; 
  iNode  : TTreeNode;  
begin
  tv := TTreeView(Sender);      { Sender is TreeView where the data is being dropped  }
  iNode := tv.GetNodeAt(x,y);   { x,y are drop coordinates (relative to the Sender)   }   
                                {   sinse Sender is TreeView we can evaluate          }
                                {   a tree at the X,Y coordinates                     } 
                                
  { TreeView can also be a Source! So we must make sure }                                
  { that Source is TEdit, before getting its text }      
  if Source is TEdit then       
    tv.Items.AddChild(iNode, TEdit(Source).Text) {now, we can add a new node, with a text from Source }
    
  else if Source = Sender then begin         { drop is happening within a TreeView   }
    if Assigned(tv.Selected) and             {  check if any node has been selected  }
      (iNode <> tv.Selected) then            {   and we're droping to another node   }
      tv.Selected.MoveTo(iNode, naAddChild); { complete the drop operation, by moving the selectede node}
  end;
end;

That's it. If you run the application now, you should have both features working.

  • Adding a new nodes by dragging text from Edit to TreeView
  • Dragging nodes inside the treeview

Hints

  • You can use some global data to inspect what is being dragged now. Don't use global variables, but your form class's fields.
    • Put the data when you start the drag
    • Inspect the data, during control's drag over event, and modify Accept flag accordinglly
    • Read and use the data on drop event