Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
his section has been updated to reflect features and conventions of the latest release, JDK 6.0,
but it is not yet final. We've published this preliminary version so you can get the most current
information now, and so you can tell us (please!) about errors, omissions, or improvements we
can make to this tutorial.
Drag and drop, and cut, copy and paste (collectively called data transfer) are essential features of
most applications. But what kind of support does Swing provide and how do you take advantage of
it?
For many components, when performing a drag and drop or a cut and paste operation, Swing handles
all of the work for you. For a handful of components, most of the work is done for you and all that is
left for you is to plug in the details of the data import and export.
This lesson provides an introduction to the data transfer mechanism used by Swing and discusses, in
particular, the TransferHandler class, the workhorse of the data transfer system.
Introduction to DnD
If you are writing an application you will want to support the ability to transfer information between
components in your application. But you will also want your application to play well with others —
this includes supporting the ability to transfer information between your application and other Java
applications, and between your application and native applications. The ability to transfer data takes
two forms:
Drag and drop (DnD) support. The following diagram illustrates dragging from a
JList and dropping onto a JTextField component (the arrows show the path of the
data):
Clipboard transfer through cut or copy and paste. The following diagrams show
cutting (or copying) from a JList and pasting onto a JTextField component:
933
Drag and Drop — Behind the Scenes
Let us say there is a user named Rollo, who is running a Java application. He wants to drag some text
from a list and deposit it into a text field. (Note that the process is the same when dragging from a
native application to a Java application.) In a nutshell, the drag and drop process works like this:
Rollo has selected a row of text in the source component: the list. While
holding the mouse button Rollo begins to drag the text — this initiates the
drag gesture.
When the drag begins, the list packages up the data for export and declares
what source actions it supports, such as COPY, MOVE, or LINK.
As Rollo drags the data, Swing continuously calculates the location and
handles the rendering.
If Rollo simultaneously holds the Shift and/or Control key during the drag,
this user action is also part of the drag gesture. Typically, an ordinary drag
requests the MOVE action. Holding the Control key while dragging requests the
COPY action, and holding both Shift and Control requests the LINK action.
Once Rollo drags the text over the bounds of a text field component, the target
is continually polled to see if it will accept or reject the potential drop. As he
drags, the target provides feedback by showing the drop location, perhaps an
insertion cursor or a highlighted selection. In this case, the text field (the
current target) allows both replacement of selected text and insertion of new
text.
When Rollo releases the mouse button, the text component inspects the
declared source actions and any user action and then it chooses what it wants
out of the available options. In this case, the text field chooses to insert the
new text at the point of the drop.
Finally, the text field imports the data.
While this might seem like a daunting process, Swing handles most of the work for you. The
framework is designed so that you plug in the details specific to your component, and the rest "just
works".
934
Note: We do not recommend that you create your own drag and drop support using the AWT classes.
This implementation would require significant complex support internal to each component. Prior to
release 1.4 when the dnd system was reworked, developers did occasionally create their own dnd
support, but it does not work with sophisticated components, like tree and table, that have subtle
selection and drop issues.
The following components recognize the drag gesture once the setDragEnabled(true) method is
invoked on the component. For example, once you invoke
myColorChooser.setDragEnabled(true) you can drag colors from your color chooser:
JColorChooser
JEditorPane
JFileChooser
JFormattedTextField
JList
JTable
JTextArea
JTextField
JTextPane
JTree
The following components support drop out of the box. If you are using one of these components,
your work is done.
JEditorPane
JFormattedTextField
JPasswordField
JTextArea
JTextField
JTextPane
JColorChooser
The framework for drop is in place for the following components, but you need to plug in a small
amount of code to customize the support for your needs.
JList
JTable
JTree
For these critical components, Swing performs the drop location calculations and rendering; it allows
you to specify a drop mode; and it handles component specific details, such as tree expansions. Your
work is fairly minimal.
935
Note: You can also install drop support on top-level containers, such as JFrame and JDialog. You
can learn more about this in Top-Level Drop.
Demo - BasicDnD
Now we will look at a simple demo, called BasicDnD, that shows you what you get for free. As you
see from the screen shot, BasicDnD contains a table, a list, a tree, a color chooser, a text area, and a
text field.
All of these components are standard out-of-the-box components except for the list. This list has
been customized to bring up a dialog showing where the drop would occur, if it accepted drops.
Text field
Text area
The color chooser accepts drops of type color, but in order to try this, you need to run two
copies of the demo (or another demo that contains a color chooser)
By default, none of the objects has default drag and drop enabled. At startup, you can check the
"Turn on Drag and Drop" check box to see what drag and drop behavior you get for free.
Try this:
1. Click the Launch button to run BasicDnD using Java™ Web Start (download JDK 6).
Alternatively, to compile and run the example yourself, consult the example index.
936
2. Select an item in the list and, while holding down the mouse button, begin to drag.
Nothing happens because the drag has not yet been enabled on the list.
3. Select the "Turn on Drag and Drop" check box.
4. Press the selected item in the list and begin to drag. Drop the text back onto the list. A
dialog shows where the text would appear if the list actually accepted drops. (The
default behavior for a list would be to show a "does not accept data" cursor.)
5. Drag the selected text over a text area. The insertion point for the text is indicated by a
blinking caret. Also, the cursor changes to indicate that the text area will accept the
text as a copy.
6. Release the mouse and watch the text appear in the text area.
7. Select some text in one of the text areas.
8. Press the mouse button while the cursor is over the selected text and begin to drag.
9. Note that this time, the cursor for a drag action appears. Successfully dropping this
text into another component will cause the text to be removed from the original
component.
10. Hold the Control key down and press again on the selected text. Begin dragging and
the copy cursor now appears. Move the cursor over the text area and drop. The text
appears in the new location but is not removed from the original location. The Control
key can be used to change any Move to a Copy.
11. Select a color from the color chooser. The selected color appears in the Preview panel.
Press and hold the mouse button over the color in the Preview panel and drag it over
the other components. Note that none of these components accepts color.
12. Try dropping text, color, and even files, onto the list. A dialog will report the
attempted action. The actual drop can be implemented with an additional six lines of
code that have been commented out in the BasicDnD.java source file.
Next we will look at the TransferHandler class, the workhorse of the drag and drop mechanism
TransferHandler Class
At the heart of the data transfer mechanism is the TransferHandler class. As its name suggests, the
TransferHandler provides an easy mechanism for transferring data to and from a JComponent —
all the details are contained in this class and its supporting classes. Most components are provided
with a default transfer handler. You can create and install your own transfer handler on any
component.
As mentioned previously, the default Swing transfer handlers, such as those used by text components
and the color chooser, provide the support considered to be most useful for both importing and
exporting of data. However list, table, and tree do not support drop by default. The reason for this is
937
that there is no all-purpose way to handle a drop on these components. For example, what does it
mean to drop on a particular node of a JTree? Does it replace the node, insert below it, or insert as a
child of that node? Also, we do not know what type of model is behind the tree — it might not be
mutable.
While Swing cannot provide a default implementation for these components, the framework for drop
is there. You need only to provide a custom TransferHandler that manages the actual import of
data.
Note: If you install a custom TransferHandler onto a Swing component, the default support is
replaced. For example, if you replace JTextField's TransferHandler with one that handles colors
only, you will disable its ability to support import and export of text.
If you must replace a default TransferHandler — for example, one that handles text — you will
need to re-implement the text import and export ability. This does not need to be as extensive as
what Swing provides — it could be as simple as supporting the StringFlavor data flavor,
depending on your application's needs.
Next we show what TransferHandler methods are required to implement data export.
Export Methods
The first set of methods we will examine are used for exporting data from a component. These
methods are invoked for the drag gesture, or the cut/copy action, when the component in question is
the source of the operation. The TransferHandler methods for exporting data are:
int getSourceActions(JComponent c) {
return COPY_OR_MOVE;
}
Transferable createTransferable(JComponent c) {
return new StringSelection(c.getSelection());
}
938
void exportDone(JComponent c, Transferable t, int action) {
if (action == MOVE) {
c.removeSelection();
}
}
Next we will look at the TransferHandler methods required for data import.
Import Methods
Now we will look at the methods used for importing data into a component. These methods are
invoked for the drop gesture, or the paste action, when the component is the target of the operation.
The TransferHandler methods for importing data are:
Version note: These methods replace older versions that do not use the TransferSupport class,
introduced in JDK 6. Unlike its replacement method, canImport(JComponent, DataFlavor[]) is
not called continuously.
You will notice that these import methods take a TransferHandler.TransferSupport argument.
Next we look at the TransferSupport class and then some sample import methods.
TransferSupport Class
The TransferSupport class, one of the inner classes of the TransferHandler class introduced in
JDK 6, serves two functions. As the name suggests, its first function is to support the transfer process
and for that purpose it provides several utility methods used to access the details of the data transfer.
The following list shows the methods that can be used to obtain information from the
TransferHandler. Several of these methods are related to drop actions, which will be discussed in
Setting the Drop Mode.
939
actions, see the API for DropTargetDragEvent. If the transfer is not a drop, this
method throws an exception.
int getSourceDropActions() — This method returns the set of actions supported
by the source component. If the transfer is not a drop, this method throws an
exception.
DataFlavor[] getDataFlavors() — This method returns all the data flavors
supported by this component. For example, a component might support files and text,
or text and color. If the transfer is not a drop, this method throws an exception.
boolean isDataFlavorSupported(DataFlavor) — This method returns true if the
specified DataFlavor is supported. The DataFlavor indicates the type of data
represented, such as an image (imageFlavor), a string (stringFlavor), a list of files
(javaFileListFlavor), and so on.
Transferable getTransferable() — This method returns the Transferable data
for this transfer. It is more efficient to use one of these methods to query information
about the transfer than to fetch the transferable and query it, so this method is not
recommended unless you cannot get the information another way.
DropLocation getDropLocation() — This method returns the drop location in the
component. Components with built-in drop support, such as list, table and tree,
override this method to return more useful data. For example, the version of this
method for the JList component returns the index in the list where the drop occurred.
If the transfer is not a drop, this method throws an exception.
Now that you are familiar with the TransferSupport utility methods, let us look at sample
canImport and importData methods:
public boolean canImport(TransferSupport supp) {
// Check for String flavor
if (!supp.isDataFlavorSupported(stringFlavor)) {
return false;
}
return true;
}
940
Next we look at how you can set the drop mode for selected components.
The default drop mode for JList is DropMode.USE_SELECTION. When dragging in this mode,
the selected item in the list moves to echo the potential drop point. On a drop the selected
item shifts to the drop location. This mode is provided for backwards compatibility but is
otherwise not recommended.
In DropMode.ON, the selected item in the list moves to echo the potential drop point, but the
selected item is not affected on the drop. This mode can be used to drop on top of existing list
items.
In DropMode.INSERT, the user is restricted to selecting the space between existing list items,
or before the first item or after the last item in the list. Selecting existing list items is not
allowed.
DropMode.ON_OR_INSERT is a combination of the ON and INSERT modes.
The JTree class provides the same set of drop modes and the JTable class has several more specific
to adding rows or columns.
To obtain the location of the drop, the TransferSupport class provides the getDropLocation
method that returns the precise point where the drop has occurred. But for a list component, the
index of the drop is more useful than a pixel location, so JList provides a special subclass, called
JList.DropLocation. This class provides the getIndex and isInsert methods, which handle the
math for you.
The table, tree, and text components each provide an implementation of DropLocation with
methods that make the most sense for each component. The JTable.setDropMode method has the
most choices. The following table shows the methods for all four classes:
Next is a demo that implements a custom transfer handler for a list component so that it fully
participates in drag and drop.
941
Demo - DropDemo
Now we will look at a demo that uses a custom transfer handler to implement drop for a list
component. Although the default transfer handler for list implements export, because we are creating
a custom transfer handler to implement import, we have to re-implement export as well.
As you see from the screen shot, DropDemo contains an editable text area, a list, and a combo box
that allows you to select the drop mode for the list.
Try this:
1. Click the Launch button to run DropDemo using Java™ Web Start (download JDK 6).
Alternatively, to compile and run the example yourself, consult the example index.
2. Select some text in the text area and drop onto the list. The selected list entry is
replaced and that item becomes the current selection. This is how USE_SELECTION
works and is provided for backwards compatibility but is otherwise not
recommended.
3. Change the List Drop Mode to ON and try the same action. Once again, the selected
list item is replaced, but the current selection does not move.
4. Change the List Drop Mode to INSERT and repeat the same action. The added text is
inserted at the drop location. In this mode it is not possible to modify existing list
items.
5. Change the List Drop Mode to ON_OR_INSERT. Depending on the cursor position, you
can either insert the new text or you can replace existing text.
942
Here is the ListTransferHandler implementation for DropDemo.java.
The transfer handler for this list supports copy and move and it reimplements the drag support that
list provides by default.
/**
* We only support importing strings.
*/
public boolean canImport(TransferHandler.TransferSupport info) {
// Check for String flavor
if (!info.isDataFlavorSupported(DataFlavor.stringFlavor)) {
return false;
}
return true;
}
/**
* Bundle up the selected items in a single list for export.
* Each line is separated by a newline.
*/
protected Transferable createTransferable(JComponent c) {
JList list = (JList)c;
indices = list.getSelectedIndices();
Object[] values = list.getSelectedValues();
/**
* We support both copy and move actions.
*/
public int getSourceActions(JComponent c) {
return TransferHandler.COPY_OR_MOVE;
}
/**
* Perform the actual import. This demo only supports drag and drop.
*/
public boolean importData(TransferHandler.TransferSupport info) {
if (!info.isDrop()) {
return false;
}
943
boolean insert = dl.isInsert();
addIndex = index;
addCount = values.length;
/**
* Remove the items moved from the list.
*/
protected void exportDone(JComponent c, Transferable data, int action) {
JList source = (JList)c;
DefaultListModel listModel = (DefaultListModel)source.getModel();
if (action == TransferHandler.MOVE) {
for (int i = indices.length - 1; i >= 0; i--) {
listModel.remove(indices[i]);
}
}
indices = null;
addCount = 0;
addIndex = -1;
}
}
Next we look at how the target can choose the drop action.
The user action indicates a preference, but ultimately it is the target that decides the drop action. For
example, consider a component that will only accept copied data. And consider a drag source that
supports both copy and move. The TransferHandler for the copy-only target can be coded to only
accept data from the source using the setDropAction method, even if the user has indicated a
preference for a move action.
This work happens in the canImport method, where the target's TransferHandler decides whether
to accept the incoming data. An implementation that explicitly chooses the COPY action, if it is
supported by the source, might look like this:
The code snippet displayed in bold shows where the source's supported drop actions are queried. If
copy is supported, the setDropAction method is invoked to ensure that only a copy action will take
place and the method returns true.
Next we will look at a demo that explicitly sets the drop action using setDropAction.
Demo - ChooseDropAction
The following demo, ChooseDropActionDemo, contains three lists. As you can see in the screen shot,
the list on the left, labeled "Drag from here", is the drag source. This list supports both move and
copy but it does not implement import — so you cannot drop into it.
On the right side are two lists that act as drop targets. The top list, labeled "Drop to COPY here" will
only allow data to be copied to it. The bottom list, labeled "Drop to MOVE here" will only allow
data to be moved to it. The source list only allows data to be dragged from it.
945
Try this:
1. Click the Launch button to run ChooseDropActionDemo using Java™ Web Start
(download JDK 6). Alternatively, to compile and run the example yourself, consult
the example index.
2. Select an item in the source list and drag to the upper target list. As you drag over the
target, notice that the copy-drop mouse cursor displays, even if you are not holding
the Control key to signify that you want a copy action. (Note that the copy cursor does
not appear on the Macintosh platform, unless you are pressing the Option key.)
3. Drop the item. It is inserted into the target list but not removed from the source — as
desired.
4. Drag again from the source list, but this time into the lower target list. Drop the item.
It is inserted into the target list and removed from the source list.
5. Select another item in the source list and, while pressing the Control key to indicate a
preference for the COPY action, drag the item to the lower target list.
6. Drop the item into the list. The item is not inserted — the drop is rejected. The
canImport method for the transfer handler was coded to reject the COPY action, but
it could have been implemented to return true, in which case the user action would
prevail and a copy would occur.
/**
* The FromTransferHandler allows dragging from the list and
* supports both copy and move actions. This transfer handler
* does not support import.
*/
946
class FromTransferHandler extends TransferHandler {
public int getSourceActions(JComponent comp) {
return COPY_OR_MOVE;
}
from.removeElementAt(index);
}
}
/**
* The ToTransferHandler has a constructor that specifies whether the
* instance will support only the copy action or the move action.
* This transfer handler does not support export.
*/
class ToTransferHandler extends TransferHandler {
int action;
947
if (!canImport(support)) {
return false;
}
return true;
}
}
The FromTransferHandler, attached to the source list, allows for dragging from the list and
supports both copy and move actions. If you try to drop onto this list, the drop will be rejected
because FromTransferHandler has not implemented the canImport and importData methods.
The ToTransferHandler, attached to both the move-only and the copy-only target list, contains a
constructor that specifies whether the target list will allow only copy or only move. An instance that
supports the copy action is attached to the copy-only list and an instance that supports the move
action is attached to the move-only list.
You might also be interested in the Top-Level Drop example which also illustrates choosing the drop
action.
To control this programmatically, you can use the setShowDropLocation method. Calling this
method with true causes the visual feedback for the drop location to always be displayed, even if the
948
drop will not be accepted. Calling this method with false prevents any visual feedback, even if the
drop will be accepted. You always invoke this method from canImport.
The Demo - LocationSensitiveDemo page includes a combo box that enables you to choose to
always show the drop location, or never show the drop location, or the default behavior. But first we
will talk about location sensitive drop.
For example, imagine a table that allows drop, but not in the first column. The canImport method
might look something like this:
return true;
}
The code displayed in bold indicates the location-sensitive drop logic: When the user drops the data
in such a way that the column cannot be calculated (and is therefore invalid) or when the user drops
the text in the first column, the canImport method returns false — so Swing shows the "no-drag"
mouse cursor. As soon as the user moves the mouse off the first column canImport returns true and
Swing shows the drag cursor.
949
Next, we show a demo of a tree that has implemented location-sensitive drop.
Demo - LocationSensitiveDemo
The following demo, LocationSensitiveDemo, shows a JTree that has been configured to support
drop on any node except for one called "names" (or its descendants). Use the text field at the top of
the frame as the drag source (it will automatically increment the string number each time you drag
from there).
A combo box below the tree allows you to toggle the behavior for showing the drop location.
Swing's default behavior is to show the drop location only when the area can accept the drop. You
can override this behavior to always show the drop location (even if the area cannot accept the drop)
or to never show the drop location (even if the area can accept the drop).
Try this:
1. Click the Launch button to run LocationSensitiveDemo using Java™ Web Start
(download JDK 6). Alternatively, to compile and run the example yourself, consult
the example index.
2. Initiate a drag by pressing on top of "String 0" in the text field and moving the mouse
a short distance. Drag into the tree and move downwards. As you hover the mouse
over most of the nodes, the drag acceptibility is indicated by both the mouse cursor
and by the node becoming highlighted. Drop the text onto the "colors" node. The new
item becomes a child of that node and a sibling to the colors listed.
3. Drag "String 1" from the textfield into the tree. Try to drop it on the "names" node. As
you drag over that node or its children, Swing will not provide any drop location
feedback and it will not accept the data.
950
4. Change the "Show drop location" combo box to "Always".
5. Repeat steps 1 and 2. The drop location now displays for the "names" node, but you
cannot drop data into that area.
6. Change the "Show drop location" combo box to "Never".
7. Repeat steps 1 and 2. The drop location will not display for any part of the tree,
though you can still drop data into the nodes, other than "names".
if (item.equals("Always")) {
info.setShowDropLocation(true);
} else if (item.equals("Never")) {
info.setShowDropLocation(false);
}
return true;
}
The first code snippet displayed in bold modifies the drop location feedback mechanism. If
"Always", then the drop location is always shown. If "Never", the drop location is never shown.
Otherwise, the default behavior applies.
The second code snippet displayed in bold contains the logic that determines whether the tree will
accept the data. If the path is not a valid path or if it is not the names path (or its descendant) it will
return false and the import will not be accepted.
951
Creating and attaching a TransferHandler.
Enabling data transfer by calling setDragEnabled(true).
Creating a scroll pane and adding the table to the scroll pane.
You run the application and try to drag valid data into the table but it rejects the drop. What gives?
The reason is that the empty table (unlike an empty list or an empty tree) does not occupy any space
in the scroll pane. The JTable does not automatically stretch to fill the height of a JScrollPane's
viewport — it only takes up as much vertical room as needed for the rows that it contains. So, when
you drag over the empty table, you are not actually over the table and the drop fails.
You can configure the table to allow drop anywhere in the view port by calling
JTable.setFillsViewportHeight(boolean). The default for this property is false to ensure
backwards compatibility.
The following example, FillViewportHeightDemo, allows you to experiment with dropping onto an
empty table. The demo contains an empty table with five columns that has its drop mode set to insert
rows and a drag source that provides five comma-delimited values that autoincrement.
Try this:
1. Click the Launch button to run FillViewportHeightDemo using Java™ Web Start
(download JDK 6). Alternatively, to compile and run the example yourself, consult
the example index.
2. Drag from the text field labeled "Drag from here" to the table.
3. Drop onto the table. The drop is rejected.
4. Double-click on the drag source. It deposits the current values (0, 0, 0, 0, 0) into the
table and increments the values in the text field.
5. Once again, drag from the text field to the table. You can insert above or below the
row, but not in the area underneath.
6. From the Options menu, choose "Fill Viewport Height" to enable the
"fillsViewportHeight" property.
7. From the Options menu, choose "Reset" to empty the table.
8. Drag from the text component to the table. You can now drop anywhere on the view
port and it inserts the data at row 0.
952
You can examine the source for FillViewportHeightDemo.java, but the primary point to
remember is that you should generally invoke setFillsViewportHeight(true) on any table that
will accept dropped data.
You can register to be notified whenever the dropLocation property changes. You would listen for
this change and do your own rendering of the drop location in a custom renderer for the component
or in the paintComponent method, using the getDropLocation method.
comp.addPropertyChangeListener("dropLocation", newRepainter());
renderPrettyIndicatorAt(loc);
}
Top-Level Drop
Up until now, we have primarily focused on attaching a TransferHandler to one of the JComponent
subclasses. But you can also set a TransferHandler directly on a top-level container, such as
JFrame and JDialog.
This is particularly useful for applications that import files, such as editors, IDEs, image
manipulation programs, CD burning programs. Such an application generally includes a menu, a
toolbar, an area for editing documents, and probably a list or mechanism for switching between open
documents.
We have such an example written by Shannon Hickey, the Swing team lead. Because this demo
reads files, we do not provide a Java Web Start version — you will have to download and compile
the demo yourself.
953
As you can see in the screen shot below, TopLevelTransferHandler has a menu (empty, except for
the Demo submenu), a (non-functional) toolbar, an area (on the left) that displays a list of open
documents, and a area (to the right) that displays the content of each open document. At startup the
blue document area has been assigned a transfer handler that supports file imports — so is the only
place that can accept a drop.
Try this:
954
Note one undesirable side effect of disabling the default transfer handler on the text component: You
can no longer drag and drop (or cut/copy/paste) text within the editing area. To fix this, you will need
to implement a custom transfer handler for the text component that accepts file drops and also re-
implements the missing support for text transfers. You might want to watch RFE 4830695 which
would allow adding data import on top of an existing TransferHandler.
/**
* Demonstration of the top-level {@code TransferHandler}
* support on {@code JFrame}.
*
* @author Shannon Hickey
*/
public class TopLevelTransferHandlerDemo extends JFrame {
try {
BufferedReader reader = new BufferedReader(new
InputStreamReader(url.openStream()));
String in;
955
while ((in = reader.readLine()) != null) {
area.append(in);
area.append("\n");
}
reader.close();
} catch (Exception e) {
e.printStackTrace();
return;
}
th = area.getTransferHandler();
area.setFont(new Font("monospaced", Font.PLAIN, 12));
area.setCaretPosition(0);
area.setDragEnabled(true);
area.setDropMode(DropMode.INSERT);
frame.getContentPane().add(new JScrollPane(area));
dp.add(frame);
frame.show();
if (DEMO) {
frame.setSize(300, 200);
} else {
frame.setSize(400, 300);
}
frame.setResizable(true);
frame.setClosable(true);
frame.setIconifiable(true);
frame.setMaximizable(true);
frame.setLocation(left, top);
incr();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
select();
}
});
nullItem.addActionListener(this);
setNullTH();
}
956
public void actionPerformed(ActionEvent ae) {
setNullTH();
}
if (copyItem.isSelected()) {
boolean copySupported = (COPY & support.getSourceDropActions())
== COPY;
if (!copySupported) {
return false;
}
support.setDropAction(COPY);
}
return true;
}
Transferable t = support.getTransferable();
try {
java.util.List l =
(java.util.List)t.getTransferData(DataFlavor.javaFileListFlavor);
for (File f : l) {
new Doc(f);
}
} catch (UnsupportedFlavorException e) {
return false;
} catch (IOException e) {
return false;
}
return true;
}
};
957
}
public TopLevelTransferHandlerDemo() {
super("TopLevelTransferHandlerDemo");
setJMenuBar(createDummyMenuBar());
getContentPane().add(createDummyToolBar(), BorderLayout.NORTH);
list.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
list.addListSelectionListener(new ListSelectionListener() {
public void valueChanged(ListSelectionEvent e) {
if (e.getValueIsAdjusting()) {
return;
}
nullItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
if (nullItem.isSelected()) {
list.setTransferHandler(null);
} else {
list.setTransferHandler(th);
}
}
});
thItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
if (thItem.isSelected()) {
setTransferHandler(handler);
} else {
setTransferHandler(null);
}
}
});
dp.setTransferHandler(handler);
}
958
test.setSize(800, 600);
}
test.setLocationRelativeTo(null);
test.setVisible(true);
test.list.requestFocus();
}
959
return mb;
}
Next we look at a cut and paste example that feature a text component.
The following demo, TextCutPaste, contains three text fields. As you can see in the screen shot,
you can cut, copy, and paste to or from any of these text fields. They also support drag and drop.
960
Try this:
1. Click the Launch button to run TextCutPaste using Java™ Web Start (download
JDK 6). Alternatively, to compile and run the example yourself, consult the example
index.
2. Select text in one of the text fields. Use the Edit menu or the keyboard equivalent to
cut or copy the text from the source.
3. Position the caret where you want the text to be pasted.
4. Paste the text using the menu or the keyboard equivalent.
5. Perform the same operation using drag and drop.
Here is the code that creates the Edit menu by hooking up the built-in cut, copy, and paste actions
defined in DefaultEditorKit to the menu items. This works with any component that descends
from JComponent:
/**
* Create an Edit menu to support cut/copy/paste.
*/
public JMenuBar createMenuBar () {
JMenuItem menuItem = null;
JMenuBar menuBar = new JMenuBar();
JMenu mainMenu = new JMenu("Edit");
mainMenu.setMnemonic(KeyEvent.VK_E);
menuBar.add(mainMenu);
return menuBar;
}
Next we will look at how to accomplish the same functionality using a component that does not have
the built-in support of the DefaultEditorKit.
961
CCP in a non-Text Component
If you are implementing cut, copy and paste using one of the Swing components that is not one of the
text components you have to do some additional setup. First, you need to install the cut, copy, and
paste actions in the action map. The following method shows how to do this:
private void setMappings(JList list) {
ActionMap map = list.getActionMap();
map.put(TransferHandler.getCutAction().getValue(Action.NAME),
TransferHandler.getCutAction());
map.put(TransferHandler.getCopyAction().getValue(Action.NAME),
TransferHandler.getCopyAction());
map.put(TransferHandler.getPasteAction().getValue(Action.NAME),
TransferHandler.getPasteAction());
When you set up the Edit menu, you can also choose to add menu accelerators, so that the user can
type Control-C to initiate a copy, for example. In the following code snippet, the bolded text shows
how to set the menu accelerator for the cut action:
If you have set the menu accelerators for the CCP actions, this next step is redundant. If you have not
set the menu accelerators, you need to add the CCP bindings to the input map. The following code
snippet shows how this is done:
Once the bindings have been installed and the Edit menu has been set up, there is another issue to be
addressed: When the user initiates a cut, copy or a paste, which component should receive the
action? In the case of a text component, the DefaultEditorKit remembers which component last
had the focus and forwards the action to that component. The following class,
TransferActionListener, performs the same function for non-text Swing components. This class
can be dropped into most any application:
public TransferActionListener() {
KeyboardFocusManager manager = KeyboardFocusManager.
getCurrentKeyboardFocusManager();
manager.addPropertyChangeListener("permanentFocusOwner", this);
}
962
public void propertyChange(PropertyChangeEvent e) {
Object o = e.getNewValue();
if (o instanceof JComponent) {
focusOwner = (JComponent)o;
} else {
focusOwner = null;
}
}
Finally, you have to decide how to handle the paste. In the case of a drag and drop, you insert the
data at the drop location. In the case of a paste, you do not have the benefit of the user pointing to the
desired paste location. You need to decide what makes sense for your application — inserting the
data before or after the current selection might be the best solution.
The following demo, ListCutPaste, shows how to implement CCP in an instance of JList. As you
can see in the screen shot there are three lists and you can cut, copy, and paste to or from any of these
lists. They also support drag and drop. For this demo, the pasted data is inserted after the current
selection. If there is no current selection, the data is appended to the end of the list.
Try this:
1. Click the Launch button to run ListCutPaste using Java™ Web Start (download JDK
6). Alternatively, to compile and run the example yourself, consult the example index.
2. Select an item in one of the lists. Use the Edit menu or the keyboard equivalent to cut
or copy the list item from the source.
963
3. Select the list item where you want the item to be pasted.
4. Paste the text using the menu or the keyboard equivalent. The item is pasted after the
current selection.
5. Perform the same operation using drag and drop.
imageFlavor represents data in the java.awt.Image format. This is used when dragging
image data.
stringFlavor represents data in the most basic form of text — java.lang.String. This is
the most commonly used data flavor for most applications.
javaFileListFlavor represents java.io.File objects in a java.util.List format. This
is useful for applications that drag files, such as the TopLevelTransferHandler example,
discussed in the Top-Level Drop lesson.
For most applications, this is all you need to know about data flavors. However, if you require a
flavor other than these predefined types, you can create your own. If you create a custom component
and want it to participate in data transfer, you will need to create a custom data flavor. The
constructor for specifying a data flavor is DataFlavor(Class, String). For example, to create a
data flavor for the java.util.ArrayList class:
Transferring the data using this mechanism uses Object serialization, so the class you use to transfer
the data must implement the Serializable interface, as must anything that is serialized with it. If
everything is not serializable, you will see a NotSerializableException during drop or copy to the
clipboard.
Creating a data flavor using the DataFlavor(Class, String) constructor allows you to transfer
data between applications, including native applications. If you want to create a data flavor that
transfers data only within an application, use javaJVMLocalObjectMimeType and the
DataFlavor(String) constructor. For example, to specify a data flavor that transfers color from a
JColorChooser only within your application, you could use this code:
To create a data flavor for an ArrayList that would work only within your application:
new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType +
";class=java.util.ArrayList");
new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType +
964
";class=\"" + int[].class.getName() + "\"");
A MIME type containing special characters, such as [ or ;, must have those characters enclosed in
quotes.
A Transferable can be implemented to support multiple flavors. For example, you can use both
local and serialization flavors together, or you can use two forms of the same data, such as the
ArrayList and integer array flavors, together, or you can create a TransferHandler that accepts
different types of data, such as color and text.
You implement both within the TransferHandler's importData method, like this:
if (transferSupport.isDrop()) {
// put data in transferSupport.getDropLocation()
} else {
// determine where you want the paste to go (ex: after current selection)
// put data there
}
The ListCutPaste example, discussed on the CCP in a non-Text Component page, supports both
dnd and ccp. Here is its importData method (the if-else drop logic is bolded):
965
int index = dl.getIndex();
if (dl.isInsert()) {
model.add(index, data);
return true;
} else {
model.set(index, data);
return true;
}
} else { //This is a paste
int index = list.getSelectedIndex();
// if there is a valid selection,
// insert data after the selection
if (index >= 0) {
model.add(list.getSelectedIndex()+1, data);
// else append to the end of the list
} else {
model.addElement(data);
}
return true;
}
}
This is the only place where you need to install if-else logic to distinguish between dnd and ccp.
Further Information
One of the best places to get the latest information on data transfer is Shannon Hickey's blog.
Shannon is the Swing team lead and he "owns" the Swing portion of drag and drop (among other
things) and he wrote several of the demos used in this lesson. The following blog entries were
written during the JDK 6 development process, so some of the method names and other details have
changed, but it is an interesting peek into the process.
Do not use your own drag gesture recognizers with these components. Use setDragEnabled(true)
and a TransferHandler.
966
You need to call seFillsViewportHeight(true) on the table. See Empty Table Drop for more
information.
967