This is an excerpt from Mastering JavaTM 2,
published by
Sybex
Inc. Written by John Zukowski, a long-time contributor to the JDC,
this book offers in-depth coverage of Java 2, including such topics
as building forms with Swing, creating Java graphics with the 2D API,
implementing drag-and-drop support, providing built-in security with
the Java Cryptography architecture,
integrating your Java program with CORBA, enhancing server-side
performance with servlets, and building fast Java solutions. The book
comes with a CD that contains all the example code and executable files,
along with links to third-party tools to use with the Java 2 platform.
This chapter explores the data transfer capabilities of the Java 2
platform, which includes access to the system clipboard as well as
how to use the drag and drop features.
Note: For an updated drag and drop example that includes
using a custom data flavor as well as dropping a file list, see
Drag and Drop,
Take 3 from the Mining Co.
The java.awt.datatransfer
and java.awt.dnd
packages encompass two very different, yet similar capabilities. These
data transfer capabilities available within Java are to transfer data
to a clipboard as well as to drag and drop within an application or
between applications. The means that both packages used to transfer
data is the same, so it will be explained first.
Transferable Objects
Objects that are transferable to/from an application's clipboard or for
drag and drop must implement the Transferable
interface,
shown here.
public interface Transferable {
public abstract Object
getTransferData(DataFlavor flavor)
throws UnsupportedFlavorException, IOException;
public abstract DataFlavor[]
getTransferDataFlavors();
public abstract boolean isDataFlavorSupported(
DataFlavor flavor);
}
NOTE:
A clipboard is an area within memory for storing temporary
information in support of cut, copy, and paste tasks. There
tends to be two types of clipboards: a "System" clipboard
to share data between applications and private clipboards for
sharing data within a single application.
When looking at the interface definition, the first thing that you may
ask is "What's a DataFlavor?" A flavor is a way to represent
data when it is being transferred. This allows the receiving class to
ask for a flavor it understands. As the following class definition shows,
there is much that needs to be done to describe how the data is represented.
public class DataFlavor implements Externalizable,
Cloneable {
public static final DataFlavor javaFileListFlavor;
public static final String
javaJVMLocalObjectMimeType;
public static final String javaRemoteObjectMimeType;
public static String javaSerializedObjectMimeType;
public static final DataFlavor plainTextFlavor;
public static final DataFlavor stringFlavor;
public DataFlavor();
public DataFlavor(
Class representationClass,
String humanPresentableName);
public DataFlavor(String mimeType)
throws MimeTypeParseException,
ClassNotFoundException;
public DataFlavor(
String mimeType,
String humanPresentableName);
public DataFlavor(
String primaryType,
String subType,
MimeTypeParameterList params,
Class representationClass,
String humanPresentableName);
public Object clone() throws
CloneNotSupportedException;
public boolean equals(DataFlavor dataFlavor);
public boolean equals(Object obj);
public boolean equals(String s);
public boolean equals(MimeType mt);
public String getHumanPresentableName();
public String getMimeType();
public String getParameter(String paramName);
public String getPrimaryType();
public Class getRepresentationClass();
public String getSubType();
public boolean isFlavorJavaFileListType();
public boolean isFlavorRemoteObjectType();
public boolean isFlavorSerializedObjectType();
public final boolean isMimeTypeEqual(
DataFlavor dataFlavor);
public boolean isMimeTypeEqual(String mimeType);
public boolean isMimeTypeEqual(MimeType mimeType);
public boolean isMimeTypeSerializedObject();
public boolean isRepresentationClassInputStream();
public boolean isRepresentationClassRemote();
public boolean isRepresentationClassSerializable();
protected String normalizeMimeType(String mimeType);
protected String normalizeMimeTypeParameter(
String name, String value);
public synchronized
void readExternal(ObjectInput oi)
throws IOException,
ClassNotFoundException;
public void setHumanPresentableName(
String humanPresentableName);
public synchronized
void writeExternal(ObjectOutput oo)
throws IOException;
}
Thankfully, the class defines several constants for frequently used flavors:
javaFileListFlavor
, javaJVMLocalObjectMimeType
,
javaRemoteObjectMimeType
,
javaSerializedObjectMimeType
, plainTextFlavor
,
and stringFlavor
. When you decide you want some information
to be Transferable
, you decide in which flavors you are going
to make the data available. These flavors are dealt with internally as the
familiar Internet standard of MIME types, and there is a MimeType
class to help. However, it is rarely necessary to think in terms of MIME types.
It is just the naming scheme Java reuses to identify flavors. You may display
the DataFlavor
as the presentable name with
getHumanPresentableName()
, but that would be the only time
you would see something like application/x-java-serialized-object
,
which happens to be the presentable name for
javaSerializedObjectMimeType
.
NOTE:
If you are not familiar with MIME types, they are defined by
RFC 2045 and 2046. MIME stands for Multipurpose Internet
Mail Extension.
This is the way flavors work. When you have some information to transfer,
you identify the formats so that the target object can receive the data.
For instance, if the source of a transfer is the contents of a
JTextPane
, which supports multi-attributed text, you might
support three flavors:
- A flavor based on
javaSerializedObjectMimeType
to preserve the attributes, so it can be pasted into another
JTextPane
- A
stringFlavor
, so anyone who understands UNICODE
strings can receive it, but will lose formatting information
- A
plainTextFlavor
, where each character is
represented by only 8 bits, losing data from any non-ASCII
characters
This allows the target of the data transfer to ask the
Transferable
object if it supports a DataFlavor
it understands, via either getTransferDataFlavors()
to get a
list of all flavors or isDataFlavorSupported(DataFlavor)
to
inquire about a specific one. The target would start with the richest form
it understands, hoping to retain as much information as possible. If the
target object was a JTextArea
, which doesn't support
multi-attributed text, the best flavor it could ask for is
stringFlavor
.
As the concept of transferring Java String
objects is fairly
common, Java provides the StringSelection
support class to help.
It provides a Transferable
object that can provide the target
with its data as one of two flavors: stringFlavor
or
plainTextFlavor
.
public class StringSelection implements Transferable,
ClipboardOwner {
public StringSelection(String string);
public synchronized Object getTransferData(
DataFlavor flavor)
throws UnsupportedFlavorException, IOException;
public synchronized DataFlavor[]
getTransferDataFlavors();
public boolean isDataFlavorSupported(
DataFlavor flavor);
public void lostOwnership
(Clipboard clip, Transferable t);
}
Clipboard
As previously stated, the clipboard is an area within memory for storing
temporary information in support of cut, copy, and paste tasks. Asking the
Toolkit
object, with getSystemClipboard()
, provides
access to the system clipboard. You can also create private clipboards,
inaccessible beyond the scope of the program. The Clipboard
class is rather simple and defines the scope of its capabilities:
public class Clipboard {
public Clipboard(String name);
public synchronized Transferable
getContents(Object obj);
public String getName();
public synchronized void
setContents(Transferable t, ClipboardOwner own);
}
WARNING:
By default, access to the system clipboard is protected
by the security manager and not permitted from applets.
Basically, you can name the clipboard (from the constructor) as well as
set or get its contents. When you place something on the clipboard, it
actually isn't copied until someone tries to get it, so the
source must be sure the object is available while it remains on the
clipboard. To find out when an object placed on the clipboard has been
replaced is done with the help of the ClipboardOwner
interface.
public interface ClipboardOwner {
public abstract void lostOwnership(
Clipboard clip, Transferable t);
}
The previously mentioned StringSelection
class handles this
for you if you are transferring String
objects. It just
retains a reference to the String
to transfer. For other
types of transferable objects, you would have to do the same. If the
originally copied data were no longer available, it would be necessary
to throw an IOException
.
As the "Editor Kits and Text Actions" section of Chapter 15
showed, when working with the Swing text components, the cut, copy, and
paste operations are automatically handled for you with the help of the
installed EditorKit
. So, you normally do not have to worry
about creating code to access the clipboard. However, to demonstrate the
capabilities, we'll create a program that manually does copy and paste
with a JTextArea
. Figure 16.1 shows the sample program screen.
|
Figure 16.1: Clipboard accessing program
The program behind the application shown in Figure 16.1 follows. Be sure
to try pasting from external applications, not just from what was copied
with the program.
import java.awt.*;
import java.awt.event.*;
import com.sun.java.swing.*;
import java.awt.datatransfer.*;
import java.io.IOException;
public class ClipIt {
public static void main (String args[]) {
final Clipboard clipboard =
Toolkit.getDefaultToolkit().getSystemClipboard();
JFrame f = new JFrame ("Clip It");
f.addWindowListener (new WindowAdapter() {
public void windowClosing (WindowEvent e) {
System.exit (0);
}
});
Container c = f.getContentPane();
final JTextArea jt = new JTextArea();
JScrollPane pane = new JScrollPane (jt);
c.add (pane, BorderLayout.CENTER);
JButton copy = new JButton ("Copy");
copy.addActionListener (new ActionListener() {
public void actionPerformed (ActionEvent e) {
String selection = jt.getSelectedText();
StringSelection data = new StringSelection(
selection);
clipboard.setContents (data, data);
}
});
JButton paste = new JButton ("Paste");
paste.addActionListener (new ActionListener() {
public void actionPerformed (ActionEvent e) {
Transferable clipData = clipboard.getContents
(clipboard);
if (clipData != null) {
try {
String s = (String)(clipData.getTransferData
(
DataFlavor.stringFlavor));
jt.replaceSelection (s);
} catch (UnsupportedFlavorException ee) {
System.err.println ("Unsupported flavor:
" + ee);
} catch (IOException ee) {
System.err.println ("Unable to get data:
" + ee);
}
}
}
});
JPanel p = new JPanel();
p.add (copy);
p.add (paste);
c.add (p, BorderLayout.SOUTH);
f.setSize (300, 300);
f.setVisible (true);
}
}
NOTE:
Java does not currently support pasting objects like images from
external applications. You could copy an ImageIcon and treat it as
a javaSerializedObjectMimeType. However, it would then only be
pasteable into another Java program, or a program that understands
Java's serialization mechanism, which will be described in the
"Object Persistence and Serialization" section of Chapter 19.
Drag and Drop
Drag and drop within Java is similar to accessing the clipboard, except the
process of moving the data is a little more complicated. The data that you
wish to transfer still needs to implement the Transferable
interface. Besides the data to transfer, there is a source and a destination.
The source of the dragging implements the DragSourceListener
interface:
public interface DragSourceListener
extends EventListener{
public abstract void dragDropEnd(
DragSourceDropEvent e);
public abstract void dragEnter(
DragSourceDragEvent e);
public abstract void dragExit(
DragSourceEvent e);
public abstract void dragOver(
DragSourceDragEvent e);
public abstract void dropActionChanged(
DragSourceDragEvent e);
}
While the target implements DropTargetListener
:
public interface DropTargetListener
extends EventListener{
public abstract void
dragEnter(DropTargetDragEvent e);
public abstract void
dragExit(DropTargetEvent e);
public abstract void
dragOver(DropTargetDragEvent e);
public abstract void
drop(DropTargetDropEvent e);
public abstract void
dropActionChanged(DropTargetDragEvent e);
}
One thing should be pointed out. There are three different event types for
each set of five methods: DragSourceEvent
,
DragSourceDragEvent
, and DragSourceDropEvent
for
dragging; and DropTargetEvent
, DropTargetDropEvent
,
and DropTargetDragEvent
for dropping. This requires a little
extra care when implementing each interface.
The source of a dragging is a DragSource
. The draggable object
would create an instance of DragSource
within its constructor.
public class DragSource extends Object{
public static final Cursor DefaultCopyDrop;
public static final Cursor DefaultCopyNoDrop;
public static final Cursor DefaultLinkDrop;
public static final Cursor DefaultLinkNoDrop;
public static final Cursor DefaultMoveDrop;
public static final Cursor DefaultMoveNoDrop;
public DragSource();
public static DragSource getDefaultDragSource();
public static boolean isDragImageSupported();
public DragGestureRecognizer
createDefaultDragGestureRecognizer(
Component target,
int actions,
DragGestureListener l);
public DragGestureRecognizer
createDragGestureRecognizer(
Class recognizerAbstractClass,
Component target,
int actions,
DragGestureListener l);
protected DragSourceContext createDragSourceContext(
DragSourceContextPeerdscp,
DragGestureEvent dgl,
Cursor dragCursor,
Image dragImage,
Point imageOffset,
Transferable t,
DragSourceListener l);
public FlavorMap getFlavorMap();
public void startDrag(
DragGestureEvent trigger,
Cursor dragCursor,
Image
dragImage,
Point dragOffset,
Transferable t,
DragSourceListener l)
throws InvalidDnDOperationException
public void startDrag(
DragGestureEvent trigger,
Cursor dragCursor,
Image
dragImage,
Point imageOffset,
Transferable t,
DragSourceListener l,
FlavorMap map)
throws
InvalidDnDOperationException;
public void startDrag(
DragGestureEvent trigger,
Cursor dragCursor,
Transferable t,
DragSourceListener l)
throws InvalidDnDOperationException;
public void startDrag(
DragGestureEvent trigger,
Cursor dragCursor,
Transferable t,
DragSourceListener l,
FlavorMap map)
throws InvalidDnDOperationException;
The other thing you must do in the constructor is create a
DragGestureRecognizer
. Since detecting when the dragging
has started is platform specific, this is available from the
Toolkit
via createDragGestureRecognizer()
.
Instead of asking the Toolkit for the recognizer, there is
a convenience method
createDefaultDragGestureRecognizer()
available
from DragSource
that both creates the recognizer
and registers a DragGestureListener
.
public interface DragGestureListener
extends EventListener {
public abstract void dragGestureRecognized(
DragGestureEvent e);
}
It is within this dragGestureRecognized()
method that you
notify the DragSource
the dragging has started and call its
startDrag()
method.
To put all this together, create a draggable JLabel
object. This involves subclassing JLabel
and implementing
DragSourceListener
and DragGestureListener
.
The actual DragSourceListener
methods do nothing, as all
the work will be in the dragGestureRecognized()
method.
import java.awt.*;
import java.awt.dnd.*;
import java.awt.datatransfer.*;
import com.sun.java.swing.*;
import com.sun.java.swing.event.*;
public class DraggableLabel extends JLabel implements
DragSourceListener,
DragGestureListener {
DragSource dragSource;
public DraggableLabel(String s) {
super (s);
dragSource = new DragSource();
dragSource.createDefaultDragGestureRecognizer(
this, DnDConstants.ACTION_COPY, this);
}
public void dragGestureRecognized(
DragGestureEvent e) {
StringSelection text =
new StringSelection(getText());
dragSource.startDrag(
e,
DragSource.DefaultCopyDrop,
text,
this);
}
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){}
}
The only part not explained yet is the DnDConstants
.
The different actions of the events are defined within the
DnDConstants
class.
public final class DnDConstants {
public static final int ACTION_COPY;
public static final int ACTION_COPY_OR_MOVE;
public static final int ACTION_LINK;
public static final int ACTION_MOVE;
public static final int ACTION_NONE;
public static final int ACTION_REFERENCE;
public DnDConstants();
}
NOTE:
In JDK 1.2beta4, this is an abstract class. However, it should be
changed to an interface by the time 1.2 is released.
The component you'll drop the draggable label into is a JList
.
This involves subclassing and implementing the DropTargetListener
.
There are two methods of the interface that we need to implement:
dragEnter()
and drop()
. In dragEnter()
you need to tell the component what type of drag events you accept. Then in
drop()
you accept the drop, process the dragged data, and tell
the event you're done with it. As with clipboard operations, the dragged
data is dealt with via flavors.
import java.awt.*;
import java.awt.dnd.*;
import com.sun.java.swing.*;
import java.awt.datatransfer.*;
import java.io.IOException;
import java.util.Vector;
public class DroppableList extends JList
implements DropTargetListener {
DropTarget dropTarget;
Vector v = new Vector();
public DroppableList() {
dropTarget = new DropTarget (this, this);
}
public void dragEnter (DropTargetDragEvent e){
e.acceptDrag (DnDConstants.ACTION_COPY);
}
public void dragExit (DropTargetEvent e){
}
public void dragOver (DropTargetDragEvent e){
}
public void drop (DropTargetDropEvent e){
try {
Transferable tr = e.getTransferable();
if (tr.isDataFlavorSupported (
DataFlavor.stringFlavor)) {
e.acceptDrop
(DnDConstants.ACTION_COPY_OR_MOVE);
String s = (String)tr.getTransferData (
DataFlavor.stringFlavor);
v.add (s);
setListData (v);
paintImmediately(getVisibleRect());
e.getDropTargetContext().dropComplete(true);
} else {
System.err.println ("Rejected");
e.rejectDrop();
}
} catch (IOException io) {
io.printStackTrace();
e.rejectDrop();
} catch (UnsupportedFlavorException ufe){
ufe.printStackTrace();
e.rejectDrop();
}
}
public void dropActionChanged
(DropTargetDragEvent e){
}
}
To demonstrate the droppable list and draggable label, Figure 16.2 shows
a program with four labels around the list after several drag operations.
|
Figure 16.2: Drag and Drop demonstration
program
The test program behind Figure 16.2 is below:
import java.awt.*;
import java.awt.event.*;
import com.sun.java.swing.*;
import com.sun.java.swing.event.*;
public class Tester {
public static void main (String args[]) {
Frame f = new Frame("Tester");
DraggableLabel north = new DraggableLabel(
"One");
DraggableLabel south = new DraggableLabel(
"Two");
DraggableLabel east = new DraggableLabel(
"Three");
DraggableLabel west = new DraggableLabel(
"Four");
DroppableList list = new DroppableList();
JScrollPane pane = new JScrollPane (list);
f.add (north, BorderLayout.NORTH);
f.add (south, BorderLayout.SOUTH);
f.add (east, BorderLayout.EAST);
f.add (west, BorderLayout.WEST);
f.add (pane, BorderLayout.CENTER);
f.setSize (300, 300);
f.addWindowListener (new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
f.setVisible (true);
}
}
Summary
This chapter described how you deal with transferring data, both with
clipboards and with drag and drop operations. While the tasks are definitely
different, they share several common characteristics. By understanding the
Transferable
interface, as well as the DataFlavor
way of dealing with the data, you should have no problem with either operation.
About the Author
John Zukowski is a Software Mage with MageLang Institute. In addition to
being the author of Mastering Java 2,
Java AWT Reference, and Borland
JBuilder: No Experience Required. John also serves as
the Focus on Java Guide
at The Mining Co.
Reader Feedback
Tell us what you think of this book excerpt.