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 2 3 Page
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more