Skip to main content

Implementing Drag-And-Drop Functionality

  • 6 minutes to read

Drag-and-drop allows end-users to drag one control (or its items) to another within the same application or rearrange items within the same control. There are four different situations when you may need to implement the drag-and-drop feature within the TreeList control:

  • Rearranging nodes within the TreeList control by changing their parent nodes.

  • Drag and drop from a VCL control onto a TreeList control.

  • Drag and drop from a TreeList control onto a VCL control.

  • Drag and drop between two TreeList controls.

In this topic we will discuss all four types of drag-and-drop.

Drag and drop within a TreeList control

This is the easiest case, because it can be implemented automatically by setting the DragMode property of the TreeList control to dmAutomatic in the Object Inspector:

Additionally, at the very least you will have to provide an empty implementation of the OnDragOver event to enable drag-and-drop within the TreeList control.

If you are working with a data-aware TreeList control, it changes parent field values during drag-and-drop operations automatically. Set the OptionsData.AutoCalcKeyValue property to True to enable automatic changing of the node’s key field values.

On starting an application, you will be able to drag-and-drop nodes within the TreeList control.

Drag and drop from a VCL control onto a TreeList control

Let’s create a small application with one form containing two controls: TreeList and Listbox. An end-user will be able to drag items from the Listbox onto the TreeList.

  1. Drop TcxTreeList and TListBox controls on a form.

  2. Create one column in the TreeList control and set the OptionsView.ColumnAutoWidth property to True.

  3. Add 5 items to the ListBox control.

  4. Change ListBox properties: DragMode to dmAutomatic and MultiSelect to True.

  1. Write the following code for the OnDragOver event of the TreeList control to show that the TreeList can accept items from the ListBox control.
procedure TForm1.cxTreeList1DragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState; var Accept: Boolean);
begin
  Accept := Source = ListBox1;
end;
  1. Write the following code for the TreeList control’s OnDragDrop event to process the final part of the drag-and-drop operation.
procedure TForm1.cxTreeList1DragDrop(Sender, Source: TObject; X, Y: Integer);
var
  AParentNode, ANode: TcxTreeListNode;
  I: Integer;
begin
  if Source <> ListBox1 then exit;
  // If the cursor was under a particular node, add items as children to that node.
  AParentNode := cxTreeList1.HitTest.HitNode;
  for I := 0 to ListBox1.Items.Count - 1 do
    if ListBox1.Selected[I] then
    begin
// add a new node
      ANode := cxTreeList1.AddChild(AParentNode);
      // set node value
      ANode.Values[0] := ListBox1.Items[I];
    end;
end;

The following image shows the application while dragging and after dropping:

As you can see, the dragged list box items (Item3 and Item4) are added to the TreeList control as Item1 children.

Drag and drop from a TreeList control onto a VCL control

Let’s use the TreeList and Listbox controls again.

  1. Drop TcxTreeList and TListBox controls on a form.

  2. Create one column in the TreeList control and set the OptionsView.ColumnAutoWidth property to True.

  3. Invoke the TreeList control’s Items Editor and add several nodes.

  1. Disable the TreeList control’s OptionsBehavior.ImmediateEditor and OptionsBehavior.AlwaysShowEditor properties and set DragMode to dmAutomatic.

  2. Write the following code for the Listbox control’s OnDragOver and OnDragDrop events.

procedure TForm1.ListBox1DragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState; var Accept: Boolean);
begin
  Accept := Source = cxTreeList1;  // accept drag-and-drop for the TreeList control only
end;
procedure TForm1.ListBox1DragDrop(Sender, Source: TObject; X, Y: Integer);
var
  I: Integer;
begin
  // exit if the drag-and-drop operation was initialized from a control different from cxTreeList1
  if Source <> cxTreeList1 then exit;
  // loop through selected nodes and add them to the ListBox1 control
  for I := 0 to cxTreeList1.SelectionCount - 1 do
    ListBox1.Items.Add(cxTreeList1.Selections[I].Values[0]);
end;

The following image shows the application while dragging:

And the following image shows the TreeList control after dropping:

Drag and drop between two TreeList controls

Dragging one TreeList control to another has one major difference from the examples above: the tree structure must be recreated on the target TreeList control.

  1. Drop a TcxTreeList control on a form.

  2. Create a column in the TreeList control and set the OptionsView.ColumnAutoWidth property to True.

  3. Invoke the Items Editor and add several nodes.

  4. Disable the OptionsBehavior.ImmediateEditor and OptionsBehavior.AlwaysShowEditor properties and set DragMode to dmAutomatic.

  5. Copy the TreeList control to the clipboard and paste it into the same form, so that we have two controls with the same settings.

  1. Write the following code for the OnDragOver and OnDragDrop events of the TreeList controls. Note that we use the same code for both controls.
procedure TForm1.cxTreeList1DragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState; var Accept: Boolean);
begin
  // accept drag-and-drop from the other TreeList control only
  Accept := (Source is TcxTreeList) and (Sender <> Source);
end;
// Since we want to store the tree structure in the TreeList control
// the method looks pretty complex
procedure TForm1.cxTreeList1DragDrop(Sender, Source: TObject; X, Y: Integer);
var
  ASourceTreeList, ADestTreeList: TcxTreeList;
  AParentNode: TcxTreeListNode;
  // get the correct parent node
  // new inserted nodes have their data property set to the source node
  // for the original nodes, the data property equals nil
  function GetParentNode(ANewNode: TcxTreeListNode): TcxTreeListNode;
  var
    I: Integer;
    ANode: TcxTreeListNode;
  begin
    Result := nil;
    // loop through all nodes in the destination TreeList control
    for I := 0 to ADestTreeList.Nodes.Count - 1 do
      // is it a new inserted node?
      if ADestTreeList.Nodes.Items[I].Data <> nil then
      begin
        // get the source node
        ANode := TcxTreeListNode(ADestTreeList.Nodes.Items[I].Data);
        // if the new source node has ANode as parent
        if ANewNode.HasAsParent(ANode) and
          ((Result = nil) or TcxTreeListNode(Result.Data).HasAsParent(ANode)) then
          Result := ADestTreeList.Nodes.Items[I];
      end;
    if Result = nil then
      Result := AParentNode;
  end;
var
  ANode, ASelectedNode: TcxTreeListNode;
  I: Integer;
begin
  if not (Source is TcxTreeList) then exit;
  ASourceTreeList := TcxTreeList(Source);
  ADestTreeList := TcxTreeList(Sender);
  AParentNode := ADestTreeList.HitTest.HitNode;
  // loop through all selected nodes in the source TreeList control
  for I := 0 to ASourceTreeList.SelectionCount - 1 do
  begin
    ASelectedNode := ASourceTreeList.Selections[I];
    // add a new node
    ANode := ADestTreeList.AddChild(GetParentNode(ASelectedNode), ASelectedNode);
    // set the data in cell(s)
    ANode.Values[0] := ASelectedNode.Values[0]
  end;
  // set the data property for all nodes to nil
  for I := 0 to ADestTreeList.Nodes.Count - 1 do
    ADestTreeList.Nodes.Items[I].Data := nil;
end;

The following image shows the resulting application while dragging:

And the following image shows the drag-and-drop result:

Note

You may also find the following TreeList control properties useful when handling drag-and-drop operations: OptionsView.DropNodeIndicator, OptionsBehavior.AutoDragCopy, OptionsBehavior.DragCollapse, and OptionsBehavior.DragExpand.