Drag
and Drop with Swing
Part 1: Drag and Drop Fundamentals
By David Geary
David Geary is the author of Graphic Java: Mastering the JFC.
Volumes 1 and 2 cover the AWT
and Swing,
respectively. David is currently working on the third volume,
Advanced
Swing.
This article focuses on drag and
drop fundamentals. It is Part 1 of a two-part article on drag and
drop with Swing. The second article will discuss Swing-specific
considerations, including drag and drop between Swing components
and native applications.
Drag and Drop
Fundamentals
Drag and drop involves a drag source and one or more drop
targets, each of which is associated with a component. Dragging
is initiated by a drag gesture in a drag source component.
The dragged item can subsequently be dropped on a drop target
component.
Several objects, listed in
Table 1, are involved in drag and drop operations. All of
the classes listed in the table are from the
java.awt.dnd package, with the exception of Transferable,
which is from the java.awt.datatransfer
package.
Table
1
Drag and Drop Participants.
Class/Interface
|
Description
|
DragGestureRecognizer
|
Fires
events in response to drag gestures in a component
|
DragSource
|
Initiates
drags and creates drag gesture recognizers
|
DropTarget
|
Drop
takes place in a drop target's associated component
|
Transferable
|
A wrapper
for data that is transferred via drag and drop
|
DragGestureListener
|
Notified
by recognizer of drag gesture; typically initiates drag
|
DragSourceListener
|
Responds
to events in a drag source
|
DropTargetListener
|
Handles
drop target events including the drop itself
|
Drag and drop is initiated with
a gesture, which is almost always a mouse down followed
by mouse drags. Drag gesture recognizers fire events
when drag gestures are detected in an associated component.
Drag sources
initiate drags with the startDrag()
method and create drag gesture recognizers with either createDragGestureRecognizer()or
createDefaultDragGestureRecognizer()
.
Drop targets
are associated with a component and a drop target listener.
When drop target events (including the drop) occur in the component,
the listener is notified.
Transferables
wrap data that is transferred from a drag source to a drop target.
The initiator of a drag wraps data in a transferable, and drops
are handled by accessing a transferable's data.
Drag gesture
listeners are notified of drag gestures by a recognizer.
The typical response is to initiate a drag by invoking DragSource.startDrag().
Drag source
listeners react to events that occur in a drag source after
a drag is initiated.
Drop target
listeners are responsible for handling drop target events
including the actual drop itself.
On the surface,
drag and drop can be intimidating. Drag sources, drop targets,
drag gesture recognizers, and transferables must be created
and a cast of listeners implemented for even the simplest drag
and drop operation.
In practice,
however, drag and drop is straightforward. Other than wrapping
data in a transferable and handling the drop, most of the action
takes place without programmer intervention.
Adding Drag and Drop to Swing Components
Adding drag
and drop capabilities to Swing components typically involves
one of the following options:
-
Extend a Swing component class, such as JList
or JTree, to
act as a drag source component, a drop target component, or
both.
-
Have a third party associate a component with a drag source,
drop target, or both.
Both options
involve associating components with drag sources and drop targets.
The difference stems from who does the associating. For Option
1, it's a subclass of a Swing component. For Option 2, it's
an object other than the component, the drag source, or the
drop target.
Notice that
extending a Swing component -- Option 1 -- adds drag and drop
functionality only to instances of the component extension;
functionality cannot be added to standard components. For example,
drag and drop functionality in a ListThatsADragSource
extension of JList
is only available to instances of ListThatsADragSource
and cannot be used with instances of JList
. Therefore, the use of Option 1 should be limited to custom
components with inherent drag and drop capabilities that are
not easily generalized.
The application
shown in Figure 1 exercises
both options--the drag source is implemented as an extension
of the JTree
class, and an instance of JTextPane
is associated with a drop target by the application. Filenames
dragged from the tree and dropped in the textpane are displayed
as text files. The top picture in Figure
1 shows the initiation of a drag, and the bottom picture
shows the result of a subsequent drop in the text pane.
Figure
1
Dragging from a JTree into a JTextPane.
The Drag Source
Example 1 is a truncated listing of the application's
drag source. The DragTree
class extends JTree
and implements the DragGestureListener
and DragSourceListener
interfaces.
Example
1
A Drag Source
class DragTree extends JTree
implements DragGestureListener,
DragSourceListener {
public DragTree() {
DragSource dragSource = DragSource.getDefaultDragSource();
// creating the recognizer is all that’s necessary - it
// does not need to be manipulated after creation
dragSource.createDefaultDragGestureRecognizer(
this, // component where drag originates
DnDConstants.ACTION_COPY_OR_MOVE, // actions
this); // drag gesture listener
...
}
public void dragGestureRecognized(DragGestureEvent e) {
// drag anything ...
e.startDrag(DragSource.DefaultCopyDrop, // cursor
new StringSelection(getFilename()), // transferable
this); // drag source listener
}
public void dragDropEnd(DragSourceDropEvent e) {}
public void dragEnter(DragSourceDragEvent e) {}
public void dragExit(DragSourceEvent e) {}
public void dragOver(DragSourceDragEvent e) {}
public void dropActionChanged(DragSourceDragEvent e) {}
...
}
When the tree is constructed, a reference
to a default drag source is obtained with the static DragSource.getDefaultDragSource()
method, and the drag source is used to create a drag
gesture recognizer. The recognizer, when constructed,
is associated with a component (the tree) and a drag gesture
listener (also the tree). It is not necessary to directly
manipulate the recognizer after its creation, and therefore
the DragTree
class does not maintain a reference to it.
Like
most drag gesture listeners, the tree reacts to drag gestures
by initiating a drag with the DragSource.startDrag()
method. The startDrag()
method is passed a filename wrapped in an instance of
the StringSelection
class. java.awt.datatransfer.StringSelection
is an implementation of the Transferable
interface for transferring strings.
The
group of empty methods implemented by the DragTree
class are defined by the DragSourceListener
interface. DragSource.startDrag()
must be passed a non-null
reference to a DragSourceListener,
so the tree itself acts as a bug-free but thoroughly boring
drag source listener.
Click
the Source Code button for the complete DragTree.java
listing.
The Drop Target
The drop
target for the application shown in Figure
1 is handled by the Test
class, which is listed in
Example 2.
Example
2
A Drop Target
public class Test extends JFrame
implements DropTargetListener {
private JTextPane textPane = new JTextPane();
public Test() {
...
new DropTarget(textPane, // component
DnDConstants.ACTION_COPY_OR_MOVE, // actions
this); // DropTargetListener
...
}
...
public void drop(DropTargetDropEvent e) {
try {
DataFlavor stringFlavor = DataFlavor.stringFlavor;
Transferable tr = e.getTransferable();
if(e.isDataFlavorSupported(stringFlavor)) {
String filename =
(String)tr.getTransferData(stringFlavor);
e.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
readFile(filename);
textPane.setCaretPosition(0);
e.dropComplete(true);
}
else {
e.rejectDrop();
}
}
catch(IOException ioe) {
ioe.printStackTrace();
}
catch(UnsupportedFlavorException ufe) {
ufe.printStackTrace();
}
}
public void dragEnter(DropTargetDragEvent e) { }
public void dragExit(DropTargetEvent e) { }
public void dragOver(DropTargetDragEvent e) { }
public void dropActionChanged(DropTargetDragEvent e) { }
}
The
Test constructor
creates a drop target, specifying the text pane as the
drop target component and the application as the drop
target listener; thus, Test.drop()
is invoked when a drop occurs in the text pane. The drop
target, like the DragTree's
drag gesture recognizer, is instantiated but not used.
The
drop is accepted if the transferable associated with the
drop can supply its data as a string. After accepting
the drop, the application's readFile()
method is invoked, which loads the contents of the file
into the text pane. The caret position for the text pane
is set to 0, ensuring that the first line of text is displayed
at the top of the text pane. After the drop is complete,
e.dropComplete(true)
is invoked.
The
rest of the methods defined by the DropTargetListener
interface are implemented as empty methods.
Click
the Source Code button for the complete Test.java
listing.
Selective Dragging
Sometimes
it is desirable to restrain the types of objects that can
be dragged from a drag source. For example, the tree in
Figure 1 allows any type of file -- including executable
files -- to be dropped in the text pane, which displays files
as text.
Figure 2 shows a modification of the application shown
in Figure 1 that restrains
dragging to text files and Java source files. The picture
in Figure 2 shows the
result of an illegal drag attempt.
Figure 2
Restricting dragging to text files.
The modified
drag source is listed in Example 3.
Example
3
Restricting Draggable Objects
class DragTree extends JTree implements DragGestureListener,
DragSourceListener {
...
public void dragGestureRecognized(DragGestureEvent e) {
String s = getFilename();
if(s.endsWith(".txt") || s.endsWith(".java")) {
e.startDrag(DragSource.DefaultCopyDrop, // cursor
new StringSelection(s), // transferable
this); // drag source listener
}
else {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JOptionPane.showMessageDialog(
SwingUtilities.getRootPane(DragTree.this),
"Only \".txt\" and \".java\"
files " +
"can be dragged",
"Not Draggable",
JOptionPane.ERROR_MESSAGE);
}
});
}
}
...
}
The
tree's dragGestureRecognized()
method starts a drag only if the selected file ends in
".txt" or ".java".
If an
attempt is made to drag a file that does not end in ".txt"
or ".java", an error dialog is displayed.
A bug prevents the dialog from being shown directly in
the drop()
method, so SwingUtilities.invokeLater()
is used to show the dialog on the next leg of the event
dispatch thread.
A Drag and Drop Recipe
Using
drag and drop is relatively simple, as illustrated by the example
discussed in "Adding Drag and Drop
to Swing Components." Perhaps the most difficult aspect
of drag and drop is remembering all of the steps involved; because
of this difficulty, we will conclude with a recipe for drag
and drop:
-
Obtain a reference to a drag source with DragSource.getDefaultDragSource()
or by instantiating a new one -- for example, new
DragSource() .
-
Create a drag gesture recognizer with the drag source from
step 1 by invoking DragSource.createDefaultDragGestureRecognizer().
The method is passed the component where the drag originates.
-
Create a drop target, specifying a component and a drop target
listener.
-
Wrap the data to be dragged in a transferable.
-
Initiate a drag when the drag gesture recognizer from step
2 is notified by invoking DragSource.startDrag()
for the drag source from step 1. The transferable from step
4 is passed to the startDrag()
method.
-
Handle the drop by implementing the DropTargetListener
interface.
-
Implement the DragSource
interface (often with empty methods).
|