How to drag and drop with Java 2, Part 1

Explore the Java platform's new drag and drop classes

If you've ever selected a file icon in a filesystem browser like Windows Explorer and dragged it to an icon representing another directory (and it's likely you have), you've already used drag and drop to transfer data. If you'd like to use Java to transfer data, read on!

Java 2 (formerly JDK 1.2) introduced the ability to transfer data using the familiar drag and drop (D&D) metaphor. In Java 2, D&D utilizes the underlying data-transfer mechanism introduced in JDK 1.1 (java.awt.datatransfer) for use with the clipboard. Although this article discusses D&D operations in the context of GUI components, the specification includes no restrictions that prevent direct programmatic operations.

To develop the D&D metaphor, Java 2 defines several new classes in package java.awt.dnd. Please note: The GUI components used in this article are Swing components. In actuality, any subclass of java.awt.Component may be used.

First, we'll look at how a GUI component representing the data source of a D&D operation maintains an association with a java.awt.dnd.DropSource object.

Second, we'll examine how another GUI component representing the destination of the data of a D&D operation maintains an association with a java.awt.dnd.DropTarget object.

Finally, we'll wrap up with a java.awt.datatransfer.Transferable object that encapsulates the data transferred between the DragSource and DropTarget objects.

To download the source code in either zip or tar formats, see Resources.

DataFlavors and actions

When the Transferable object encapsulates data, it makes the data available to DropTarget in a variety of DataFlavors. For a local transfer within the same JVM (Java virtual machine), Transferable provides an object reference.

However, for transfers to another JVM or to the native system, this wouldn't make any sense, so a DataFlavor using a java.io.InputStream subclass usually is provided. (While a discussion of data transfer classes is beyond the scope of this article, you will find a linked list of previous JavaWorld articles on this topic in the Resources section below.)

When invoking a drag and drop operation, you may request various drag and drop actions. The DnDConstants class defines the class variables for the supported actions:

  • ACTION_NONE -- no action taken
  • ACTION_COPY -- the DragSource leaves the data intact
  • ACTION_MOVE -- the DragSource deletes the data upon successful completion of the drop
  • ACTION_COPY or ACTION_MOVE -- the DragSource will perform either action requested by the DropTarget
  • ACTION_LINK or ACTION_REFERENCE -- a data change to either the source or the destination propagates to the other location

Creating a draggable component

For a GUI component to act as the source of a D&D operation, it must be associated with five objects:

  • java.awt.dnd.DragSource
  • java.awt.dnd.DragGestureRecognizer
  • java.awt.dnd.DragGestureListener
  • java.awt.datatransfer.Transferable
  • java.awt.dnd.DragSourceListener

The DragSource

A common way to obtain a DragSource object is to use one instance per JVM. Class method DragSource.getDefaultDragSource will obtain a shared DragSource object that is used for the lifetime of the JVM. Another option is to provide one DragSource per instance of the Component class. With this option, however, you accept responsibility for implementation.

The DragGestureRecognizer

The user gesture or set of gestures that initiates a D&D operation will vary per component, platform, and device:

Windows drag and drop gestures
Click left mouse buttonMove
Control, left mouse buttonCopy
Shift-Control, left mouse buttonLink
Motif Drag and drop gestures
Shift, BTransfer (middle button)Move
Control, BTransferCopy
Shift-Control, BTransferLink

A DragGestureRecognizer encapsulates these implementation details, shielding you from platform dependencies. The instance method dragSource.createDefaultDragGestureRecognizer() will obtain a recognizer and associate it with a component, action, and DragGestureListener.

This example creates a subclass of a Swing label (JLabel). In its constructor, the necessary classes and associations are made for it to act as a drag source for either a copy or move operation. We'll discuss listeners next. Here's the first step in making any draggable component:

public class DragLabel extends JLabel { public DragLabel(String s) { this.setText(s); this.dragSource = DragSource.getDefaultDragSource(); this.dgListener = new DGListener(); this.dsListener = new DSListener();

// component, action, listener this.dragSource.createDefaultDragGestureRecognizer( this, DnDConstants.ACTION_COPY_OR_MOVE, this.dgListener ); } private DragSource dragSource; private DragGestureListener dgListener; private DragSourceListener dsListener; }

The DragGestureListener

When the DragGestureRecognizer associated with the GUI component recognizes a D&D action, it messages the registered DragGestureListener. Next, the DragGestureListener sends the DragSource a startDrag message telling it to initiate the drag:

interface DragGestureListener {
    public void dragGestureRecognized(DragGestureEvent e);
}

When the DragSource receives the startDrag message, it creates a DragSourceContext context object. This object tracks the state of the operation by listening to a native DragSourceContextPeer. In this situation, the DragSource may be obtained from the Event object or by an instance variable.

The particular DragSourceListener that will be informed during the progress of the D&D operation is specified as a formal parameter to dragGestureRecognized. The initial drag cursor that shows the preliminary state of the D&D operation is also specified as a parameter. If the draggable component cannot accept drops, the initial cursor should be DragSource.DefaultCopyNoDrop.

If your platform allows it, you may specify an optional "drag image" to be displayed in addition to the cursors. Win32 platforms, however, do not support drag images.

A Transferable object encapsulates the data -- most likely associated with the Component (that is, the label's text) -- that will be transferred. Here's how to start a drag:

    public void dragGestureRecognized(DragGestureEvent e) {
      // check to see if action is OK ...
      try {
    Transferable transferable = ... 
    //initial cursor, transferable, dsource listener      
    e.startDrag(DragSource.DefaultCopyNoDrop, transferable, dsListener);
    // or if dragSource is an instance variable:
    // dragSource.startDrag(e, DragSource.DefaultCopyNoDrop, transferable, 
dsListener);
      }catch( InvalidDnDOperationException idoe ) {
    System.err.println( idoe );
      }
    }

The Transferable object

The java.awt.datatransfer.StringSelection class works well for transfers within the same JVM but suffers from a ClassCastException when used in inter-JVM cases. To solve this problem, you'll have to provide a custom Transferable object.

The custom Transferable object creates instances of the DataFlavors it wishes to provide. The Transferable interface directs method getTransferDataFlavors() to return an array of these flavors. To this end, we create a java.util.List representation of this array to facilitate the implementation of isDataFlavorSupported(DataFlavor).

This example provides two flavors. Since we're simply transferring text data, we can use the two predefined DataFlavor flavors. For local transfers (within the same JVM), we can use DataFlavor.stringFlavor. For nonlocal transfers, we prefer DataFlavor.plainTextFlavor, since its internal representation class is a java.io.InputStream.

Moreover, we could define our own DataFlavors to map to MIME types such as image/JPEG, or define custom-text charsets such as Latin-1; but we'll save that discussion for a future article.

Although the Transferable doesn't necessarily have to be a ClipboardOwner for drag and drop, enabling this functionality will make it available for clipboard transfers.

Let's see the definition of a simple Transferable for text data:

public class StringTransferable implements Transferable, ClipboardOwner { public static final DataFlavor plainTextFlavor = DataFlavor.plainTextFlavor; public static final DataFlavor localStringFlavor = DataFlavor.stringFlavor;

public static final DataFlavor[] flavors = { StringTransferable.plainTextFlavor, StringTransferable.localStringFlavor };

private static final List flavorList = Arrays.asList( flavors );

public synchronized DataFlavor[] getTransferDataFlavors() { return flavors; } public boolean isDataFlavorSupported( DataFlavor flavor ) { return (flavorList.contains(flavor)); }

The Transferable provides the data for the flavors it supports via its getTransferData method. However, if an unsupported flavor is requested, an exception will be thrown. If a local (same JVM) transfer is requested via the StringTransferable.localStringFlavor, an object reference is returned. Note: Object references don't make sense outside of the JVM.

A subclass of java.io.InputStream should be provided for native-to-Java or inter-JVM requests.

For StringTransferable.plainTextFlavor requests, getTransferData returns a java.io.ByteArrayInputStream. Text data may have different character encodings as specified in the MIME specification. (For more on the MIME specification, see Resources.)

The DataFlavor should be queried for the encoding requested by the DropTarget. Common character encodings are Unicode and Latin-1 (ISO 8859-1).

Here's how the Transferable can provide text data in a variety of formats and encodings:

public synchronized Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {

if (flavor.equals(StringTransferable.plainTextFlavor)) { String charset = flavor.getParameter("charset").trim(); if(charset.equalsIgnoreCase("unicode")) { System.out.println("returning unicode charset"); // uppercase U in Unicode here! return new ByteArrayInputStream(this.string.getBytes("Unicode")); } else { System.out.println("returning latin-1 charset"); return new ByteArrayInputStream(this.string.getBytes("iso8859-1")); } } else if (StringTransferable.localStringFlavor.equals(flavor)) { return this.string; } else { throw new UnsupportedFlavorException (flavor); } }

The DragSourceListener

The DragSourceListener is responsible for providing "drag over" effects during the D&D operation. Drag over effects provide visual feedback while the cursor is over a component, but do not permanently change the appearance of components.

interface DragSourceListener {
    public void dragEnter(DragSourceDragEvent e);
    public void dragOver(DragSourceDragEvent e);
    public void dragExit(DragSourceEvent e);
    public void dragDropEnd(DragSourceDropEvent e);
    public void dropActionChanged (DragSourceDragEvent e);
}

Usually the DragSourceListener accomplishes drag over effects via cursor changes. There are two possible cursors:

  • A Drop cursor, which is displayed while over a valid active-DropTarget
  • A NoDrop cursor, which is displayed while over anything else

The DragSource class has several predefined cursors as class variables:

Predefined cursors
DefaultCopyDropDefaultCopyNoDrop
DefaultMoveDropDefaultMoveNoDrop
DefaultLinkDropDefaultLinkNoDrop

The DragSourceListener object changes the cursor by sending a setCursor() message to the DragSourceContext -- obtained from the DragSourceEvent parameter. Additionally, the definition of the dragOver and dropActionChanged methods are similar. (As we shall see, these methods aren't invoked if the DropTarget rejects the operation.)

Here's how we can change the cursor to provide drag over feedback:

    public void dragEnter(DragSourceDragEvent e) {
      DragSourceContext context = e.getDragSourceContext();
      //intersection of the users selected action, and the source and target 
actions
      int myaction = e.getDropAction();
      if( (myaction & DnDConstants.ACTION_COPY) != 0) { 
    context.setCursor(DragSource.DefaultCopyDrop);    
      } else {
    context.setCursor(DragSource.DefaultCopyNoDrop);        
      }
    }

When the operation has ended, the DragSourceListener receives notification from a dragDropEnd message. When so notified, the listener's responsibility is to check the success of the operation, then, if successful, perform the requested action. If the operation isn't successful there's nothing for the DragSourceListener to do.

In the case of a move action, the listener will also remove the source data. (If it's a component, it will be taken out of the hierarchy; if it's the text data displayed in a text component, it will be erased.)

The following is an example of dragDropEnd. If the operation isn't successful, the methods simply return. The drop action is inspected to see if it was a move operation:

  public void dragDropEnd( DragSourceDropEvent e ) {
    if ( e.getDropSuccess() == false ) {
      return;
    }
    int dropAction = e.getDropAction();
    if ( dropAction == DnDConstants.ACTION_MOVE )
    // do whatever
  }

Flow review

Considering the complexity of the messages passed among the several objects we've discussed, it would be good to review the flow:

  1. DragGestureRecognizer recognizes a gesture, notifies the DragGestureListener.

  2. Assuming the actions and/or flavors are OK, DragGestureListener asks DragSource to startDrag().

  3. DragSource creates a DragSourceContext and a DragSourceContextPeer. The DragSourceContext adds itself as a DragSourceListener to the DragSourceContextPeer.

  4. DragSourceContextPeer receives state notifications (component entered/exited/is over) from the native system and delegates them to the DragSourceContext.

  5. The DragSourceContext notifies the DragSourceListener, which provides drag over feedback (if the DropTargetListener accepts the action). Typical feedback includes asking the DragSourceContext to change the cursor.

  6. When the drop is complete, the DragSourceListener receives a dragDropEnd notification message.

Creating a droppable component

For a GUI component to act as the destination of a D&D operation, it must be associated with two objects:

  • java.awt.dnd.DropTarget
  • java.awt.dnd.DropTargetListener

The DropTarget

The DropTarget's constructor makes associations with DropTarget and DropTargetListener objects. Alternatively, the DropTarget has setComponent and addDropTargetListener methods. The DropTarget's constructors use newly defined java.awt.Component methods to inform the Component that it is now associated with the DropTarget.

Here are the additions to java.awt.Component:

Component methods
public void setDropTarget(DropTarget dt)
public DropTarget getDropTarget()

Let's define a droppable component. Here we create a DropTarget in our Component's constructor. The Component associates itself and a DropTargetListener with the DropTarget via the DropTarget's constructor:

public class DropLabel extends JLabel { public DropLabel(String s) { this.setText(s); // not strictly necessary to maintain this reference this.dtListener = new DTListener();

// component, ops, listener, accepting this.dropTarget = new DropTarget(this, this.acceptableActions, this.dtListener, true); } private DropTarget dropTarget; private DropTargetListener dtListener; private int acceptableActions = DnDConstants.ACTION_COPY; }

The DropTargetListener

The DropTargetListener needs an association with the Component so that the Component can notify the DropTargetListener to display "drag under" effects during the operation. This listener, which can be conveniently created as an inner class, transfers the data when the drop occurs. Warning: The Component itself shouldn't be the listener, since this implies its availability for use as some other Component's listener.

The dragEnter, dragOver, and dropActionChanged methods must accept or reject the current operation after they inspect the action and flavor. Only if the operation is accepted will the DragSourceListener call its dragEnter and dragOver methods.

Additionally, the DropTargetListener invokes the dragExit method when the user leaves the droppable area. The listener may undo any drag under effects here.

Here's a DropTargetListener implementation that provides drag under feedback by displaying a green border if it can handle the transfer (we'll discuss how isDragOk determines this next) and a red border if it cannot:

  class DTListener implements DropTargetListener {
    public void dragEnter(DropTargetDragEvent e) {
      if(isDragOk(e) == false) {
    e.rejectDrag();      
    return;
      }
      DropLabel.this.borderColor=Color.green;      
      showBorder(true);
      e.acceptDrag(DropLabel.this.acceptableActions);
    }
    public void dragOver(DropTargetDragEvent e) {
      if(isDragOk(e) == false) {
    e.rejectDrag();      
    return;
      }
      e.acceptDrag(DropLabel.this.acceptableActions);      
    }
    public void dropActionChanged(DropTargetDragEvent e) {
      if(isDragOk(e) == false) {
    e.rejectDrag();      
    return;
      }
      e.acceptDrag(DropLabel.this.acceptableActions);      
    }
    public void dragExit(DropTargetEvent e) {
      DropLabel.this.borderColor=Color.green;            
      showBorder(false);
    }

In action, the complex flow of these messages remains hidden. For the curious, the event objects maintain a reference to their DropTargetContext. The DropTargetContext in turn maintains a reference to the native DropTargetContextPeer. When the listener sends the various accept or reject messages, they go all the way to the native system.

Now let's discuss how the listener can determine the validity of the current drag operation. Methods dragEnter, dragOver, and dropActionChanged use method isDragOk to determine whether the DataFlavor being dragged is compatible with the flavors the target component can accept. In this example, any of the flavors defined in StringTransferable will be accepted. In addition, a class variable referring to a collection of DataFlavors defined in the drop component might also be used.

If the flavor is compatible, the DropTargetListener compares the actions supported by the DragSource to the actions acceptable to the DropTarget. The actions specified when the Component created the DragGestureRecognizer may be retrieved through the DropTargetDragEvent's getSourceActions method. If both the action and the flavor are OK, a true value is returned.

Let's examine the definition of isDragOk. The DataFlavors and actions offered by the DragSource are inspected:

private boolean isDragOk(DropTargetDragEvent e) { DataFlavor[] flavors = StringTransferable.flavors; DataFlavor chosen = null; for (int i = 0; i < flavors.length; i++) { if (e.isDataFlavorSupported(flavors[i])) { chosen = flavors[i]; break; } } /* * the src does not support any of the StringTransferable * flavors */ if (chosen == null) { return false; } // the actions specified when the source // created the DragGestureRecognizer int sa = e.getSourceActions();

// we're saying that these actions are necessary if ((sa & DropLabel.this.acceptableActions) == 0) return false; return true; }

Data transfer

The DropTargetListener's drop method is responsible for the data transfer. There are three parts to the drop method:

  1. The DropTargetListener checks the validity of the dropped action and DataFlavor. If necessary, the drop is rejected.
  2. The DropTargetListener accepts the drop with a specified action and the Transferable's data object is retrieved.

  3. The DropTargetListener reads and uses the data.

Drop method part 1: validation

The DropTargetListener chooses the DataFlavor and checks the operation before the transfer takes place. If none of the DataFlavors and/or operations are acceptable, the DropTargetListener sends a rejectDrop message to the DropTargetDropEvent. The event object may be queried with isLocalTransfer if the drag source is in the same JVM. If so, the DropTargetListener can choose an appropriate DataFlavor:

    public void drop(DropTargetDropEvent e) {
      DataFlavor[] flavors = StringTransferable.flavors;
      DataFlavor chosen = null;    
      if (e.isLocalTransfer() == false) {
    chosen = StringTransferable.plainTextFlavor; 
      } else {
    for (int i = 0; i < flavors.length; i++ ) {
      if (e.isDataFlavorSupported(flavors[i])) {
        chosen = flavors[i];
        break;
      }
    }
      }
      if (chosen == null) {
    e.rejectDrop();         
    return;
      }
      // the actions that the source has specified with DragGestureRecognizer
      int sa = e.getSourceActions();      
      if ( ( sa & DropLabel.this.acceptableActions ) == 0 ) {
    e.rejectDrop();             
    showBorder(false);              
    return;
      }

Drop method part 2: accepting the drop

Once the drop passes the validation, the DropTargetListener sends an acceptDrop message -- with the desired operation specified -- to the DragSourceListener in its dragDropEnd method. After the DropTargetListener accepts the drop, it retrieves the Transferable and requests its data.

Here's the continuation of the drop method:

Object data=null; try { e.acceptDrop(DropLabel.this.acceptableActions); data = e.getTransferable().getTransferData(chosen); if (data == null) throw new NullPointerException(); } catch ( Throwable t ) { t.printStackTrace(); e.dropComplete(false); showBorder(false); return; }

Drop method part 3: data transfer

In the case of native-to-Java or inter-JVM transfers, the data's representation class should be a subclass of java.io.InputStream, since references to a Java object make sense only in the JVM. (There's a bug in Java 2's StringSelection class: it returns a StringReader, which isn't a subclass of InputStream.)

For local transfers, the Transferable returns an object reference.

If there are any exceptions thrown during data transfer, the DropTargetListener must send a dropComplete(false) message to the DropTargetDropEvent.

However, once the data has been successfully transferred, the DropTargetListener sends the DropTargetDropEvent a dropComplete(true) message.

Note: Unfortunately, Java 2 still contains a bug that prevents some native-to-Java transfers. Java-to-native transfers, however, do not crash despite a few inserted garbage characters. (For more information about the Java 2 bug, see Resources.)

Continuing with our definition of the drop method, we finally retrieve the data:

if (data instanceof String ) { String s = (String) data; DropLabel.this.setText(s); } else if (data instanceof InputStream) { String charset = chosen.getParameter("charset").trim(); InputStream input = (InputStream)data; InputStreamReader isr = null; try { isr=new InputStreamReader(input,charset); } catch(UnsupportedEncodingException uee) { isr=new InputStreamReader(input); }

StringBuffer str = new StringBuffer(); int in=-1; try { while((in = isr.read()) >= 0 ) { if (in != 0) str.append((char)in); } DropLabel.this.setText(str.toString()); } catch(IOException ioe) { /* bug #4094987 sun.io.MalformedInputException: Missing byte-order mark e.g. if dragging from MS Word 97 to Java still a bug in 1.2 final */ System.err.println( "cannot read" + ioe); e.dropComplete(false); showBorder(false); String message = "Bad drop\n" + ioe.getMessage(); JOptionPane.showMessageDialog(DropLabel.this, message, "Error", JOptionPane.ERROR_MESSAGE); return; } } else { System.out.println( "drop: rejecting"); e.dropComplete(false); showBorder(false); return; } e.dropComplete(true); showBorder(false); }

Path of the Transferable

Here are the gory details of the path taken by the Transferable:

The DragGestureListener gets the Transferable (probably from the GUI component) and sends it to the DragSource via the startDrag message.

The DragSource sends the same message to its DragSourceContext, which in turn forwards it to its peer (passing itself as a parameter). The native system thus has access to the Transferable through the DragSourceContext.

The DropTargetContextPeer now has access to this Transferable. When the DropTargetContext receives the getTransferable message, it creates and returns a TransferableProxy object from the DropTargetContextPeer's Transferable.

The TransferableProxy is returned to the DropTargetListener when the DropTargetListener asks the DropTargetDropEvent for the Transferable. (The Event forwards the Listener's getTransferable message to its DropTargetContext.)

Conclusion

Java 2 has given us the long-awaited ability to transfer data using a drag and drop metaphor available on many platforms. Each platform has its own native APIs for drag and drop. Fortunately, these platform dependencies are well hidden. However, Java 2 introduces a different layer of complexity by the sheer number of drag and drop classes and interfaces and their sometimes rather circuitous message-passing methods. Hopefully, this article has taken the mystery out of the use of the Java Drag and Drop API.

We've seen how to create components that can act as drag sources and drop sites to transfer text data in different flavors.

Unfortunately there are still a few bugs -- some in supporting packages such as java.io and java.awt.datatransfer. Our examples have shown how to work around these deficiencies.

In a future article, we 'll discuss D&D with applets, custom DataFlavors including transfer of image data, transfer of filenames from the native system (e.g., dropping files from the Windows Explorer into a Java program), autoscrolling, and the details of using other Swing components such as JTable, JList and JTree for D&D.

For an explanation of drag and drop terms used in this article, see the D&D Glossary.

Gene De Lisa is a senior consultant, instructor, and curriculum developer for Rockhopper Technologies Inc. He has been developing and teaching Java since the fall of 1995. Gene is currently teaching and developing using EJB and Swing. Since 1986, his activities have centered around OOAD, first using C++ and Objective-C, and now Java. He'll be delivering a session on drag and drop at JavaOne '99. He's also an amateur spheniscologist. Watch him turn into his favorite spheniscid species here.

Learn more about this topic

  • Link to the official javadocs
  • Previous JavaWorld features on the data transfer API
  • Additional data transfer resources

Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more