+ All Categories
Home > Documents > Drag and Drop and Data Transfer

Drag and Drop and Data Transfer

Date post: 17-Nov-2014
Category:
Upload: api-3792621
View: 171 times
Download: 0 times
Share this document with a friend
35
933 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:
Transcript
Page 1: Drag and Drop and Data Transfer

933

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:

Page 2: Drag and Drop and Data Transfer

934

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.

Page 3: Drag and Drop and Data Transfer

935

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.

Page 4: Drag and Drop and Data Transfer

936

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.

Page 5: Drag and Drop and Data Transfer

937

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

Page 6: Drag and Drop and Data Transfer

938

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

Page 7: Drag and Drop and Data Transfer

939

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

Page 8: Drag and Drop and Data Transfer

940

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

Page 9: Drag and Drop and Data Transfer

941

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

n JTree.DropLocatio

n JTable.DropLocatio

n JTextComponent.DropLocati

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.

Page 10: Drag and Drop and Data Transfer

942

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.

Page 11: Drag and Drop and Data Transfer

943

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

Page 12: Drag and Drop and Data Transfer

944

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.

Page 13: Drag and Drop and Data Transfer

945

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.

Page 14: Drag and Drop and Data Transfer

946

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

Page 15: Drag and Drop and Data Transfer

947

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

Page 16: Drag and Drop and Data Transfer

948

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

Page 17: Drag and Drop and Data Transfer

949

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.

Page 18: Drag and Drop and Data Transfer

950

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.

Page 19: Drag and Drop and Data Transfer

951

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.

Page 20: Drag and Drop and Data Transfer

952

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.

Page 21: Drag and Drop and Data Transfer

953

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.

Page 22: Drag and Drop and Data Transfer

954

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.

Page 23: Drag and Drop and Data Transfer

955

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;

Page 24: Drag and Drop and Data Transfer

956

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

Page 25: Drag and Drop and Data Transfer

957

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

Page 26: Drag and Drop and Data Transfer

958

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

Page 27: Drag and Drop and Data Transfer

959

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

Page 28: Drag and Drop and Data Transfer

960

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.

Page 29: Drag and Drop and Data Transfer

961

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.

Page 30: Drag and Drop and Data Transfer

962

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

Page 31: Drag and Drop and Data Transfer

963

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.

Page 32: Drag and Drop and Data Transfer

964

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 +

Page 33: Drag and Drop and Data Transfer

965

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

Page 34: Drag and Drop and Data Transfer

966

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.

Page 35: Drag and Drop and Data Transfer

967

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


Recommended