Here is a demo of usage of the Drag & Drop Library, which provides D&D support on top of Java's basic D&D in much the way Xt provided toolkit functionality on top of X11.
Here is the code to add drag handling to a JLabel:
final JLabel label = new JLabel("Drag Me");
label.setBorder(BorderFactory.createEmptyBorder(4,4,4,4));
new DragHandler(label, DnDConstants.ACTION_COPY) {
protected Transferable getTransferable(DragGestureEvent e) {
return new StringSelection(label.getText());
}
protected Icon getDragIcon(DragGestureEvent e, Point offset) {
return new ComponentIcon(label, true);
}
};
The only thing absolutely required by the API is
getTransferable
. However, overloading
getDragIcon
will automatically give you a nice ghosted drag image, regardless of whether your platform supports one natively (the
ComponentIcon
used to render the
JLabel
is just a simple class which renders its given component as an icon).
Drag and Drop has been in Java since version 1.2 when a functional but very complex set of interfaces was introduced. Compare the previous
DragHandler
interface for dragging with the following interfaces, which must be implemented carefully to get consistent results (note also the conspicuous absence of any methods which explicitly indicate the data to be dragged):
// DragGestureListener
void dragGestureRecognized(DragGestureEvent);
// DragSourceListener
void dragEnter(DragSourceDragEvent);
void dragOver(DragSourceDragEvent);
void dropActionChanged(DragSourceDragEvent);
void dragExit(DragSourceEvent);
void dragDropEnd(DragSourceDropEvent);
//DragSourceMotionListener
void dragMouseMoved(DragSourceDragEvent);
Not a trivial thing to get right, especially without an in-depth understanding of how the system works. Not something I'd expect of every developer. Unfortunately, there is no abstract implementation to provide a base level of functionality or useful example (I'm sure everyone has at least looked at
Rockhopper or
JavaWorld for functional examples). These examples provide working implementations, but not one that makes extension or reuse clear and simple. Sun has tried to make D&D simpler by introducing the TransferHandler and setting up default drag and drop handlers on Swing components. I consider the TransferHandler a mistaken attempt to merge two operations that just happened to have a little functionality in common. Copy and drag both need to produce a
Transferable
, and Paste and drop both need to absorb a
Transferable
, but the edit actions operate in a sufficiently different manner that merging the two doesn't really buy you much in practice.
Copy/paste actions are fairly trivial to construct in the first place, and the clipboard transfer is a few lines of boilerplate.
Drag and drop, on the other hand, is a
lot more lines of boilerplate, some of which can introduce subtle differences in behavior if you don't get it just right.
Back to the library. An example on a JTree is a bit more involved, but only because you now need to choose which part of the JTree is going to be dragged. For illustrative purposes, I've disabled dragging of non-leaf nodes, and enabled dragging of the entire tree if you drag outside of any rows. I futz with the ComponentIcon to draw either the whole tree or just one row, as needed. You could conceivably construct a drag from a multiple selection by painting the whole tree but adding an appropriate clipping mask when painting the icon.
final JTree tree = new JTree();
// Turn off selection of rows by dragging
tree.setDragEnabled(true);
// Turn off built-in swing drag handling
tree.setTransferHandler(null);
new DragHandler(tree, DnDConstants.ACTION_COPY) {
protected boolean canDrag(DragGestureEvent e) {
Point where = e.getDragOrigin();
int row = tree.getRowForLocation(where.x, where.y);
if (row != -1) {
TreePath path = tree.getPathForRow(row);
return tree.getModel().isLeaf(path.getLastPathComponent());
}
return true;
}
protected Transferable getTransferable(DragGestureEvent e) {
Point where = e.getDragOrigin();
final int row = tree.getRowForLocation(where.x, where.y);
if (row == -1) {
return new StringSelection("full tree");
}
Object value = tree.getPathForRow(row).getLastPathComponent();
return new StringSelection(String.valueOf(value));
}
protected Icon getDragIcon(DragGestureEvent e, Point offset) {
Point where = e.getDragOrigin();
final int row = tree.getRowForLocation(where.x, where.y);
if (row != -1) {
Rectangle r = tree.getRowBounds(row);
offset.setLocation(r.x, r.y);
}
return new ComponentIcon(tree, true) {
public void paintIcon(Component c, Graphics g, int x, int y) {
g = g.create();
if (row != -1) {
Rectangle r = tree.getRowBounds(row);
g.translate(-r.x, -r.y);
g.setClip(new Rectangle(x+r.x, y+r.y, r.width, r.height));
super.paintIcon(c, g, x, y);
}
else {
super.paintIcon(c, g, x, y);
}
g.dispose();
}
};
}
};
Again, this implementation doesn't have to worry about any of the mechanics of drag and drop. The implementation decides whether an item can be dragged, what the dragged item looks like, and what is the appropriate
Transferable
. This API hides all the boilerplate, but you can still get access to the basic Java D&D API if you need to, if for some reason you need to augment or override
dragOver
or
dragGestureRecognized
.
Next installment will demonstrate the
DropHandler
, which facilitates decorating your drop target as well as accepting or rejecting incoming data.
Source.