This tutorial describes the usage of drag and drop in the Eclipse framework. The article assumes that you are familiar with SWT and Eclipse plug-in or Eclipse RCP development.

1. Using drag and drop in SWT

SWT supports drag and drop based on transfer types defined based on the org.eclipse.swt.dnd.Transfer class. Every widget element can define that it provides a certain transfer type as source. It can also define that it accepts a transfer type as destination.

Eclipse provides several pre-defined transfer types. To define your custom transfer type it is recommended to subclass the org.eclipse.swt.dnd.ByteArrayTransfer class.

A drag source is the provider of the drag and drop data as well as the originator of the drag and drop operation. A drop target receives data in a drag and drop operation. The drop target receives the data provided by the drag source. it can be another location in the same widget, a different widget, or a different application. For example, you can drag text from your application and drop it on an email application, or you could drag an item in a tree and drop it below a different node in the same tree.

The following demonstrates how to provide text data from a label.

import org.eclipse.swt.dnd.*;

//Enable a label as a Drag Source
final Label dragLabel = new Label(shell, SWT.BORDER);
dragLabel.setText("text to be transferred");

// Allow data to be copied or moved from the drag source
int operations = DND.DROP_MOVE | DND.DROP_COPY;
DragSource source = new DragSource(dragLabel, operations);

// Provide data in Text format
Transfer[] types = new Transfer[] {TextTransfer.getInstance()};
source.setTransfer(types);

source.addDragListener(new DragSourceListener() {
public void dragStart(DragSourceEvent event) {
   // Only start the drag if there is actually text in the
   // label - this text will be what is dropped on the target.
   if (dragLabel.getText().length() == 0) {
           event.doit = false;
   }
  }
       public void dragSetData(DragSourceEvent event) {
         // Provide the data of the requested type.
         if (TextTransfer.getInstance().isSupportedType(event.dataType)) {
              event.data = dragLabel.getText();
         }
       }
       public void dragFinished(DragSourceEvent event) {
         // If a move operation has been performed, remove the data
         // from the source
         if (event.detail == DND.DROP_MOVE)
             dragLabel.setText("");
         }
       }
});
import org.eclipse.swt.dnd.*;
2
3   // Enable a table as a Drop Target
4   final Table dropTable = new Table(shell, SWT.BORDER);
5   for (int i = 0; i < 10; i++) {
6       TableItem item = new TableItem(dropTable, SWT.NONE);
7       item.setText("item" + I);
8   }
9
10  // Allow data to be copied or moved to the drop target
11  operations = DND.DROP_MOVE | DND.DROP_COPY | DND.DROP_DEFAULT;
12  DropTarget target = new DropTarget(dropTable, operations);
13
14  // Receive data in Text or File format
15  final TextTransfer textTransfer = TextTransfer.getInstance();
16  final FileTransfer fileTransfer = FileTransfer.getInstance();
17  types = new Transfer[] {fileTransfer, textTransfer};
18  target.setTransfer(types);
19
20  target.addDropListener(new DropTargetListener() {
21    public void dragEnter(DropTargetEvent event) {
22       if (event.detail == DND.DROP_DEFAULT) {
23           if ((event.operations & DND.DROP_COPY) != 0) {
24               event.detail = DND.DROP_COPY;
25           } else {
26               event.detail = DND.DROP_NONE;
27           }
28       }
29       // will accept text but prefer to have files dropped
30       for (int i = 0; i < event.dataTypes.length; i++) {
31           if (fileTransfer.isSupportedType(event.dataTypes[i])){
32               event.currentDataType = event.dataTypes[i];
33               // files should only be copied
34               if (event.detail != DND.DROP_COPY) {
35                   event.detail = DND.DROP_NONE;
36               }
37               break;
38           }
39       }
40     }
41     public void dragOver(DropTargetEvent event) {
42          event.feedback = DND.FEEDBACK_SELECT | DND.FEEDBACK_SCROLL;
43          if (textTransfer.isSupportedType(event.currentDataType)) {
44              // NOTE: on unsupported platforms this will return null
45              Object o = textTransfer.nativeToJava(event.currentDataType);
46              String t = (String)o;
47              if (t != null) System.out.println(t);
48          }
50      }
51      public void dragOperationChanged(DropTargetEvent event) {
52          if (event.detail == DND.DROP_DEFAULT) {
53              if ((event.operations & DND.DROP_COPY) != 0) {
54                  event.detail = DND.DROP_COPY;
55              } else {
56                  event.detail = DND.DROP_NONE;
57              }
58          }
59          // allow text to be moved but files should only be copied
60          if (fileTransfer.isSupportedType(event.currentDataType)){
61              if (event.detail != DND.DROP_COPY) {
62                  event.detail = DND.DROP_NONE;
63              }
64          }
65      }
66      public void dragLeave(DropTargetEvent event) {
67      }
68      public void dropAccept(DropTargetEvent event) {
69      }
70      public void drop(DropTargetEvent event) {
71          if (textTransfer.isSupportedType(event.currentDataType)) {
72              String text = (String)event.data;
73              TableItem item = new TableItem(dropTable, SWT.NONE);
74              item.setText(text);
75          }
76          if (fileTransfer.isSupportedType(event.currentDataType)){
77              String[] files = (String[])event.data;
78              for (int i = 0; i < files.length; i++) {
79                  TableItem item = new TableItem(dropTable, SWT.NONE);
80                  item.setText(files[i]);
81              }
82          }
83      }
84  });

2. Exercise: Drag and Drop in SWT

The methods moveAbove and moveBelow provide the option to re-arrange widgets within a layout. The following example demonstrates the usage of drag and drop to re-arrange widgets in a layout. It assumes that you have created several png file in an images folder of the project.

Create the following class.

package com.vogella.swt.widgets.dnd;

import java.io.File;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DragSource;
import org.eclipse.swt.dnd.DragSourceAdapter;
import org.eclipse.swt.dnd.DragSourceEvent;
import org.eclipse.swt.dnd.DropTarget;
import org.eclipse.swt.dnd.DropTargetAdapter;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;

public class PhotoShuffler {

    public static void main(String[] args) {
        // setup the SWT window
        Display display = new Display();
        final Shell shell = new Shell(display);
        shell.setLayout(new FillLayout());
        shell.setText("Photo Shuffler");

        // initialize a parent composite with a grid layout manager
        Composite parent = new Composite(shell, SWT.NONE);
        GridLayout gridLayout = new GridLayout();
        gridLayout.numColumns = 2;
        gridLayout.marginWidth=20;
        parent.setLayout(gridLayout);

        // determine the path where the pictures are stored
        String path = System.getProperty("user.dir") + "/images/";
        // initialize an array with the photograph names

        File imageDir= new File(path);

        // loop over the photo array and establish all listeners
        List<File> files = Arrays.stream(imageDir.listFiles())
                        .filter(f -> f.isFile() && f.getName().endsWith(".png"))
                        .collect(Collectors.toList());

        for (File file : files) {
            // labels serve as containers for the images
            Label label = new Label(parent, SWT.NONE);
            Image img = new Image(display,file.getAbsolutePath());
            label.setImage(img);

            // enable each label to be draggable
            DragSource source = new DragSource(label, DND.DROP_NONE);
            source.setTransfer(TextTransfer.getInstance()); // varargs are supported as of 4.7
            // add a drag listener
            source.addDragListener(new MyDragSourceListener(parent, source));

            // enable each label to be a drop target
            DropTarget target = new DropTarget(label, DND.DROP_NONE);
            target.setTransfer(new Transfer[] { TextTransfer.getInstance() }); // varargs are not yet supported see https://git.eclipse.org/r/#/c/92236         // add a drop listener
            target.addDropListener(new MyDropTargetListener(parent, target));
        }

        // show the SWT window
        shell.pack();
        shell.open();
        while (!shell.isDisposed()) {
            if (!display.readAndDispatch())
                display.sleep();
        }
        // tear down the SWT window
        display.dispose();
    }

    private static class MyDragSourceListener extends DragSourceAdapter {

        private Composite parentComposite;
        private DragSource source;

        /**
         * @param parentComposite - the composite that holds all pictures
         * @param source - the drag source
         *
         */
        public MyDragSourceListener(Composite parentComposite, DragSource source) {
            this.parentComposite = parentComposite;
            this.source = source;
        }

        /**
         * The method computes the position / index of the source control
         * (label) in the children array of the parent composite. This index is
         * passed to the drop target using the data field of the drag source
         * event.
         */
        public void dragSetData(DragSourceEvent event) {
            for (int i = 0; i < parentComposite.getChildren().length; i++) {
                if (parentComposite.getChildren()[i].equals(source.getControl())) {
                    event.data = new Integer(i).toString();
                    break;
                }
            }
        }

    }


public static class MyDropTargetListener extends DropTargetAdapter {

    private Composite parentComposite;
    private DropTarget target;

    /**
     * @param parentComposite - the composite that holds all pictures
     * @param target - the drop target
     */
    public MyDropTargetListener(Composite parentComposite, DropTarget target) {
        this.parentComposite = parentComposite;
        this.target = target;
    }

    /**
     * This method moves the dragged picture to the new position and shifts the
     * old picture to the right or left.
     */
    public void drop(DropTargetEvent event) {

        // retrieve the stored index
        int sourceIndex = Integer.valueOf(event.data.toString());

        // compute the index of target control
        Control targetControl = target.getControl();
        int targetIndex = -1;
        for (int i = 0; i < parentComposite.getChildren().length; i++) {
            if (parentComposite.getChildren()[i].equals(targetControl)) {
                targetIndex = i;
                break;
            }
        }

        Control sourceControl = parentComposite.getChildren()[sourceIndex];
        // do not do anything if the dragged photo is dropped at the same
        // position
        if (targetIndex == sourceIndex)
            return;

        // if dragged from left to right
        // shift the old picture to the left
        if (targetIndex > sourceIndex)
            sourceControl.moveBelow(targetControl);
        // if dragged from right to left
        // shift the old picture to the right
        else
            sourceControl.moveAbove(targetControl);

        // repaint the parent composite
        parentComposite.requestLayout();
    }

}


}
photodnd

2.1. Adding a drag icon with drag shadow

For this exercise we are going to use the class DropShadow from imageEffects.zip. Please make sure to import it into your project. The code is from the Simple Image Effects for SWT Eclipse guide.

We want to replace the drag icon with the dragged image itself and add a nice drop shadow. For this purpose we add an additional DragListener to the DragSource.

source.addDragListener(new DragSourceAdapter() {
    @Override
    public void dragStart(DragSourceEvent dragSourceEvent) {
        // getting control widget - Label in this case
        Label label = ((Label) ((DragSource) dragSourceEvent.getSource()).getControl());
        // make the drag icon the dragged image itself
        dragSourceEvent.image = label.getImage();

        // add shadow to drag icon
        dragSourceEvent.image = new Image(display,
                DropShadow.dropShadow(dragSourceEvent.image.getImageData(),
                        display.getSystemColor(SWT.COLOR_BLACK),
                        10, 30, 100));
    }
});
drag_shadow.png

3. Exercise: Implement drag and drop

The following exercise demonstrates the usage of drag and drop for customer domain objects and SWT widgets. You drag and drop between two JFace viewers (a table and a tree).

3.1. Create new project

Create a new Eclipse RCP application called com.vogella.dnd.jface with two views.

Create the following model class.

package com.vogella.dnd.jface.model;

public class Todo {
    private String shortDescription;
    private String longDescription;

    public Todo(String shortDescription, String longDescription) {
        this.shortDescription = shortDescription;
        this.longDescription = longDescription;
    }

    public String getShortDescription() {
        return shortDescription;
    }
    public void setShortDescription(String shortDescription) {
        this.shortDescription = shortDescription;
    }
    public String getLongDescription() {
        return longDescription;
    }
    public void setLongDescription(String longDescription) {
        this.longDescription = longDescription;
    }


}

Create the following content providers (Singletons).

package com.vogella.dnd.jface.model;

import java.util.ArrayList;
import java.util.List;

public enum ContentProvider {
    INSTANCE;

    public List<Todo> getModel(){
        List<Todo> list = new ArrayList<Todo>();
        Todo todo = new Todo("Java", "Learn the Closure proposal");
        list.add(todo);
        todo = new Todo("Eclipse", "Learn more about the RCP platform");
        list.add(todo);
        return list;
    }
}
package com.vogella.dnd.jface.model;

import java.util.ArrayList;
import java.util.List;

public enum ContentProviderTree {
    INSTANCE;
    List<String> list = new ArrayList<String>();

    private ContentProviderTree() {
        list.add("Branch1");
        list.add("Branch1");
    }

    public List<String> getModel(){
        return list;
    }
}

Create the following label and content provider for your JFace viewers.

package com.vogella.dnd.jface.viewers;

import java.util.List;

import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.Viewer;

import com.vogella.dnd.jface.model.Todo;

public class TableContentProvider implements IStructuredContentProvider {

    @Override
    public Object[] getElements(Object inputElement) {
        List<Todo> list = (List<Todo>) inputElement;
        return list.toArray();
    }

}
package com.vogella.dnd.jface.viewers;

import java.util.List;

import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.Viewer;

public class TreeContentProvider implements ITreeContentProvider {

    @Override
    public Object[] getChildren(Object parentElement) {
        return null;
    }

    @Override
    public Object getParent(Object element) {
        return null;
    }

    @Override
    public boolean hasChildren(Object element) {
        return false;
    }

    @Override
    public Object[] getElements(Object inputElement) {
        List<String> list = (List<String>) inputElement;
        return list.toArray();
    }
}
package com.vogella.dnd.jface.viewers;

import org.eclipse.jface.viewers.BaseLabelProvider;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.swt.graphics.Image;

import com.vogella.dnd.jface.model.Todo;

public class TableLabelProvider extends BaseLabelProvider implements
        ITableLabelProvider {

    @Override
    public Image getColumnImage(Object element, int columnIndex) {
        return null;
    }

    @Override
    public String getColumnText(Object element, int columnIndex) {
        Todo todo = (Todo) element;
        switch (columnIndex) {
        case 0:
            return todo.getShortDescription();
        case 1:
            return todo.getLongDescription();
        }
        return "Not supported";
    }

}
package com.vogella.dnd.jface.viewers;

import org.eclipse.jface.viewers.LabelProvider;


public class TreeLabelProvider extends LabelProvider {
    @Override
    public String getText(Object element) {
        String s = (String) element;
        return s;
    }

}

3.2. Drag and drop Listener

Create the following drag and drop listener. These are later assigned to your table and tree widgets.

package com.vogella.dnd.jface.dnd;

import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.swt.dnd.DragSourceEvent;
import org.eclipse.swt.dnd.DragSourceListener;
import org.eclipse.swt.dnd.TextTransfer;

import com.vogella.dnd.jface.model.Todo;
public class MyDragListener implements DragSourceListener {

    private final TableViewer viewer;

    public MyDragListener(TableViewer viewer) {
        this.viewer = viewer;
    }

    @Override
    public void dragFinished(DragSourceEvent event) {
        System.out.println("Finshed Drag");
    }

    @Override
    public void dragSetData(DragSourceEvent event) {
        // Here you do the convertion to the type which is expected.
        IStructuredSelection selection = viewer.getStructuredSelection();
        Todo firstElement = (Todo) selection.getFirstElement();

        if (TextTransfer.getInstance().isSupportedType(event.dataType)) {
            event.data = firstElement.getShortDescription() + " " + firstElement.getLongDescription();
        }

    }

    @Override
    public void dragStart(DragSourceEvent event) {
        System.out.println("Start Drag");
    }

}
package com.vogella.dnd.jface.dnd;

import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerDropAdapter;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.dnd.TransferData;

import com.vogella.dnd.jface.model.ContentProviderTree;

public class MyDropListener extends ViewerDropAdapter {

    private final Viewer viewer;

    public MyDropListener(Viewer viewer) {
        super(viewer);
        this.viewer = viewer;
    }

    @Override
    public void drop(DropTargetEvent event) {
        int location = this.determineLocation(event);
        String target = (String) determineTarget(event);
        String translatedLocation ="";
        switch (location){
        case 1 :
            translatedLocation = "Dropped before the target ";
            break;
        case 2 :
            translatedLocation = "Dropped after the target ";
            break;
        case 3 :
            translatedLocation = "Dropped on the target ";
            break;
        case 4 :
            translatedLocation = "Dropped into nothing ";
            break;
        }
        System.out.println(translatedLocation);
        System.out.println("The drop was done on the element: " + target );
        super.drop(event);
    }

    // This method performs the actual drop
    // We simply add the String we receive to the model and trigger a refresh of the
    // viewer by calling its setInput method.
    @Override
    public boolean performDrop(Object data) {
        ContentProviderTree.INSTANCE.getModel().add( data.toString());
        viewer.setInput(ContentProviderTree.INSTANCE.getModel());
        return false;
    }

    @Override
    public boolean validateDrop(Object target, int operation,
            TransferData transferType) {
        return true;

    }



}

3.3. Adjusting your parts

Adjust your views. The first view shows a table and allows the user to drag an elements from it. The second view accepts drops.

package com.vogella.dnd.jface.view;

import javax.annotation.PostConstruct;

import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.widgets.Composite;

import com.vogella.dnd.jface.dnd.MyDragListener;
import com.vogella.dnd.jface.model.ContentProvider;
import com.vogella.dnd.jface.viewers.TableContentProvider;
import com.vogella.dnd.jface.viewers.TableLabelProvider;

public class TableView {


    @PostConstruct
    public void createPartControl(Composite parent) {
        TableViewer viewer = new TableViewer(parent, SWT.MULTI | SWT.H_SCROLL
                | SWT.V_SCROLL);
        int operations = DND.DROP_COPY| DND.DROP_MOVE;
        Transfer[] transferTypes = new Transfer[]{TextTransfer.getInstance()};
        viewer.addDragSupport(operations, transferTypes , new MyDragListener(viewer));
        viewer.setContentProvider(new TableContentProvider());
        viewer.setLabelProvider(new TableLabelProvider());
        viewer.setInput(ContentProvider.INSTANCE.getModel());
    }
}
package com.vogella.dnd.jface.view;

import javax.annotation.PostConstruct;

import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.part.ViewPart;

import com.vogella.dnd.jface.dnd.MyDropListener;
import com.vogella.dnd.jface.model.ContentProviderTree;
import com.vogella.dnd.jface.viewers.TreeContentProvider;
import com.vogella.dnd.jface.viewers.TreeLabelProvider;

public class TreeView  {

    @PostConstruct
    public void createPartControl(Composite parent) {
        TreeViewer viewer = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL
                | SWT.V_SCROLL);
        int operations = DND.DROP_COPY | DND.DROP_MOVE;
        Transfer[] transferTypes = new Transfer[]{TextTransfer.getInstance()};
        viewer.addDropSupport(operations, transferTypes, new MyDropListener(viewer));
        viewer.setContentProvider(new TreeContentProvider());
        viewer.setLabelProvider(new TreeLabelProvider());
        viewer.setInput(ContentProviderTree.INSTANCE.getModel());
    }
}

4. Eclipse drag and drop resources