How to drag and drop with Java 2, Part 2

Create a library of drag and drop-enabled Swing components

How would you like to drag and drop almost any kind of data using almost any project Swing component? The first article in this series, "How to drag and drop with Java 2" (see Resources), introduced data transfer using familiar drag and drop (D&D) gestures. The first article's examples were limited to a single project Swing component -- a D&D-enabled JLabel. We transferred text data between labels in the same Java virtual machine (JVM), to other JVMs, and to the native system.

This article will show you how to create D&D-enabled versions of many more Swing components. We'll also learn how to transfer various data types beyond plain text.

Custom Transferable classes

In the first article in this series, we discussed how to transfer text. It is, however, possible to drag and drop other data types, such as images and GUI components. In order to transfer other kinds of data, we must create at least one DataFlavor class and a Transferable class.

Previously, we created a StringTransferable class that provided text data in several DataFlavors. StringTransferable encapsulates a java.lang.String object, which it makes available in these flavors. When the user drops the data onto a droppable component, the component requests the data in its own preferred DataFlavor. Depending on the DataFlavor requested, the String is either simply returned or transformed into a byte stream.

An important point to remember is that a java.lang.String is serializable. If the DropTarget resides in the same JVM, it will receive a reference to a live object. If, however, the DropTarget is in a different JVM, it will receive a serialized copy of the String. The D&D system automatically serializes the String in the same way that RMI parameters are marshalled. We can easily transfer any data encapsulated in a serializable object by defining our own Transferable class.

Create a custom Transferable class

In this example, we'll create a Transferable class for our own Rockhopper class. Rockhopper will be serializable, as will its members; if any are not, we can mark them transient and transfer their data manually via the writeObject() method. This Transferable creates two DataFlavors for our class. In both DataFlavors our Rockhopper class is the representation type. The Transferable object simply keeps a reference to the Rockhopper object, which is returned in the getTransferData() method. Remember that, if the DropTarget is in another JVM, the D&D system will automatically serialize the Rockhopper object.

Now, let's look at the code for this example:

public class Rockhopper extends Bird implements Serializable {
   private String name; // String is serializable
}
public class RockhopperTransferable 
   implements Transferable,
   ClipboardOwner {
    public static DataFlavor rockhopperFlavor=null;
    public static DataFlavor localRockhopperFlavor=null;
    static {
     try {
         rockhopperFlavor = new
         DataFlavor(com.rockhoppertech.Rockhopper.class,
                      "Non local Rockhopper");
         localRockhopperFlavor = new
          DataFlavor(DataFlavor.javaJVMLocalObjectMimeType +
                  "; class=com.rockhoppertech.Rockhopper",
                  "Local Rockhopper");
     } catch(Exception e) {
         System.err.println(e);
     }
    }
   private Rockhopper penguin;
   public RockhopperTransferable(Rockhopper rocky) {
      this.penguin = rocky;
   }
   public Object getTransferData(...) {
      // check to see if the requested flavor matches
   ... etc ...
      return this.penguin;  // easy!
   }
etc.

You may want to create a library of Transferable classes for commonly used data types. Popular custom Transferable choices include java.awt.Color, java.util.Date, java.awt.Font, and java.io.File.

Create an Image Transferable class

Creating an Image Transferable class is a bit tricky. The problem is that java.awt.Image isn't serializable, so it can't be used as the representation type for a DataFlavor class. Luckily, javax.swing.ImageIcon is serializable. The ImageIcon maintains a reference to an Image, but it's a transient member. To actually transfer the Image data, the ImageIcon class defines the readObject() and writeObject() methods for manual transfer.

The capability to transfer image data to the native system is more complicated. For native transfers, we need to provide the data as a stream of bytes via an InputStream subclass. For our StringTransferable class, we employed a ByteArrayInputStream object that used the String's bytes in the proper charset.

Similarly, we can create a JPEGTransferable class that provides the image data in JPEG format. The DataFlavor used by the Transferable can specify image/jpeg and its MIME type. The getTransferData() method can provide the data either via the com.sun.image.codec.jpeg classes or the Java Advanced Imaging API. The latter also provides other formats, such as GIF.

Our JPEGTransferable class will work as long as we have a native program that accepts data with an image/jpeg MIME type. On Windows systems, however, these programs are rare. Most Windows programs accept only Win32 clipboard formats such as DIB (device independent bitmap) or ENHMETAFILE (enhanced metafile). Therefore, for Windows environments you'll need to create InputStream classes that provide the data in one of these formats. Moreover, you'll need to use a special FlavorMap instance.

The FlavorMap class provides mapping between MIME types used in Java and native clipboard types such as DIB or HDROP. The D&D system creates the default FlavorMap from $(JDK)/jre/lib/flavormap.properties. For our custom native InputStreams to be used, we need to add an entry to flavormap.properties. Alternatively, we could create a FlavorMap instance for our listeners.

Here's a custom entry in flavormap.properties:

DIB=image/x-win-bmp;class=com.rockhoppertech.dnd
.datatransfer.DIBInputStream

Build a D&D library

If you need a single D&D-enabled component, you can create a subclass that defines DragGestureListener, DragSourceListener, and DropTargetListener as inner classes. This was the technique used in the first article. If you need a number of D&D-enabled components, you will write very similar code for each component's listeners.

Rather than using inner classes for the listeners, in this article we will use reusable adapters. This makes more sense for a D&D-enabled component library; it should be kept in mind, however, that it is also a bit more difficult to create adapters that will adapt to all possible uses.

The main classes and interfaces in our D&D library include:

  • DragComponent
  • DragSourceAdapter
  • DragGestureAdapter
  • DropComponent
  • DropTargetAdapter

A D&D-enabled component would create associations with an instance of each of these classes, as shown in Figure 1.

Figure 1. UML diagram of D&D library classes

The Drag classes

A drag-enabled component implements the DragComponent interface. It creates an instance of DragGestureAdapter and an instance of DragSourceAdapter.

The DragSourceAdapter implements the DragSourceListener interface and maintains a reference to a DragComponent object. When a drag is initiated, the DragSourceAdapter queries the DragComponent for the acceptable drag operation and an appropriate Transferable object. If this is a move operation, the DragSourceAdapter will tell the DragComponent to move the data. The move operation actually adds the data to the destination, then removes the data from the source at the end of the D&D operation. We'll discuss this more when we come to the JList and JTable examples. The drag-over effects are the DragSourceAdapter's responsibility. These are usually cursor changes. The DragComponent may register custom cursors with the DragSourceAdapter object.

The DragComponent uses a DragGestureAdapter object, which implements the DragGestureListener interface, in order to initiate the drag operation. With components such as a JTree, it is possible that not all nodes can be dragged. The DragGestureAdapter verifies the drag with the DragComponent's isStartDragOk() method, and it registers the DragComponent's DragSourceAdapter.

Figure 2. DragComponent message trace

The Drop classes

A drop-enabled component implements the DropComponent interface. It creates and maintains a reference to a DropTargetAdapter.

Figure 3. DropComponent message trace

Transferable models

For some components, we might not want to create an individual subclass for each type of data the component might represent. It would be better to create a transferable model for these components. When the drag operation starts, the component gets the Transferable from the model. This strategy works well for the JTable and JList.

JList

It is convenient to place all of the common D&D methods in an abstract subclass of JList. This abstract DnDList takes care of creating and registering the appropriate adapters, drag-under and drag-over feedback, autoscrolling, moving, and event/flavor validation. The concrete subclass specifies the acceptable operations and flavors, and defines an add() method that is called when the DropTargetAdapter receives the drop message.

We can define a TransferableListModel interface with method public Transferable getTransferable(int index). For convenience, we can define an AbstractDnDList model that has data structure maintenance code but requires that subclasses implement the getTransferable() method. This concrete subclass determines the exact type of Transferable returned. For example, a DnDTextList concrete subclass of DnDList would create a DnDTextListModel concrete subclass of AbstractDnDList; the latter in turn creates and returns a StringTransferable object from getTransferable.

Figure 4. D&D list model

JTable

The JTable follows the same pattern as the JList. We place the common D&D code in an abstract class that uses an abstract transferable model. The concrete table subclasses create a concrete transferable model.

Figure 5. D&D table model

JTree

It isn't necessary to create a Transferable model for the JTree. The tree nodes may contain a user object. If this object is serializable, you can define a TreeTransferable class with a DataFlavor that uses a DefaultMutableTreeNode as its representation class.

What does it mean to copy a tree node? Do you want to copy just the node, or all of its children? We will address these issues below, when we talk about the JTree in more detail.

Issues for drag and drop components

When you create a D&D-enabled component, there are several issues you need to address:

  • Starting the drag operation
  • Drag-under feedback
  • Drag-over feedback
  • The drop
  • Transferable
  • The move operation

I will address each of these issues for three complex components -- the JList, the JTable, and the JTree -- with the help of our D&D library classes. First however, let's examine these issues in a general sense.

Starting the drag operation

Is it OK to start dragging at the current pointer location? Perhaps the component is a JTree and the selected tree node cannot be dragged. The DragGestureAdapter object calls the isStartDragOk() method of our DragComponent object. If the return value is false, the drag simply will not start.

Drag-under feedback

How do we show that a drop is valid? If, for example, the component is a JList, where will the drop occur? To show the user where the data will be inserted, define the dragUnderFeedback() and undoDragUnderFeedback() methods from our DropComponent interface.

Drag-over feedback

Most of the time, the default drag-over feedback is fine. However, there could be a situation in which you would like to use custom cursors. The DropTargetAdapter calls the DropComponent's isDragOk() method repeatedly during the drag. The return value sets the drag-over feedback. For most components the initial cursor should be a no-drop cursor, since dropping the data in the same place as its origin is useless.

The drop

If you accept the dropped data, you insert it someplace in the component. Where is it going to be inserted? What flavors are accepted?

Transferable

What Transferable/DataFlavor class should you use? When a drag starts, how do you get the Transferable object? The Transferable object may encapsulate data associated with the component or data retrieved from its model. Our DragGestureAdapter calls the DragComponent's getTransferable() method.

The move operation

Because it's a two-step process, the move operation actually removes the data. The D&D system adds the data to the destination and then removes it from the source. If it's a complex component, such as a JList, which item is removed? The DragSourceAdapter calls the DragComponent's move() method.

JList

Now, let's look at possible solutions for each of the issues we've just raised in the context of a D&D-enabled JList.

Starting the drag

Remember that, with a list and a table, you get the Transferable from the model. Since our drag-under feedback selects the item that is under the pointer, we can get the Transferable from the model at the selected index. We must remember this index, since a move operation will remove the item from the model.

Drag-under feedback

Selecting the item under the pointer provides intuitive drag-under feedback. The pointer location can be retrieved from the DropTargetDragEvent object by its getLocation() method. This location can be turned into an index with the JList locationToIndex() method. If the returned index is -1, we need to clear the selection, since the pointer isn't over any item. Otherwise, we select the item at the returned index. If the drop cannot take place at the selected index, we can change the background color to red and reset it to the default selection color when the drop is OK. To undo the drag-under feedback, we can simply clear the selection.

Drag-over feedback

With the isDragOk() method, we can determine if the pointer is currently over an item or not. If the value returned from the locationToIndex() method is -1, the return value should be false. The adapter will then set the no-drop cursor. One special case is that if the model is empty, the locationToIndex() method will always return -1, since there are no items over which the pointer can be. Therefore, we should check to see if the model size is zero and return true if it is.

The drop

The combination of our drag-over and drag-under feedback ensures that there will be an item selected when the drop occurs. Should we insert the data before or after the item selected by our drag-under feedback?

One way to determine this is to make it a user preference. We need to remember which index we insert the data into, since it will be used in the move() method.

The Transferable

Since the JList may contain many different kinds of data, we rely on the TransferableListModel to provide the Transferable. The JList only needs code to find the selected index and to get the Transferable from the model at that index. Here, too, we must remember the selected index, since it will be needed in the case of a move operation.

Here's the code:

this.fromIndex = this.getSelectedIndex();
return this.model.getTransferable(this.fromIndex);

The move operation

We need to remember both the index of the item used to create the Transferable and the index where the item was inserted. If the same JList is both the source and destination, we can't simply remove the item at that index. The dropped data is added before the move operation. If the dropped data is inserted at an index that is less than the original index, it will be off by one.

If the move operation isn't local, we can simply remove the element at that index.

Let's look at this in detail:

public void move() {
     if(this.local) {
         if(this.insertIndex < this.fromIndex)
          this.fromIndex++;
     }
     this.model.remove(this.fromIndex);
}

JTree

For each of the D&D issues, we will now discuss possible solutions for a D&D-enabled JTree.

Starting the drag

There's no easy way to get the node at the current pointer location. To do so, you need to get the TreePath at the location and get the last path component from it. If the user is currently editing a node, stop the edit before starting the drag.

Now, let's examine how this works:

protected DefaultMutableTreeNode getTreeNode(Point p) {
     TreePath path = this.getPathForLocation(p.x, p.y);
     if(path != null) {
         return (DefaultMutableTreeNode)path.getLastPathComponent();
     } else
         return null;
    }
}

Drag-under feedback

You might want to expand the node under the pointer and select the node that the pointer is over. Display invalid feedback if this node doesn't accept children.

Drag-over feedback

You can check to see if the node under the pointer allows children. If not, return false from the isDragOk() method to show the no-drop cursor.

The drop

If the drop is local and the operation is to copy, you probably want to create a copy of the node rather than simply insert the node. Otherwise, when either of the nodes (the original or the copy) is modified, the other will be as well. You might also want to make a deep copy of the dropped node. The default clone method makes a shallow copy. You need to write your own deep-copy code. You can do this with the getChildCount() and getChildAt() methods.

The Transferable

Create a TreeTransferable object from the node under the current pointer location. Then save a reference to this node.

The move operation

Remove the node that was used to create the Transferable object with method removeNodeFromParent().

JTable

The JTable has many of the same issues as the JList.

The table is a view of the data in its model. If each table cell represents an object in the model, you may want to create a table in which the user can drag and drop individual cells. If the table maps each object to a row and the table columns to the object's attributes, you might want the user to be able to drag and drop a table row.

One frequent question that comes up in such situations is how to drop onto an empty table or a table with empty cells. For example, the table could represent the innings in a baseball game. If the game is in progress, the table's later innings would be empty. (This is more of a model question than a D&D issue.) One way to solve the problem is for the game model to start with nine default inning objects. At the end of each inning the user can drop the new inning onto the table to replace the default one. A more efficient answer for a situation with a large number of cells is to create a model that uses a sparse collection.

Starting the drag

In addition to verifying that the data under the pointer is draggable, you need to check to see if an edit is in progress. If so, stop the edit with the editingStopped() method.

Drag-under feedback

You can select the cell or row under the pointer for drag-under feedback. To accomplish this, use methods columnAtPoint() and/or rowAtPoint() to determine which row or cell the pointer is over.

Drag-over feedback

You may want to check the row and column at the current point and veto some cells. If you save the row and column that you used to create the Transferable object, you can prevent dragging and dropping on the same cell. You can also retrieve the row and column counts from the model. If the calculated row or column is greater than the model's row or column count, you may want to veto the drop.

The drop

Does the drop replace the selected row or cell, or does it insert a new row or cell? If it isn't a local drop, it is possible that an edit is in progress. Stop the editing, then either use the model.setValueAt(row, col) method to replace the selection or create a method in the model that allows insertion before or after the selection.

Transferable

Retrieve the Transferable object from the model. Use the selected row and/or column to allow the model to determine how to make the Transferable. If the table drags and drops rows, the selected column is ignored.

The move operation

Save the row and column of the node used to create the Transferable. Use removeNodeFromParent() to remove the node.

Composite components

Components such as the JFileChooser and JColorChooser are composed of several other components. In this section, we discuss the issues involved with adding D&D capability to these components

JFileChooser and GlassPanes

The JFileChooser contains JComboBoxes, a JList, JButtons, and a JTextField. If you create a JFileChooser subclass and use either inner class listeners or adapters, the D&D operation may occur only where there are no children visible. This situation will not be intuitive for the user.

As a solution, we create a special glass pane that implements the drag and/or drop listeners. When visible, the glass pane would need to forward mouse events to the child components. For the JFileChooser, the glass pane can be created and installed in the showDialog() method. The DataFlavors that this glass pane should accept include DataFlavor.javaFileListFlavor. For a drag operation, the JFileChooser's selected file is used to create an instance of our custom FileTransferable class.

Now, let's take a look in detail:

     LinkedList list = new LinkedList();
     list.add( this.filechooser.getSelectedFile() );
     return new FileTransferable( list );

To retarget mouse events, you can get a head start by borrowing parts of the GlassPaneDispatcher inner class from the BasicInternalFrameUI class. However, the GlassPaneDispatcher must be modified to work with JPopupMenus. The JComboBox children of the JFileChooser would not work otherwise, since combination boxes use a JPopupMenu. Moreover, menus in a menu bar won't work either. We need to make the glass pane invisible when a popup is visible in order to make popups work correctly. The MenuSelectionManager class can help to determine if a popup is visible:

MenuSelectionManager msm =
MenuSelectionManager.defaultManager();
MenuElement[] p = msm.getSelectedPath();

If the selected path is greater than zero and the mouse event is a press (the instant in time when the user depresses the mouse button, but has not yet released it) or a click (a press followed by a release), you need to set the glass pane as invisible. The selected path can search for an instance of a JPopupMenu. If one is found, a PopupMenuListener can be installed that will make the glass pane visible again when the popup becomes invisible.

JColorChooser

The glass pane technique doesn't work well with a JColorChooser component. The BasicColorChooserUI object uses a JTabbedPane, which doesn't handle mouse events correctly on tabs beyond the first one. The JTabbedPane may be modified to work correctly, but that doesn't help with the JColorChooser unless a new UI is installed. Also, since the glass pane forwards events to the children, the JSlider children would interfere with the drag gesture.

The current solution is to write a new UI delegate with corrected JTabbedPane and D&D-enabled components. You can install your D&D-enabled versions of the SwatchChooserPanel, HSBChooserPanel, and RGBChooserPanels classes either by your own ColorComponentChooserFactory class or by the ColorChooser's setChooserPanels() method. It would be convenient if you were able to set the ChooserFactory, but that isn't currently possible. This JTabbedPane bug (actually it's a java.awt.Component bug) is in the list of outstanding bugs and will be fixed in a future release of the JDK.

InternalFrame

A common problem with the JInternalFrame is that D&D seems to work at first, but breaks down when an internal frame is selected. The mystery is that when an internal frame is not selected, its glass pane becomes visible and catches events. This bug results from the user's need to be able to select an inactive frame by pressing the mouse anywhere on the internal frame. This bug was supposedly fixed in the JDK 1.2.2 release. In this release you were, in fact, able to drop onto the frame's children. However, the fix seems to have introduced a selection-repaint bug where the previous selection isn't completely cleared during a drag.

One solution is to install another special glass pane that is a DropTargetListener on each internal frame. This glass pane will forward mouse events to the internal frame's child components. We can reuse our modified GlassPaneDispatcher for this. The only modification to the dispatcher is to select the glass pane's internal frame on mouse-press events. A complication is that this glass pane cannot dispatch D&D events (as it can MouseEvents) since they are not AWTEvent subclasses.

JApplet

You can use D&D in an applet if your browser supports Java 2 or if you can use the Java 2 plugin. There are two steps to enable D&D in an applet. First, the applet needs permissions granted in a policy file:

grant {
  permission java.awt.AWTPermission "accessEventQueue";
  permission java.awt.AWTPermission "setDropTarget";
  permission java.awt.AWTPermission "accessClipboard";
  permission java.awt.AWTPermission "acceptDropBetweenAccessControllerContexts";
  permission java.awt.AWTPermission "listenToAllAWTEvents";
};

To use this policy (in a file named policy) with appletviewer, use this command:

 
appletviewer -J-Djava.security.policy=policy index.html 

(Policy files have been discussed elsewhere in JavaWorld, see Resources for URLs.)

Second, DropTargets will not work if you create them in either the init() or start() methods. If you do create them in these methods, you will be able to drag, but not drop.

Below is an example of a JApplet that contains D&D-enabled children. The createComponents() method will create these components and add them to the content pane. You will also want to check to see if this is the first time that the start message has been received. Otherwise, you'd be adding many components!

public void  start() {
     // do this only the first time start is called
     Thread kicker = new Thread( this );
     kicker.start();
    }
    public void  run() {
     this.createComponents();
     JPanel p = (JPanel)getContentPane();
     p.revalidate();
    }

If the JApplet itself is a drop site, you'll need to create the DropTarget the same way -- in a separate thread. In addition, you'll need to specify the content pane as the DropTarget's component:

public void  run() {
     this.inidDnD();
     JPanel p = (JPanel)getContentPane();
     p.revalidate();
    }
void initDnD() {
     this.dropTargetAdapter = new DropTargetAdapter( etc...
     this.dropTarget = new DropTarget(this.getContentPane(),
                          DnDConstants.ACTION_COPY_OR_MOVE,
                          this.dropTargetAdapter,
                          true);
    }

Dialogs

Let's say that the user has just dropped a duplicate item onto a DnDList, and you would like to ask if he or she really would like two copies of that item in the list. You cannot show a dialog that requires input during a D&D operation (that is, in one of the listener's callbacks) without the risk of deadlock. You might be able to get away with displaying a message dialog that is shown via SwingUtilies.invokeLater without deadlock. However, if you would like the JOptionPane to show, say, a confirm dialog, you need to use the SwingWorker class. This isn't a standard class in the JDK, but you can download it from the Swing Connection (see Resources).

The second example (Example2.java) of using the SwingWorker class requires only a little modification to work with D&D. You create an instance of a SwingWorker in the add() method that is called when the data is dropped. The waitForUserConfirmation() method can be modified for the dialog of your choice. The doWork() method needs to return a boolean value based on the input from the dialog. This boolean value may be retrieved in the SwingWorker's finished method by the get() method. If the value is true, go ahead and add the item; otherwise, do nothing. If it's a local operation, the user is rearranging items and a duplicate already exists. In that case, you should not show the dialog.

Deficiencies

There are some problems in the JDK that you should be aware of when using D&D. This section will identify them and present known workarounds.

Caret problem

Text components work well as drop sites. You can't drag from them, however, since the drag gesture interferes with the caret.

Clipboard

The sun.awt.windows.WClipboard class currently uses instanceof StringSelection instead of checking the MIME type. A custom text flavor works well with D&D, but not with the clipboard.

Cursor feedback

Setting a custom cursor during dragEnter or dragOver causes it to flicker between the default platform cursor and your custom cursor. One workaround is to set the cursor to null and then set it to your custom cursor.

JTabbedPane

There is a problem with dragging and dropping children of a JTabbedPane. It seems to work only on the first tab. Actually, it will return a child from the first tab even if another tab is selected. The solution is to create a subclass of JTabbbedPane to override the findComponentAt() method inherited from java.awt.Container. You can simply add a check to the existing code to see if the container (current tab) is visible.

CardLayout

D&D doesn't work with the CardLayout manager. There is no known workaround at this time.

Conclusion

Even though there are several problems using D&D with Swing components, we have found solutions or workarounds for many of them. In addition, rather than rewriting the same D&D code over and over again, we have developed a library of adapters and abstract classes that can be used and/or subclassed for each new D&D-enabled component.

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. Since the beginning of 1998 his teaching and development has centered around Enterprise JavaBeans, and he has most recently focused on J2EE. His original drag and drop development was for EJB clients. He recently delivered a technical session on drag and drop at the 1999 JavaOne conference. He is also an amateur spheniscologist. You can see him turn into his favorite spheniscid species in an Applet on his Web page. (He's the one with the tie.)

Learn more about this topic