Document 195464

http://www.javaworld.com/javaworld/jw-08-1999/jw-08-draganddrop.html http://www.javaworld.com/cgi-bin/mailto/x_java.cgi?pagetosend=/export...
1 of 14
Sponsored by:
This story appeared on JavaWorld at
http://www.javaworld.com/javaworld/jw-08-1999/jw-08-draganddrop.html
How to drag and drop with Java 2, Part 2
Create a library of drag and drop-enabled Swing components
By Gene De Lisa, JavaWorld.com, 08/20/99
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
3/12/2012 4:51 PM
http://www.javaworld.com/javaworld/jw-08-1999/jw-08-draganddrop.html http://www.javaworld.com/cgi-bin/mailto/x_java.cgi?pagetosend=/export...
2 of 14
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
3/12/2012 4:51 PM
http://www.javaworld.com/javaworld/jw-08-1999/jw-08-draganddrop.html http://www.javaworld.com/cgi-bin/mailto/x_java.cgi?pagetosend=/export...
3 of 14
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.
3/12/2012 4:51 PM
http://www.javaworld.com/javaworld/jw-08-1999/jw-08-draganddrop.html http://www.javaworld.com/cgi-bin/mailto/x_java.cgi?pagetosend=/export...
4 of 14
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.
3/12/2012 4:51 PM
http://www.javaworld.com/javaworld/jw-08-1999/jw-08-draganddrop.html http://www.javaworld.com/cgi-bin/mailto/x_java.cgi?pagetosend=/export...
5 of 14
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.
3/12/2012 4:51 PM
http://www.javaworld.com/javaworld/jw-08-1999/jw-08-draganddrop.html http://www.javaworld.com/cgi-bin/mailto/x_java.cgi?pagetosend=/export...
6 of 14
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.
3/12/2012 4:51 PM
http://www.javaworld.com/javaworld/jw-08-1999/jw-08-draganddrop.html http://www.javaworld.com/cgi-bin/mailto/x_java.cgi?pagetosend=/export...
7 of 14
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
3/12/2012 4:51 PM
http://www.javaworld.com/javaworld/jw-08-1999/jw-08-draganddrop.html http://www.javaworld.com/cgi-bin/mailto/x_java.cgi?pagetosend=/export...
8 of 14
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);
3/12/2012 4:51 PM
http://www.javaworld.com/javaworld/jw-08-1999/jw-08-draganddrop.html http://www.javaworld.com/cgi-bin/mailto/x_java.cgi?pagetosend=/export...
9 of 14
}
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
3/12/2012 4:51 PM
http://www.javaworld.com/javaworld/jw-08-1999/jw-08-draganddrop.html http://www.javaworld.com/cgi-bin/mailto/x_java.cgi?pagetosend=/export...
10 of 14
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
3/12/2012 4:51 PM
http://www.javaworld.com/javaworld/jw-08-1999/jw-08-draganddrop.html http://www.javaworld.com/cgi-bin/mailto/x_java.cgi?pagetosend=/export...
11 of 14
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
3/12/2012 4:51 PM
http://www.javaworld.com/javaworld/jw-08-1999/jw-08-draganddrop.html http://www.javaworld.com/cgi-bin/mailto/x_java.cgi?pagetosend=/export...
12 of 14
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
permission
permission
permission
permission
};
java.awt.AWTPermission
java.awt.AWTPermission
java.awt.AWTPermission
java.awt.AWTPermission
java.awt.AWTPermission
"accessEventQueue";
"setDropTarget";
"accessClipboard";
"acceptDropBetweenAccessControllerContexts";
"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();
}
3/12/2012 4:51 PM
http://www.javaworld.com/javaworld/jw-08-1999/jw-08-draganddrop.html http://www.javaworld.com/cgi-bin/mailto/x_java.cgi?pagetosend=/export...
13 of 14
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
3/12/2012 4:51 PM
http://www.javaworld.com/javaworld/jw-08-1999/jw-08-draganddrop.html http://www.javaworld.com/cgi-bin/mailto/x_java.cgi?pagetosend=/export...
14 of 14
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.
About the author
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.)
All contents copyright 1995-2012 Java World, Inc. http://www.javaworld.com
3/12/2012 4:51 PM