[an error occurred while processing this directive]

Page One
Page Two
The Databank
What Is Swing?
Special Report
IDE Roundup
Swing and the Web
Swing Text
Tech Topics
Friends
Tips and Tricks
The PLAF Papers
Call 911
The Archive
JFC Home
Download Swing
Swing API Docs
Download JDK
JDK Docs
Java Tutorial
 
The Swing Connection Tech Topics

Drag and Drop with Swing

Part 1: Drag and Drop Fundamentals
By David Geary

David Geary is the author of Graphic Java: Mastering the JFC. Volumes 1 and 2 cover the AWT and Swing, respectively. David is currently working on the third volume, Advanced Swing.

This article focuses on drag and drop fundamentals. It is Part 1 of a two-part article on drag and drop with Swing. The second article will discuss Swing-specific considerations, including drag and drop between Swing components and native applications.


Drag and Drop Fundamentals

Drag and drop involves a drag source and one or more drop targets, each of which is associated with a component. Dragging is initiated by a drag gesture in a drag source component. The dragged item can subsequently be dropped on a drop target component.

Several objects, listed in Table 1, are involved in drag and drop operations. All of the classes listed in the table are from the java.awt.dnd package, with the exception of Transferable, which is from the java.awt.datatransfer package.


Table 1
Drag and Drop Participants.

Class/Interface

Description

DragGestureRecognizer

Fires events in response to drag gestures in a component

DragSource

Initiates drags and creates drag gesture recognizers

DropTarget

Drop takes place in a drop target's associated component

Transferable

A wrapper for data that is transferred via drag and drop

DragGestureListener

Notified by recognizer of drag gesture; typically initiates drag

DragSourceListener

Responds to events in a drag source

DropTargetListener

Handles drop target events including the drop itself



Drag and drop is initiated with a gesture, which is almost always a mouse down followed by mouse drags. Drag gesture recognizers fire events when drag gestures are detected in an associated component.

Drag sources initiate drags with the startDrag() method and create drag gesture recognizers with either createDragGestureRecognizer()or createDefaultDragGestureRecognizer() .

Drop targets are associated with a component and a drop target listener. When drop target events (including the drop) occur in the component, the listener is notified.

Transferables wrap data that is transferred from a drag source to a drop target.** The initiator of a drag wraps data in a transferable, and drops are handled by accessing a transferable's data.

Drag gesture listeners are notified of drag gestures by a recognizer. The typical response is to initiate a drag by invoking DragSource.startDrag().

Drag source listeners react to events that occur in a drag source after a drag is initiated.

Drop target listeners are responsible for handling drop target events including the actual drop itself.

On the surface, drag and drop can be intimidating. Drag sources, drop targets, drag gesture recognizers, and transferables must be created and a cast of listeners implemented for even the simplest drag and drop operation.

In practice, however, drag and drop is straightforward. Other than wrapping data in a transferable and handling the drop, most of the action takes place without programmer intervention.


Adding Drag and Drop to Swing Components

Adding drag and drop capabilities to Swing components typically involves one of the following options:

  1. Extend a Swing component class, such as JList or JTree, to act as a drag source component, a drop target component, or both.
  2. Have a third party associate a component with a drag source, drop target, or both.

Both options involve associating components with drag sources and drop targets. The difference stems from who does the associating. For Option 1, it's a subclass of a Swing component. For Option 2, it's an object other than the component, the drag source, or the drop target.

Notice that extending a Swing component -- Option 1 -- adds drag and drop functionality only to instances of the component extension; functionality cannot be added to standard components. For example, drag and drop functionality in a ListThatsADragSource extension of JList is only available to instances of ListThatsADragSource and cannot be used with instances of JList . Therefore, the use of Option 1 should be limited to custom components with inherent drag and drop capabilities that are not easily generalized.

The application shown in Figure 1 exercises both options--the drag source is implemented as an extension of the JTree class, and an instance of JTextPane is associated with a drop target by the application. Filenames dragged from the tree and dropped in the textpane are displayed as text files. The top picture in Figure 1 shows the initiation of a drag, and the bottom picture shows the result of a subsequent drop in the text pane.



Figure 1
Dragging from a JTree into a JTextPane.

The Drag Source

Example 1 is a truncated listing of the application's drag source. The DragTree class extends JTree and implements the DragGestureListener and DragSourceListener interfaces.

Example 1
A Drag Source

class DragTree extends JTree 
                implements DragGestureListener,
                DragSourceListener {    

   public DragTree() {

      DragSource dragSource = DragSource.getDefaultDragSource();


      // creating the recognizer is all that’s necessary - it
      // does not need to be manipulated after creation
      dragSource.createDefaultDragGestureRecognizer(
         this, // component where drag originates
         DnDConstants.ACTION_COPY_OR_MOVE, // actions
         this); // drag gesture listener
         ...
      }
      public void dragGestureRecognized(DragGestureEvent e) {
         // drag anything ...
         e.startDrag(DragSource.DefaultCopyDrop, // cursor
            new StringSelection(getFilename()), // transferable
            this); // drag source listener
      }
      public void dragDropEnd(DragSourceDropEvent e) {}
      public void dragEnter(DragSourceDragEvent e) {}
      public void dragExit(DragSourceEvent e) {}
      public void dragOver(DragSourceDragEvent e) {}
      public void dropActionChanged(DragSourceDragEvent e) {}
      ...
   }   

When the tree is constructed, a reference to a default drag source is obtained with the static DragSource.getDefaultDragSource() method, and the drag source is used to create a drag gesture recognizer. The recognizer, when constructed, is associated with a component (the tree) and a drag gesture listener (also the tree). It is not necessary to directly manipulate the recognizer after its creation, and therefore the DragTree class does not maintain a reference to it.***

Like most drag gesture listeners, the tree reacts to drag gestures by initiating a drag with the DragSource.startDrag() method. The startDrag() method is passed a filename wrapped in an instance of the StringSelection class. java.awt.datatransfer.StringSelection is an implementation of the Transferable interface for transferring strings.

The group of empty methods implemented by the DragTree class are defined by the DragSourceListener interface. DragSource.startDrag() must be passed a non-null reference to a DragSourceListener, so the tree itself acts as a bug-free but thoroughly boring drag source listener.

Click the Source Code button for the complete DragTree.java listing.

The Drop Target

The drop target for the application shown in Figure 1 is handled by the Test class, which is listed in Example 2.

Example 2
A Drop Target

public class Test extends JFrame 
                 implements DropTargetListener {
   private JTextPane textPane = new JTextPane();
   public Test() {
      ...
      new DropTarget(textPane, // component
        DnDConstants.ACTION_COPY_OR_MOVE, // actions
         this); // DropTargetListener
      ...
   }
   ...
   public void drop(DropTargetDropEvent e) {
      try {
         DataFlavor stringFlavor = DataFlavor.stringFlavor;
         Transferable tr = e.getTransferable();
         if(e.isDataFlavorSupported(stringFlavor)) {
            String filename = 
                 (String)tr.getTransferData(stringFlavor);
            e.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
            readFile(filename);
            textPane.setCaretPosition(0);
            e.dropComplete(true);
         }
         else {
            e.rejectDrop();
         }
      }
      catch(IOException ioe) {
         ioe.printStackTrace();
      }
      catch(UnsupportedFlavorException ufe) {
         ufe.printStackTrace();
      }
    }
    public void dragEnter(DropTargetDragEvent e) { }
    public void dragExit(DropTargetEvent e) { }
    public void dragOver(DropTargetDragEvent e) { }
    public void dropActionChanged(DropTargetDragEvent e) { }
}          

The Test constructor creates a drop target, specifying the text pane as the drop target component and the application as the drop target listener; thus, Test.drop() is invoked when a drop occurs in the text pane. The drop target, like the DragTree's drag gesture recognizer, is instantiated but not used.

The drop is accepted if the transferable associated with the drop can supply its data as a string. After accepting the drop, the application's readFile() method is invoked, which loads the contents of the file into the text pane. The caret position for the text pane is set to 0, ensuring that the first line of text is displayed at the top of the text pane. After the drop is complete, e.dropComplete(true) is invoked.

The rest of the methods defined by the DropTargetListener interface are implemented as empty methods.

Click the Source Code button for the complete Test.java listing.

Selective Dragging

Sometimes it is desirable to restrain the types of objects that can be dragged from a drag source. For example, the tree in Figure 1 allows any type of file -- including executable files -- to be dropped in the text pane, which displays files as text.

Figure 2 shows a modification of the application shown in Figure 1 that restrains dragging to text files and Java source files. The picture in Figure 2 shows the result of an illegal drag attempt.



Figure 2
Restricting dragging to text files.

The modified drag source is listed in Example 3.

Example 3
Restricting Draggable Objects

class DragTree extends JTree implements DragGestureListener,
        DragSourceListener {
   ...
   public void dragGestureRecognized(DragGestureEvent e) {
     String s = getFilename();
     if(s.endsWith(".txt") || s.endsWith(".java")) {
        e.startDrag(DragSource.DefaultCopyDrop,   // cursor
           new StringSelection(s),     // transferable
           this);  // drag source listener
     }
     else {
        SwingUtilities.invokeLater(new Runnable() {
           public void run() {
              JOptionPane.showMessageDialog(
                 SwingUtilities.getRootPane(DragTree.this),
                 "Only \".txt\" and \".java\" 
                 files " +
                 "can be dragged",
                 "Not Draggable",
                 JOptionPane.ERROR_MESSAGE);
           }
        });
     }
   }
   ...
 }         

The tree's dragGestureRecognized() method starts a drag only if the selected file ends in ".txt" or ".java".

If an attempt is made to drag a file that does not end in ".txt" or ".java", an error dialog is displayed. A bug prevents the dialog from being shown directly in the drop() method, so SwingUtilities.invokeLater() is used to show the dialog on the next leg of the event dispatch thread.


A Drag and Drop Recipe

Using drag and drop is relatively simple, as illustrated by the example discussed in "Adding Drag and Drop to Swing Components." Perhaps the most difficult aspect of drag and drop is remembering all of the steps involved; because of this difficulty, we will conclude with a recipe for drag and drop:

  1. Obtain a reference to a drag source with DragSource.getDefaultDragSource() or by instantiating a new one -- for example, new DragSource() .
  2. Create a drag gesture recognizer with the drag source from step 1 by invoking DragSource.createDefaultDragGestureRecognizer(). The method is passed the component where the drag originates.
  3. Create a drop target, specifying a component and a drop target listener.
  4. Wrap the data to be dragged in a transferable.
  5. Initiate a drag when the drag gesture recognizer from step 2 is notified by invoking DragSource.startDrag() for the drag source from step 1. The transferable from step 4 is passed to the startDrag() method.
  6. Handle the drop by implementing the DropTargetListener interface.
  7. Implement the DragSource interface (often with empty methods).

** Transferables may be transferred via the clipboard without using drag and drop.

*** Thus the rather odd construct of instantiating an object without equating it to a reference.

 

[an error occurred while processing this directive]