Sie sind auf Seite 1von 35

Lesson: Drag and Drop and Data Transfer

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".

More on this in the next section.

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.

Default DnD Support


Technically speaking, the framework for drag and drop supports all Swing components — the data
transfer mechanism is built into every JComponent. If you wanted, you could implement drop
support for a JSlider so that it could fully participate in data transfer. While JSlider does not
support drop by default, the components you would want (and expect) to support drag and drop do
provide specialized built-in support.

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.

The following areas accept 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.

This figure has been reduced to fit on the page.


Click the image to view it at its natural size.

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.

There are three methods used to engage a TransferHandler on a component:

 setDragEnabled(boolean) — turns on drag support. (The default is false.) This method is


defined on each component that supports the drag gesture; the link takes you to the
documentation for JList.
 setDropMode(DropMode) — configures how drop locations are determined. This method is
defined for JList, JTable, and JTree; the link takes you to the documentation for JList.
 setTransferHandler(TransferHandler) — used to plug in custom data import and
export. This method is defined on JComponent, so it is inherited by every Swing 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:

 getSourceActions(JComponent) — This method is used to query what actions are


supported by the source component, such as COPY, MOVE, or LINK, in any combination.
For example, a customer list might not support moving a customer name out of the
list, but it would very likely support copying the customer name. Most of our
examples support both COPY and MOVE.
 createTransferable(JComponent) — This method bundles up the data to be
exported into a Transferable object in preparation for the transfer.
 exportDone(JComponent, Transferable, int) — This method is invoked after
the export is complete. When the action is a MOVE, the data needs to be removed from
the source after the transfer is complete — this method is where any necessary
cleanup occurs.

Sample Export Methods

Here are some sample implementations of the export methods:

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:

 canImport(TransferHandler.TransferSupport) — This method is called repeatedly


during a drag gesture and returns true if the area below the cursor can accept the transfer, or
false if the transfer will be rejected. For example, if a user drags a color over a component
that accepts only text, the canImport method for that component's TransferHandler should
return false.
 importData(TransferHandler.TransferSupport) — This method is called on a
successful drop (or paste) and initiates the transfer of data to the target component. This
method returns true if the import was successful and false otherwise.

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.

 Component getComponent()— This method returns the target component of the


transfer.
 int getDropAction() — This method returns the chosen action (COPY, MOVE or
LINK) when the transfer is a drop. If the transfer is not a drop, this method throws an
exception.
 int getUserDropAction() — This method returns the user's chosen drop action.
For example, if the user simultaneously holds Control and Shift during the drag
gesture, this indicates an ACTION_LINK action. For more information on user drop

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.

Sample Import Methods

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;
}

// Fetch the drop location


DropLocation loc = supp.getDropLocation();

// Return whether we accept the location


return shouldAcceptDropLocation(loc);
}

public boolean importData(TransferSupport supp) {


if (!canImport(sup)) {
return false;
}

// Fetch the Transferable and its data


Transferable t = supp.getTransferable();
String data = t.getTransferData(stringFlavor);

// Fetch the drop location


DropLocation loc = supp.getDropLocation();

// Insert the data at this location


insertAt(loc, data);

return true;
}

940
Next we look at how you can set the drop mode for selected components.

Setting the Drop Mode


When enabling drop on a component, such as a list, you need to decide how you want the drop
location to be interpreted. For example, do you want to restrict the user to replacing existing entries?
Do you want to only allow adding or inserting new entries? Do you want to allow both? To configure
this behavior, the JList class provides the setDropMode method which supports the following drop
modes.

 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:

DropLocation Methods for JList, JTree, JTable and JTextComponent


JList.DropLocatio JTree.DropLocatio JTable.DropLocatio JTextComponent.DropLocati
n n n on
isInsert getChildIndex isInsertRow getIndex
getIndex getPath isInsertColumn getBias
getRow
getColumn

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.

public class ListTransferHandler extends TransferHandler {


private int[] indices = null;
private int addIndex = -1; //Location where items were added
private int addCount = 0; //Number of items added.

/**
* 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();

StringBuffer buff = new StringBuffer();

for (int i = 0; i < values.length; i++) {


Object val = values[i];
buff.append(val == null ? "" : val.toString());
if (i != values.length - 1) {
buff.append("\n");
}
}

return new StringSelection(buff.toString());


}

/**
* 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;
}

JList list = (JList)info.getComponent();


DefaultListModel listModel = (DefaultListModel)list.getModel();
JList.DropLocation dl = (JList.DropLocation)info.getDropLocation();
int index = dl.getIndex();

943
boolean insert = dl.isInsert();

// Get the string that is being dropped.


Transferable t = info.getTransferable();
String data;
try {
data = (String)t.getTransferData(DataFlavor.stringFlavor);
}
catch (Exception e) { return false; }

// Wherever there is a newline in the incoming data,


// break it into a separate item in the list.
String[] values = data.split("\n");

addIndex = index;
addCount = values.length;

// Perform the actual import.


for (int i = 0; i < values.length; i++) {
if (insert) {
listModel.add(index++, values[i]);
} else {
// If the items go beyond the end of the current
// list, add them in.
if (index < listModel.getSize()) {
listModel.set(index++, values[i]);
} else {
listModel.add(index++, values[i]);
}
}
}
return true;
}

/**
* 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.

Choosing the Drop Action


Every drag source (Java based or otherwise) advertises the set of actions it supports when exporting
data. If it supports data being copied, it advertises the COPY action; if it supports data being moved
from it, then it advertises the MOVE action, and so on. For Swing components, the source actions are
advertised through the getSourceActions method.
944
When a drag is initiated, the user has some control over which of the source actions is chosen for the
transfer by way of keyboard modifiers used in conjunction with the drag gesture — this is called the
user action. For example, the default (where no modifiers are used) generally indicates a move
action, holding the Control key indicates a copy action, and holding both Shift and Control indicates
a linking action. The user action is available via the getUserDropAction method.

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:

public boolean canImport(TransferHandler.TransferSupport support) {


// for the demo, we will only support drops (not clipboard paste)
if (!support.isDrop()) {
return false;
}

// we only import Strings


if (!support.isDataFlavorSupported(DataFlavor.stringFlavor)) {
return false;
}

// check if the source actions (a bitwise-OR of supported actions)


// contains the COPY action
boolean copySupported = (COPY & support.getSourceDropActions()) == COPY;
if (copySupported) {
support.setDropAction(COPY);
return true;
}

// COPY is not supported, so reject the transfer


return false;
}

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.

As you might guess, the ChooseDropActionDemo.java example contains two TransferHandler


implementations:

/**
* 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;
}

private int index = 0;

public Transferable createTransferable(JComponent comp) {


index = dragFrom.getSelectedIndex();
if (index < 0 || index >= from.getSize()) {
return null;
}

return new StringSelection((String)dragFrom.getSelectedValue());


}

public void exportDone(JComponent comp, Transferable trans, int action) {


if (action != MOVE) {
return;
}

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;

public ToTransferHandler(int action) {


this.action = action;
}

public boolean canImport(TransferHandler.TransferSupport support) {


// for the demo, we will only support drops (not clipboard paste)
if (!support.isDrop()) {
return false;
}

// we only import Strings


if (!support.isDataFlavorSupported(DataFlavor.stringFlavor)) {
return false;
}

// check if the source actions contain the desired action -


// either copy or move, depending on what was specified when
// this instance was created
boolean actionSupported = (action & support.getSourceDropActions()) ==
action;
if (actionSupported) {
support.setDropAction(action);
return true;
}

// the desired action is not supported, so reject the transfer


return false;
}

public boolean importData(TransferHandler.TransferSupport support) {


// if we cannot handle the import, say so

947
if (!canImport(support)) {
return false;
}

// fetch the drop location


JList.DropLocation dl = (JList.DropLocation)support.getDropLocation();

int index = dl.getIndex();

// fetch the data and bail if this fails


String data;
try {
data =
(String)support.getTransferable().getTransferData(DataFlavor.stringFlavor);
} catch (UnsupportedFlavorException e) {
return false;
} catch (java.io.IOException e) {
return false;
}

JList list = (JList)support.getComponent();


DefaultListModel model = (DefaultListModel)list.getModel();
model.insertElementAt(data, index);

Rectangle rect = list.getCellBounds(index, index);


list.scrollRectToVisible(rect);
list.setSelectedIndex(index);
list.requestFocusInWindow();

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.

Next we look at showing the drop location.

Showing the Drop Location


Generally during a drag operation, a component gives visual feedback when it can accept the data. It
might highlight the drop location, or it might show a caret or a horizontal line where the insertion
would occur. Swing renders the drop location when the canImport method for the component's
TransferHandler returns true.

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.

Location Sensitive Drop


Sometimes you have a complex component and you want the user to be able to drop on some parts of
it, but not on others. Perhaps it is a table that allows data to be dropped only in certain columns; or
perhaps it is a tree that allows data to be dropped only on certain nodes. Obviously you want the
cursor to provide accurate feedback — it should only show the drop location when it is over the
specific part of the component that accepts drops.

This is simple to accomplish by installing the necessary logic in the


canImport(TransferHandler.TransferSupport) method of the TransferHandler class. It works
only with this particular version of canImport because it is called continously while the drag gesture
is over the bounds of the component. When this method returns true, Swing shows the drop cursor
and the drop location is visually indicated; when this method returns false, Swing shows the "no-
drag" cursor and the drop location is not displayed.

For example, imagine a table that allows drop, but not in the first column. The canImport method
might look something like this:

public boolean canImport(TransferHandler.TransferSupport info) {


// for the demo, we will only support drops (not clipboard paste)
if (!info.isDrop()) {
return false;
}

// we only import Strings


if (!info.isDataFlavorSupported(DataFlavor.stringFlavor)) {
return false;
}

// fetch the drop location


JTable.DropLocation dl = (JTable.DropLocation)info.getDropLocation();

int column = dl.getColumn();

// we do not support invalid columns or the first column


if (column == -1 || column == 0) {
return false;
}

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".

The canImport method for LocationSensitiveDemo looks like this:

public boolean canImport(TransferHandler.TransferSupport info) {


// for the demo, we will only support drops (not clipboard paste)
if (!info.isDrop()) {
return false;
}

String item = (String)indicateCombo.getSelectedItem();

if (item.equals("Always")) {
info.setShowDropLocation(true);
} else if (item.equals("Never")) {
info.setShowDropLocation(false);
}

// we only import Strings


if (!info.isDataFlavorSupported(DataFlavor.stringFlavor)) {
return false;
}

// fetch the drop location


JTree.DropLocation dl = (JTree.DropLocation)info.getDropLocation();

TreePath path = dl.getPath();

// we do not support invalid paths or descendants of the names folder


if (path == null || namesPath.isDescendant(path)) {
return 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.

Empty Table Drop


Dragging and dropping into an empty table presents a unique challenge. When adhering to the proper
steps:

 Creating the empty table.

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.

Drop Location Rendering


This is a more advanced topic and most people do not need to worry about it. However, if you have a
custom component you will need to handle the drop location rendering yourself.

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.

Here is an example of listening for the dropLocation property:

class Repainter extends PropertyChangeListener {


public void propertyChange(PropertyChangeEvent pce) {
repaintDropLocation(pce.getOldValue());
repaintDropLocation(pce.getNewValue());
}
}

comp.addPropertyChangeListener("dropLocation", newRepainter());

Here is an example of the paintComponent approach:

public void paintComponent(Graphics g) {


super.paintComponent(g);

DropLocation loc= getDropLocation();


if (loc == null) {
return;
}

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:

1. Compile and run the TopLevelTransferHandlerDemo example, consult the example


index if you would like to download a zip file structured for NetBeans.
2. Drag a file from your native desktop or file system and drop it on the blue document
area to the right. The file is opened and a frame filled with its contents will appear.
The document area, a JDesktopPane, contains a transfer handler that supports import
of javaFileListFlavor.
3. Drag another file and attempt to drop it on the document area. You will find that you
cannot drop it on top of the frame displaying the last file. You also cannot drop it on
the list, the menu, or the toolbar. The only place you can drop is the blue portion of
the document area or on the menu bar of a previously opened frame. Inside each
content frame there is a text component's transfer handler that doesn't understand a
file drop — you can drop text into that area, but not a file.
4. From the menu, choose Demo->Use Top-Level TransferHandler to install the transfer
handler on the top-level container — a JFrame.
5. Try dragging over the demo again. The number of areas that accept drops has
increased. You can now drop most anywhere on the application including the menu
bar, toolbar, the frame's title bar, except for the list (on the left) or the content area of
a previously opened file. Neither the JList's nor the text area's transfer handlers
know how to import files.
6. Disable the transfer handlers on those remaining components by choosing Demo-
>Remove TransferHandler from List and Text from the menu.
7. Drag over the demo again. You can now drop a file anywhere on the application!
8. From the menu, choose Demo->Use COPY Action.
9. Drag over the demo again. Note that the mouse cursor now shows the COPY cursor
— this provides more accurate feedback because a successful drop does not remove
the file from the source. The target can be programmed to select from the available
drop actions as described in Choosing the Drop Action.

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.

Here is the source code for TopLevelTransferHandlerDemo.java:

/**
* Demonstration of the top-level {@code TransferHandler}
* support on {@code JFrame}.
*
* @author Shannon Hickey
*/
public class TopLevelTransferHandlerDemo extends JFrame {

private static boolean DEMO = false;

private JDesktopPane dp = new JDesktopPane();


private DefaultListModel listModel = new DefaultListModel();
private JList list = new JList(listModel);
private static int left;
private static int top;
private JCheckBoxMenuItem copyItem;
private JCheckBoxMenuItem nullItem;
private JCheckBoxMenuItem thItem;

private class Doc extends InternalFrameAdapter implements ActionListener {


String name;
JInternalFrame frame;
TransferHandler th;
JTextArea area;

public Doc(File file) {


this.name = file.getName();
try {
init(file.toURI().toURL());
} catch (MalformedURLException e) {
e.printStackTrace();
}
}

public Doc(String name) {


this.name = name;
init(getClass().getResource(name));
}

private void init(URL url) {


frame = new JInternalFrame(name);
frame.addInternalFrameListener(this);
listModel.add(listModel.size(), this);

area = new JTextArea();


area.setMargin(new Insets(5, 5, 5, 5));

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();
}

public void internalFrameClosing(InternalFrameEvent event) {


listModel.removeElement(this);
nullItem.removeActionListener(this);
}

public void internalFrameOpened(InternalFrameEvent event) {


int index = listModel.indexOf(this);
list.getSelectionModel().setSelectionInterval(index, index);
}

public void internalFrameActivated(InternalFrameEvent event) {


int index = listModel.indexOf(this);
list.getSelectionModel().setSelectionInterval(index, index);
}

public String toString() {


return name;
}

public void select() {


try {
frame.toFront();
frame.setSelected(true);
} catch (java.beans.PropertyVetoException e) {}
}

956
public void actionPerformed(ActionEvent ae) {
setNullTH();
}

public void setNullTH() {


if (nullItem.isSelected()) {
area.setTransferHandler(null);
} else {
area.setTransferHandler(th);
}
}
}

private TransferHandler handler = new TransferHandler() {


public boolean canImport(TransferHandler.TransferSupport support) {
if (!support.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
return false;
}

if (copyItem.isSelected()) {
boolean copySupported = (COPY & support.getSourceDropActions())
== COPY;

if (!copySupported) {
return false;
}

support.setDropAction(COPY);
}

return true;
}

public boolean importData(TransferHandler.TransferSupport support) {


if (!canImport(support)) {
return false;
}

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;
}
};

private static void incr() {


left += 30;
top += 30;
if (top == 150) {
top = 0;
}

957
}

public TopLevelTransferHandlerDemo() {
super("TopLevelTransferHandlerDemo");
setJMenuBar(createDummyMenuBar());
getContentPane().add(createDummyToolBar(), BorderLayout.NORTH);

JSplitPane sp = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, list, dp);


sp.setDividerLocation(120);
getContentPane().add(sp);
//new Doc("sample.txt");
//new Doc("sample.txt");
//new Doc("sample.txt");

list.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

list.addListSelectionListener(new ListSelectionListener() {
public void valueChanged(ListSelectionEvent e) {
if (e.getValueIsAdjusting()) {
return;
}

Doc val = (Doc)list.getSelectedValue();


if (val != null) {
val.select();
}
}
});

final TransferHandler th = list.getTransferHandler();

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);
}

private static void createAndShowGUI(String[] args) {


try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
}

TopLevelTransferHandlerDemo test = new TopLevelTransferHandlerDemo();


test.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
if (DEMO) {
test.setSize(493, 307);
} else {

958
test.setSize(800, 600);
}
test.setLocationRelativeTo(null);
test.setVisible(true);
test.list.requestFocus();
}

public static void main(final String[] args) {


SwingUtilities.invokeLater(new Runnable() {
public void run() {
//Turn off metal's use of bold fonts
UIManager.put("swing.boldMetal", Boolean.FALSE);
createAndShowGUI(args);
}
});
}

private JToolBar createDummyToolBar() {


JToolBar tb = new JToolBar();
JButton b;
b = new JButton("New");
b.setRequestFocusEnabled(false);
tb.add(b);
b = new JButton("Open");
b.setRequestFocusEnabled(false);
tb.add(b);
b = new JButton("Save");
b.setRequestFocusEnabled(false);
tb.add(b);
b = new JButton("Print");
b.setRequestFocusEnabled(false);
tb.add(b);
b = new JButton("Preview");
b.setRequestFocusEnabled(false);
tb.add(b);
tb.setFloatable(false);
return tb;
}

private JMenuBar createDummyMenuBar() {


JMenuBar mb = new JMenuBar();
mb.add(createDummyMenu("File"));
mb.add(createDummyMenu("Edit"));
mb.add(createDummyMenu("Search"));
mb.add(createDummyMenu("View"));
mb.add(createDummyMenu("Tools"));
mb.add(createDummyMenu("Help"));

JMenu demo = new JMenu("Demo");


demo.setMnemonic(KeyEvent.VK_D);
mb.add(demo);

thItem = new JCheckBoxMenuItem("Use Top-Level TransferHandler");


thItem.setMnemonic(KeyEvent.VK_T);
demo.add(thItem);

nullItem = new JCheckBoxMenuItem("Remove TransferHandler from List and


Text");
nullItem.setMnemonic(KeyEvent.VK_R);
demo.add(nullItem);

copyItem = new JCheckBoxMenuItem("Use COPY Action");


copyItem.setMnemonic(KeyEvent.VK_C);
demo.add(copyItem);

959
return mb;
}

private JMenu createDummyMenu(String str) {


JMenu menu = new JMenu(str);
JMenuItem item = new JMenuItem("[Empty]");
item.setEnabled(false);
menu.add(item);
return menu;
}
}

Adding Cut, Copy and Paste (CCP)


So far our discussion has centered mostly around drag and drop support. However, it is an easy
matter to hook up cut or copy or paste (ccp) to a transfer handler. This requires the following steps:

 Ensure a transfer handler is installed on the component.


 Create a manner by which the TransferHandler's ccp support can be invoked. Typically this
involves adding bindings to the input and action maps to have the TransferHandler's ccp
actions invoked in response to particular keystrokes.
 Create ccp menu items and/or buttons. (This step is optional but recommended.) This is easy
to do with text components but requires a bit more work with other components, since you
need logic to determine which component to fire the action on. See CCP in a non-Text
Component for more information.
 Decide where you want to perform the paste. Perhaps above or below the current selection.
Install the logic in the importData method.

Next we look at a cut and paste example that feature a text component.

CCP in a Text Component


If you are implementing cut, copy and paste using one of the Swing text components (text field,
password field, formatted text field, or text area) your work is very straightforward. These text
components utilize the DefaultEditorKit which provides built-in actions for cut, copy and paste.
The default editor kit also handles the work of remembering which component last had the focus.
This means that if the user initiates one of these actions using the menu or a keyboard equivalent, the
correct component receives the action — no additional code is required.

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);

menuItem = new JMenuItem(new DefaultEditorKit.CutAction());


menuItem.setText("Cut");
menuItem.setMnemonic(KeyEvent.VK_T);
mainMenu.add(menuItem);

menuItem = new JMenuItem(new DefaultEditorKit.CopyAction());


menuItem.setText("Copy");
menuItem.setMnemonic(KeyEvent.VK_C);
mainMenu.add(menuItem);

menuItem = new JMenuItem(new DefaultEditorKit.PasteAction());


menuItem.setText("Paste");
menuItem.setMnemonic(KeyEvent.VK_P);
mainMenu.add(menuItem);

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:

menuItem = new JMenuItem("Cut");


menuItem.setActionCommand((String)TransferHandler.getCutAction().
getValue(Action.NAME));
menuItem.addActionListener(actionListener);
menuItem.setAccelerator(
KeyStroke.getKeyStroke(KeyEvent.VK_X, ActionEvent.CTRL_MASK));
menuItem.setMnemonic(KeyEvent.VK_T);
mainMenu.add(menuItem);

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:

// only required if you have not set the menu accelerators


InputMap imap = this.getInputMap();
imap.put(KeyStroke.getKeyStroke("ctrl X"),
TransferHandler.getCutAction().getValue(Action.NAME));
imap.put(KeyStroke.getKeyStroke("ctrl C"),
TransferHandler.getCopyAction().getValue(Action.NAME));
imap.put(KeyStroke.getKeyStroke("ctrl V"),
TransferHandler.getPasteAction().getValue(Action.NAME));

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 class TransferActionListener implements ActionListener,


PropertyChangeListener {
private JComponent focusOwner = null;

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;
}
}

public void actionPerformed(ActionEvent e) {


if (focusOwner == null)
return;
String action = (String)e.getActionCommand();
Action a = focusOwner.getActionMap().get(action);
if (a != null) {
a.actionPerformed(new ActionEvent(focusOwner,
ActionEvent.ACTION_PERFORMED,
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.

Using and Creating a DataFlavor


The DataFlavor class allows you to specify the content type of your data. You need to specify a
DataFlavor when fetching the data from the importData method. Several flavor types are
predefined for you:

 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:

new DataFlavor(ArrayList.class, "ArrayList");


To create a data flavor for an integer array:
new DataFlavor(int[].class, "Integer Array");

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:

String colorType = DataFlavor.javaJVMLocalObjectMimeType +


";class=java.awt.Color";
DataFlavor colorFlavor = new DataFlavor(colorType);

To create a data flavor for an ArrayList that would work only within your application:

new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType +
";class=java.util.ArrayList");

To create a data flavor for an integer array:

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.

When you create an array of DataFlavors to be returned from the Transferable's


getTransferDataFlavors method, the flavors should be inserted in preferred order, with the most
preferred appearing at element 0 of the array. Genereally the preferred order is from the richest, or
most complex, form of the data down to the simpleset — the form most likely to be understood by
other objects.

Putting it All Together - DnD and CCP


We have shown how to implement drag and drop support and how to implement cut, copy, paste
support. How do you combine both in one component?

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):

public boolean importData(TransferHandler.TransferSupport info) {


String data = null;

//If we cannot handle the import, bail now.


if (!canImport(info)) {
return false;
}

JList list = (JList)info.getComponent();


DefaultListModel model = (DefaultListModel)list.getModel();
//Fetch the data -- bail if this fails
try {
data =
(String)info.getTransferable().getTransferData(DataFlavor.stringFlavor);
} catch (UnsupportedFlavorException ufe) {
System.out.println("importData: unsupported data flavor");
return false;
} catch (IOException ioe) {
System.out.println("importData: I/O exception");
return false;
}

if (info.isDrop()) { //This is a drop


JList.DropLocation dl = (JList.DropLocation)info.getDropLocation();

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.

Here are some specific entries you may want to study:

 First Class Drag and Drop Support in JDK 6


 Location-Sensitive Drag and Drop
 Top-Level Drop
 Choosing the Drop Action and Other Changes to Drag and Drop
 Enable Dropping into Empty JTables
 Improved Drag Gesture

Solving Common Data Transfer Problems


This section discusses problems that you might encounter when using data transfer.

Problem: My drag gesture recognizer is not working properly with table/list/tree/text.

Do not use your own drag gesture recognizers with these components. Use setDragEnabled(true)
and a TransferHandler.

Problem: I am unable to drop data onto my empty JTable.

966
You need to call seFillsViewportHeight(true) on the table. See Empty Table Drop for more
information.

967

Das könnte Ihnen auch gefallen