The JFC Swing Tutorial: A Guide to Constructing GUIs (2nd Edition)
< Day Day Up > |
Most programs can benefit from the ability to transfer information between components , between Java applications, or between Java and native applications. Transferring data takes two forms:
The TransferHandler object, the heart of the data transfer system, is described in more detail later. The arrows in Figures 13 and 14 show the path of the data. Many Swing components provide out-of-the-box support for transferring data, as shown in Table 8. Table 8. Data Transfer Support
[a] Enabled by invoking component.setDragEnabled(true) on the component. [b] Imports and exports data of type java.awt. Color . [c] Exports a list of file names as java.io.File objects (preferred) and as strings for components that don't accept File objects. The File Name text field in the file chooser accepts strings; the browser in the file chooser doesn't accept data. Note that as of release 1.4, clipboard copy from a JFileChooser is broken and actually causes the file to be moved when it is pasted. You may want to track bug #4915992 online at: http://developer.java.sun.com/developer/bugParade/ bugs /4915992.html.
Version Note: This section describes the drag-and-drop architecture implemented in release 1.4. Prior to 1.4, AWT support for data transfer was not well integrated into Swing components.
The data transfer mechanism is built into every JComponent . For Swing components with an empty space in Table 8, only a small amount of code is needed to customize the support. Support can easily be added to JComponent s not listed in the table so that they can fully participate in data transfer. A Visual Guide to Drag-and-Drop Cursor Icons
Before delving into drag and drop further, it's useful to take a look at the various cursor icons you may encounter when initiating a drag operation. We expect the Solaris and Linux cursor icons to change for release 1.5, but Table 9 is a guide as of release 1.4. Table 9. Cursor Icons for Drag and Drop
On a component supporting both copy and move, a normal drag from the component performs a move and a Control-drag performs a copy. The drag behavior from a native application to a Java application is platform-dependent. If only one of the operations is supported, a normal drag performs it. For more information on the behavior of the drop action, see the class specification for DragSourceDragEvent . [20] [20] DragSourceDragEvent API documentation: http://java.sun.com/j2se/1.4.2/docs/api/java/awt/dnd/DragSourceDragEvent.html. Introduction to Data Transfer Support
The simple demo BasicDnD illustrates default drag and drop behavior for several Swing components. (See Figure 15.) At startup the components don't have drag turned on, but a check box allows you to enable dragging on the fly. At startup, even though drag isn't enabled, many of the components do support the cut/copy/paste of text using key bindings. Figure 15. The BasicDnD example. (This image has been modified to fit the available space.)
Try This:
When the Turn on Drag and Drop check box is checked, BasicDnD demonstrates drag and drop behavior that becomes available to a component with the following line of code: component.setDragEnabled(true); Many components support text cut/copy/paste using the keyboard bindings Control-X, Control-C, and Control-V, respectively. This is because a JTextComponent installs the cut/copy/paste key bindings and action map when it's created. You only need to add a bit more code to create a menu with Cut, Copy, and Paste menu items and tie those items to the default text support. We show you how to do that in the DragColorTextFieldDemo example, which is discussed in Replacing Default Support: Color and Text (page 565). Cut/copy/paste is further discussed in Adding Cut/Copy/Paste Support (page 572). At the heart of the data transfer mechanism is the TransferHandler [22] class, which provides an easy mechanism for transferring data to and from a JComponent . The data to be transferred is bundled into an object that implements the Transferable [23] interface. The components in Table 8, "Data Transfer Support" (page 546), are provided with default transfer handlers, but a transfer handler can be created and installed on any component using the JComponent method setTransferHandler : [22] TransferHandler API documentation: http://java.sun.com/j2se/1.4.2/docs/api/javax/swing/TransferHandler.html. [23] Transferable API documentation: http://java.sun.com/j2se/1.4.2/docs/api/java/awt/datatransfer/Transferable.html. component.setTransferHandler(new MyTransferHandler()); 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. If you install a custom TransferHandler on a Swing component, the default support is replaced . For example, if you replace JTextField 's TransferHandler with a handler for colors only, you'll disable its ability to support text import and export. This means that if you must replace a default TransferHandler for example, one that handles textyou'll need to reimplement the text import and export ability. This doesn't need to be as extensive as what Swing provides; it could be as simple as supporting the String-Flavor data flavor, depending on your application's needs. DragColorTextFieldDemo gives an example of this. You might also want to watch RFE #4830695, [24] which requests the ability to add data import on top of an existing TransferHandler . [24] You can track this bug online at: http://developer.java.sun.com/developer/bugParade/bugs/4830695.html. The remainder of this section describes how to use data transfer in a variety of ways. Here's a list of some common scenarios and where you can find more information: How do I provide drop support for those components in Table 8 (page 546) that don't have a checkmark in the drop column? You need to implement a custom transfer handler to provide drop support. See Extending Default DnD Support (page 553) for an example. I want my component to import only. How do I do that? You need to provide a custom transfer handler with implementations for the canImport and importData methods . The DragColorDemo example in Importing a New Flavor: Color (page 561) does this. How do I create a component that can accept multiple types of data? A transfer handler can be created to accept more than one type of data. Replacing Default Support: Color and Text (page 565) shows the example DragColorTextFieldDemo , which installs a custom transfer handler on a JTextField to import both color and text and to export text. Also, DragFileDemo in Importing a New Flavor: Files (page 566) installs a transfer handler on the text area/tabbed pane that imports both files and strings. How do I create a custom transfer handler to import/export a nonstandard type of data? Specifying the Data Format (page 558) describes how to create a data flavor with a variety of data types. Also, the DragListDemo example in Data Transfer with a Custom DataFlavor (page 571) shows how to transfer data in the ArrayList format. How do I make data transfer work with my custom component? Data Transfer with a Custom Component (page 568) discusses the requirements of making a custom component work with the data transfer system. How do I enable the cut/copy/paste bindings? Adding Cut/Copy/Paste Support (page 572) describes how to enable the built-in cut/copy/paste support for text components. Implementing cut/copy/paste for nontext components is also covered. How do I obtain the drop position in the destination component? You can obtain the drop location by way of the component's selection. You'll notice that the selection changes in a component as you drag over it. For lists, tables, and trees you can query the current selection at drop time to find the drop position. For text components, you can query the position of the caret. There is an example of this in ArrayListTransferHandler , part of the DragListDemo example. You might also want to watch RFE #4468566, [25] which requests a better way of indicating (and displaying) the drop location without changing the selection. [25] Track this bug online at: http://developer.java.sun.com/developer/bugParade/bugs/4468566.html. This section has been a brief introduction to the Swing data transfer mechanism. If you want more details, see the "Swing Data Transfer" document in the release notes for your particular J2SE release. [26] [26] In the 1.4.2 release notes this document is at: http://java.sun.com/j2se/1.4.2/docs/guide/swing/1.4/dnd.html. A Simple Example: Adding DnD to JLabel
By default, the JLabel component doesn't support drag or drop, but it's a fairly simple exercise to add this support, as the following demo shows. (See Figure 16.) Figure 16. A screenshot of the LabelDnD example.
Try This:
While it's possible to extend this example to show copy and paste, JLabel doesn't have bindings for copy and paste and, by default, doesn't receive the focus that is required to support this feature. Here's the code that creates the label and installs a transfer handler on it: label = new JLabel("I'm a Label!", SwingConstants.LEADING); label.setTransferHandler(new TransferHandler("text")); MouseListener listener = new DragMouseAdapter(); label.addMouseListener(listener); To add drag support to JLabel or any custom component, you must add the ability to detect activity on the mouse. LabelDnD implements a mouse listener for this. When the mouse is pressed, the transfer handler initiates the drag from the label by invoking exportAsDrag with the COPY argument: public class DragMouseAdapter extends MouseAdapter { public void mousePressed(MouseEvent e) { JComponent c = (JComponent)e.getSource(); TransferHandler handler = c.getTransferHandler(); handler.exportAsDrag(c, e, TransferHandler.COPY); } } Specifying the Data Format (page 558) will explain what the following call does: new TransferHandler("text") Extending Default DnD Support
You saw in the Table 8, "Data Transfer Support," on page 546, that several components don't support drop by default. The reason for this is that there is no all-purpose way to handle a drop on them. 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 don't know what type of model is behind the treeit might not be mutable. However, while Swing doesn't provide a default implementation, the framework for drop is there. All you need is to provide a custom TransferHandler that deals with the actual transfer of data. The following example, ExtendedDnDDemo (see Figure 17), tweaks the default drag and drop behavior for two components, JList and JTable . This demo shows how to:
Figure 17. A screenshot of the ExtendedDnDDemo example.
If you've run the BasicDnD example previously, you'll see that this tweaked behavior is slightly different than the default Swing behavior, which does not use commas as a separator. Try This:
The code for the example's main class is in ExtendedDnDDemo.java . StringTransfer-Handler , an abstract subclass of TransferHandler , defines three abstract methods for importing and exporting the strings: exportString , importString , and cleanup . StringTransferHandler also overrides the standard TransferHandler methods: importData and canImport are required to import data; getSourceActions , createTransferable , and exportDone are required for export. ( exportDone may not be necessary if you only implement copy and therefore don't need to remove data from the source as you would for a move.) The abstract methods importString , exportString , and cleanup are called by the importData , createTransferable , and exportDone methods, respectively. Here's the code for StringTransferHandler.java : public abstract class StringTransferHandler extends TransferHandler { protected abstract String exportString(JComponent c); protected abstract void importString(JComponent c, String str); protected abstract void cleanup(JComponent c, boolean remove); protected Transferable createTransferable(JComponent c) { return new StringSelection(exportString(c)); } public int getSourceActions(JComponent c) { return COPY_OR_MOVE; } public boolean importData(JComponent c, Transferable t) { if (canImport(c, t.getTransferDataFlavors())) { try { String str = (String)t.getTransferData(DataFlavor.stringFlavor); importString(c, str); return true; } catch (UnsupportedFlavorException ufe) { } catch (IOException ioe) { } } return false; } protected void exportDone(JComponent c, Transferable data, int action){ cleanup(c, action == MOVE); } public boolean canImport(JComponent c, DataFlavor[] flavors) { for (int i = 0; i < flavors.length; i++) { if (DataFlavor.stringFlavor.equals(flavors[i])) { return true; } } return false; } } The StringSelection [29] class implements the Transferable interface and handles the details of bundling the data for transport. [29] StringSelection API documentation: http://java.sun.com/j2se/1.4.2/docs/api/java/awt/datatransfer/StringSelection.html. Two custom subclasses of StringTransferHandler ListTransferHandler and TableTransferHandler implement the abstract importString , exportString , and cleanup methods and deal with the specifics of a list and a table, respectively. Here's the code for ListTransferHandler : public class ListTransferHandler extends StringTransferHandler { private int[] indices = null; private int addIndex = -1; //Location where items were added private int addCount = 0; //Number of items added. //Bundle up the selected items in the list //as a single string, for export. protected String exportString(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 buff.toString(); } //Take the incoming string and wherever there is a //newline, break it into a separate item in the list. protected void importString(JComponent c, String str) { JList target = (JList)c; DefaultListModel listModel = (DefaultListModel)target.getModel(); int index = target.getSelectedIndex(); //Prevent the user from dropping data back on itself. //For example, if the user is moving items #4,#5,#6 and #7 and //attempts to insert the items after item #5, this would //be problematic when removing the original items. //So this is not allowed. if (indices != null && index >= indices[0] - 1 && index <= indices[indices.length - 1]) { indices = null; return; } int max = listModel.getSize(); if (index < 0) { index = max; } else { index++; if (index > max) { index = max; } } addIndex = index; String[] values = str.split("\n"); addCount = values.length; for (int i = 0; i < values.length; i++) { listModel.add(index++, values[i]); } } //If the remove argument is true, the drop has been //successful and it's time to remove the selected items //from the list. If the remove argument is false, it //was a Copy operation and the original list is left //intact. protected void cleanup(JComponent c, boolean remove) { if (remove && indices != null) { JList source = (JList)c; DefaultListModel model = (DefaultListModel)source.getModel(); //If we are moving items around in the same list, we //need to adjust the indices accordingly, since those //after the insertion point have moved. if (addCount > 0) { for (int i = 0; i < indices.length; i++) { if (indices[i] > addIndex) { indices[i] += addCount; } } } for (int i = indices.length - 1; i >= 0; i--) { model.remove(indices[i]); } } indices = null; addCount = 0; addIndex = -1; } } Note that importString uses the split utility method of String to divide the incoming text so that wherever a newline occurs a new list item is created. To support moving text from a list, exportDone calls cleanup , which removes the dragged items from the original list. The cleanup method has special handling for items within the same list and data moved to a higher position in the list (with a smaller index). cleanup is called after the data has been inserted into the list, which changes the indices of the items to be deleted. When the indices of the original items to be moved change, they must be adjusted accordingly before they can be deleted. The example's custom table transfer handler class, TableTransferHandler , is implemented in a similar manner, though it's slightly more complex because it has support for text that's newline- and comma-delimited. Specifying the Data Format
Creating a TransferHandler can be as simple as using the constructor. For example, in the LabelDnD demo the label supports both importing and exporting String s to and from its text property with the following line of code: label.setTransferHandler(new TransferHandler("text")); When using the property name form of the constructor, there must be a getProperty method in the component's API to export data and a setProperty method to import it. The label's transfer handler works because JLabel has a getText method. This works with any property so that, if you had instead created the label's transfer handler like this: label.setTransferHandler(new TransferHandler("foreground")); you would be able to drag a color from the color chooser and drop it on the label; the label's text color would change because label has a setForeground method that requires a java.awt.Color object. We have another version of LabelDnD , called LabelDnD2 , which demonstrates using the TransferHandler constructor, this time for the label's foreground property. (See Figure 18.) Figure 18. The LabelDnD2 example.
Try This:
If you can't use the property name form of the TransferHandler constructor, the Data-Flavor [31] class allows you to specify the content-type of your data for both your TransferHandler and, if necessary, your Transferable . Three flavor types are predefined for you: [31] DataFlavor API documentation: http://java.sun.com/j2se/1.4.2/docs/api/java/awt/datatransfer/DataFlavor.html.
For example, a TransferHandler imports String data with this line of code: String str = (String)t.getTransferData(DataFlavor.stringFlavor); Or it imports a java.awt.Image using imageFlavor with this line of code: Image image = (Image)t.getTransferData(DataFlavor.imageFlavor); If you require a flavor other than these predefined types, you need to create your own. The format for specifying a data flavor is: DataFlavor(Class representationClass, String humanPresentableName); 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 in this manner 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 not everything is Serializable , you'll see a NotSerializableException during drop or copy to the clipboard. For more information, see The Java Tutorial's "Object Serialization" section [32] in the Essential Java Classes trail. [32] The Object Serialization section is on the CD at: JavaTutorial/essential/io/serialization.html . 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 , use this code: String colorType = DataFlavor.javaJVMLocalObjectMimeType + ";class=java.awt.Color"; DataFlavor colorFlavor = new DataFlavor(colorType); To create a data flavor for an ArrayList use: new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType + ";class=java.util.ArrayList"); To transfer the data as an integer array use: new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType + ";class=\"" + int[].class.getName() + "\""); You'll see that a MIME type containing special characters , such as [ and ;, must have those characters enclosed in quotes. Finally, 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 together, such as the ArrayList and integer array flavors, or you can create a TransferHandler that accepts different types of data, such as color and text, as you'll see later. When you create the array of DataFlavors to be returned from Transferable 's get-TransferDataFlavors method, the flavors should be inserted in preferred order, with the most preferred appearing at element 0 of the array. Generally the preferred order is from the richest or most complex form down to the simplest, that is, the form most likely to be understood by other objects. See the "Components That Support DnD" table in the release notes for your particular J2SE release for which data types each component imports and exports. [33] [33] In the v1.4.2 release notes the "Components That Support DnD" table is at: http://java.sun.com/j2se/1.4.2/docs/guide/swing/1.4/dnd.html#DefaultTransferHandlerSupport. Importing a New Flavor: Color
The only Swing component that can, by default, import or export color is JColorChooser . Previously we described how you can create a transfer handler that will transfer data as specified by a named property. This is easy to do, but it has limited functionality. For example, if you specify the foreground property, a drop only changes the color of the text; it won't change the background color. And if your component drags and drops text by default, replacing the transfer handler in this manner causes the component to lose this default ability. To solve this problem you need to write a custom TransferHandler . We show how to create one that can be installed on a component so that it can accept color on a drop. DragColorDemo (see Figure 19) specifically shows how you can drop a color onto the foreground or background of a button or label. Figure 19. A screenshot of the DragColorDemo example.
Try This:
The example's main class can be found in DragColorDemo.java . The custom transfer handler is defined in ColorTransferHandler.java . In this example, we're only implementing import functionality and therefore only need to implement the methods canImport and importData . A single instance of the ColorTransferHandler is created and shared by all nine buttons and the label. Here's a snippet of code where the transfer handler is created and installed on the buttons : colorHandler = new ColorTransferHandler(); ... for (int i = 0; i < 9; i++) { JButton tmp = new JButton("Button "+i); tmp.setTransferHandler(colorHandler); .... } Here's the code for ColorTransferHandler : class ColorTransferHandler extends TransferHandler { //The data type exported from JColorChooser. String mimeType = DataFlavor.javaJVMLocalObjectMimeType + ";class=java.awt.Color"; DataFlavor colorFlavor; private boolean changesForegroundColor = true; ColorTransferHandler() { //Try to create a DataFlavor for color. try { colorFlavor = new DataFlavor(mimeType); } catch (ClassNotFoundException e) { } } /** * Overridden to import a Color if it is available. * getChangesForegroundColor is used to determine whether * the foreground or the background color is changed. */ public boolean importData(JComponent c, Transferable t) { if (hasColorFlavor(t.getTransferDataFlavors())) { try { Color col = (Color)t.getTransferData(colorFlavor); if (getChangesForegroundColor()) { c.setForeground(col); } else { c.setBackground(col); } return true; } catch (UnsupportedFlavorException ufe) { } catch (IOException ioe) { } } return false; } /** * Does the flavor list have a Color flavor? */ protected boolean hasColorFlavor(DataFlavor[] flavors) { if (colorFlavor == null) { return false; } for (int i = 0; i < flavors.length; i++) { if (colorFlavor.equals(flavors[i])) { return true; } } return false; } /** * Overridden to include a check for a color flavor. */ public boolean canImport(JComponent c, DataFlavor[] flavors) { return hasColorFlavor(flavors); } protected void setChangesForegroundColor(boolean flag) { changesForegroundColor = flag; } protected boolean getChangesForegroundColor() { return changesForegroundColor; } } The ColorTransferHandler is implemented to support JavaJVMlocalObjectMimeType with the representation class class=java.awt.Color , which is the mechanism JColorChooser uses to export color. For a discussion of how data is specified to the transfer mechanism, see the previous section Specifying the Data Format (page 558). Replacing Default Support: Color and Text
The DragColorDemo example shown in the preceding section replaces the component's current transfer handler. When it installs the ColorTransferHandler on its components, it clobbers any preexisting transfer handler. This isn't so much of a problem with buttons or labels, which don't have any predefined data to transfer, but it can be a problem when you want to add the ability to import/export color on top of a component that already imports and/or exports other data, such as text. As discussed in Introduction to Data Transfer Support (page 548), if you install a custom transfer handler on a component that has a Swing-provided transfer handler, such as JTextField , you need to reimplement the Swing support. We've provided a version of DragColorDemo , called DragColorTextFieldDemo , that creates a transfer handler that accepts color and also reimplements the clobbered support for text. (See Figure 20.) Figure 20. The DragColorTextFieldDemo
Try This:
This transfer handler descends from ColorTransferHandler , which was used in the DragColorDemo example. Since ColorAndTextTransferHandler must export data, it implements createTransferable , getSourceActions , and exportDone (in addition to the two methods it provides for import support). The code is too long to include here, but you can find the main class's source code in DragColorTextFieldDemo.java . The custom transfer handler can be found in ColorAndTextTransferHandler.java . Importing a New Flavor: Files
The JFileChooser exports the javaFileListFlavor a List [36] of File [37] objects discussed in Specifying the Data Format (page 558). The file chooser also exports its file names as a list of String sboth in text/plain and text/html formats. Dragging a file from a drag-enabled file chooser and dropping it on a JTextArea , for example, causes the file name to be inserted into the text area but not the contents of the file. However, a custom transfer handler that knows about javaFileListFlavor can be installed to accept the file list provided by a file chooser, open the file, read the contents, and display the contents in the text area. We have provided an example that does this. Because this example reads files from your local file system, launching the demo via Java Web Start will bring up a warning panel requiring permission before executing the application. If you prefer, you can download the application and run it locally. [36] List API documentation: http://java.sun.com/j2se/1.4.2/docs/api/java/util/List.html. [37] File API documentation: http://java.sun.com/j2se/1.4.2/docs/api/java/io/File.html. Figure 21. The DragFileDemo example.
Try This:
The code is too long to include here, but you can find the main class's source in DragFileDemo.java . The custom TransferHandler for the text area is in FileAndTextTransferHandler.java . DragFileDemo doesn't do anything unusual except embed the file chooser in the main window rather than run it from a dialog. This allows the file chooser to be interactive without blocking the rest of the application. A separate class, TabbedPaneController , manages the JTextArea / JTabbedPane that displays the contents of the files. In the constructor for DragFileDemo , the tab pane controller is created and installed like this: JTabbedPane tabbedPane = new JTabbedPane(); JPanel tabPanel = new JPanel(new BorderLayout()); ... tpc = new TabbedPaneController(tabbedPane, tabPanel); TabbedPaneController.java contains the implementation for the tabbed pane controller. The FileandTextTransferHandler installed on the text area imports two flavors: javaFileListFlavor and stringFlavor . As you saw before, stringFlavor is necessary because the new transfer handler clobbers the default behavior for the text area and this re-implements its basic behavior. In the importData method for the transfer handler, the code first checks to see if files are being imported. If so, the files are opened in a BufferedReader and the contents are appended. If the imported data isn't files, it then checks for strings. Data Transfer with a Custom Component
We've seen how to customize data transfer for standard Swing components, but how do you add data transfer to a custom component? The simplest data transfer to implement is drag and drop:
With a bit more code, cut/copy/paste support can be added. The DragPictureDemo example shows how to implement full data transfer with a custom component. (See Figure 22.) Figure 22. The DragPictureDemo example (modified to fit available space).
Try This:
You can find the main class's source code in DragPictureDemo.java you may recognize the basic functionality from the TrackFocusDemo example in the How to Use the Focus Subsystem (page 583) section. The custom component DTPicture is a subclass of the Picture component, modified to support data transfer. A new class is PictureTransferHandler , the custom transfer handler for DTPicture . The custom component DTPicture enables drag and drop by implementing the Mouse-MotionListener interface. MouseMotionListener allows you to detect mouse motion by implementing the mouseDragged method. We arbitrarily chose a displacement of 5 pixels to determine whether the user is actually attempting to drag, as opposed to clicking on a picture. Once the cursor has moved a distance of 5 pixels in either direction while the mouse button is down, the transfer handler is called to initiate the drag. The mouseDragged method also checks to see if the Control button is being pushed on the keyboardif it is, the action is a copy; otherwise , the action is a move. The mousePressed , mouseDragged , and mouse-Released methods in DTPicture look like this: MouseEvent firstMouseEvent = null; public void mousePressed(MouseEvent e) { //Don't bother to drag if there is no image. if (image == null) return; firstMouseEvent = e; e.consume(); } public void mouseDragged(MouseEvent e) { //Don't bother to drag if the component displays no image. if (image == null) return; if (firstMouseEvent != null) { e.consume(); //If they are holding down the control key, COPY rather than MOVE int ctrlMask = InputEvent.CTRL_DOWN_MASK; int action = ((e.getModifiersEx() & ctrlMask) == ctrlMask) ? TransferHandler.COPY : TransferHandler.MOVE; int dx = Math.abs(e.getX() - firstMouseEvent.getX()); int dy = Math.abs(e.getY() - firstMouseEvent.getY()); //Arbitrarily define a 5-pixel shift as the //official beginning of a drag. if (dx > 5 dy > 5) { //This is a drag, not a click. JComponent c = (JComponent)e.getSource(); //Tell the transfer handler to initiate the drag. TransferHandler handler = c.getTransferHandler(); handler.exportAsDrag(c, firstMouseEvent, action); firstMouseEvent = null; } } } public void mouseReleased(MouseEvent e) { firstMouseEvent = null; } The Adding Cut/Copy/Paste Support (page 572) section discusses how DragPictureDemo implements cut, copy, and paste with and without menu support. The PictureTransferHandler class looks very much like other custom transfer handlers except that it transfers data using the built-in support for java.awt.Image s DataFlavor. imageFlavor . For more information, see Specifying the Data Format (page 558). If you're interested in more discussion on the custom Picture component, see Tracking Focus Changes to Multiple Components (page 595) in How to Use the Focus Subsystem (page 583). Data Transfer with a Custom DataFlavor
By now you've seen several examples of transfer handlers that transfer data using conventional formats. This section takes a standard Swing objecta JList and transfers the data using a content-type based on the java.util.ArrayList class. To achieve this, a custom Transferable is created. In order to implement a Transferable you must conform to the Transferable interface and provide implementations for the methods getTransferData , getTransferDataFlavors , and isDataFlavorSupported . The DragListDemo example (see Figure 23) shows how to implement the Transferable interface. Figure 23. The DragListDemo example.
Try This:
The DragListDemo class creates and displays the lists in the usual manner. Installed on each list is a shared instance of a custom transfer handler class called ArrayListTransfer-Handler . Adding Cut/Copy/Paste Support
So far our discussion has centered mostly around drag and drop support. However, it's an easy matter to hook up cut/copy/paste to a transfer handler. The basic steps are:
The DragColorTextFieldDemo , in Replacing Default Support: Color and Text (page 565), shows how to use the default cut/copy/paste text support provided by DefaultEditorKit [41] with the custom TransferHandler installed on the text fields. A nice feature of the DefaultEditorKit methods is that they remember which component had the focus last. Here's the code that creates the Edit menu and uses the cut, copy, and paste Action s defined in DefaultEditorKit to create the menu items: [41] DefaultEditorKit is discussed in Text Component Features (page 64) in Chapter 3. //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; } Hooking up cut/copy/paste support in this manner works with any component that descends from JTextComponent . For any nontext component, you must manually set up the bindings in the input and action maps. The DragPictureDemo example, in Data Transfer with a Custom Component (page 568), shows how to do this. Here's a code snippet from the constructor for the DTPicture component: if (installInputMapBindings) { 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)); } ActionMap map = this.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()); The boolean installInputMapBindings is true in this case and will be further discussed when we show how to add an Edit menu to support cut/copy/paste. While you can implement cut/copy/paste to work exclusively with key bindings, it's considered good GUI design to provide menu items as well. We've provided the DragPictureDemo2 example (see Figure 24), which extends DragPictureDemo with an Edit menu. Figure 24. A screenshot of the DragPictureDemo2 example.
Try This:
The DragPictureDemo2 example creates the Edit menu like this: public JMenuBar createMenuBar() { JMenuItem menuItem = null; JMenuBar menuBar = new JMenuBar(); JMenu mainMenu = new JMenu("Edit"); mainMenu.setMnemonic(KeyEvent.VK_E); TransferActionListener actionListener = new TransferActionListener(); 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); menuItem = new JMenuItem("Copy"); menuItem.setActionCommand((String)TransferHandler.getCopyAction(). getValue(Action.NAME)); menuItem.addActionListener(actionListener); menuItem.setAccelerator( KeyStroke.getKeyStroke(KeyEvent.VK_C, ActionEvent.CTRL_MASK)); menuItem.setMnemonic(KeyEvent.VK_C); mainMenu.add(menuItem); menuItem = new JMenuItem("Paste"); menuItem.setActionCommand((String)TransferHandler.getPasteAction(). getValue(Action.NAME)); menuItem.addActionListener(actionListener); menuItem.setAccelerator( KeyStroke.getKeyStroke(KeyEvent.VK_V, ActionEvent.CTRL_MASK)); menuItem.setMnemonic(KeyEvent.VK_P); mainMenu.add(menuItem); menuBar.add(mainMenu); return menuBar; } This line ties the copy action to the menu item: menuItem.setActionCommand((String)TransferHandler.getCopyAction(). getValue(Action.NAME)); This line defines Control-C as the key binding for the action: menuItem.setAccelerator( KeyStroke.getKeyStroke(KeyEvent.VK_C, ActionEvent.CTRL_MASK)); The keyboard shortcuts, E ( E dit), T (cu T ), C ( C opy), and P ( P aste), are installed by calling setMnemonic . For a discussion of mnemonics versus accelerators, see Enabling Keyboard Operation (page 282) in Chapter 7. When you register the keystroke in the input map yourself, you register it on a per-component basiswhen the component has the focus and the keystroke is typed, the action is fired . When you set a key binding on a menu, the keystroke information is added to a global input map, and the key binding is active all of the time when the window has the focus. For that reason, setting up the input maps on the components yourself is redundant when menu accelerators are used. If you're not using menu accelerators, you need to set up the input map yourself. The DTPicture class can be used by both DragPictureDemo and DragPictureDemo2 because the static property installInputMapBindings allows one demo to set the bindings on the input map and the other to skip that step. For your program, choose one approach or the other, but not both. The final change to DragPictureDemo2 is necessary to ensure that the action goes to the correct component when the user initiates a cut, copy, or paste. The TransferActionListener class is installed as an action listener on the cut/copy/paste menu items and as a property change listener on the keyboard focus manager. Each time the focus owner changes, TransferActionListener keeps track of the new focus owner. When the user initiates a cut, copy, or paste through a menu item, TransferActionListener is notified and then fires the appropriate action on the component that has the focus. Here's the code for Transfer-ActionListener : public class TransferActionListener implements ActionListener, PropertyChangeListener { private JComponent focusOwner = null; public TransferActionListener() { KeyboardFocusManager manager = KeyboardFocusManager. getCurrentKeyboardFocusManager(); manager.addPropertyChangeListener("permanentFocusOwner", this); } 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)); } } } The Data Transfer API
Tables 10 through 14 list the commonly used constructors and methods in data transfer. For more detailed information about the data transfer mechanism, see the "Swing Data Transfer" document in the release notes for your particular J2SE release. [43] You can find API documentation for JComponent at: http://java.sun.com/j2se/1.4.2/docs/api/javax/swing/JComponent.html. The documentation for the TransferHandler class is at: http://java.sun.com/j2se/1.4.2/docs/api/javax/swing/TransferHandler.html. The rest of the API is in the java.awt.datatransfer package, which is documented at: http://java.sun.com/j2se/1.4.2/docs/api/java/awt/datatransfer/package-summary.html. [43] In the v1.4.2 release notes this document is at: http://java.sun.com/j2se/1.4.2/docs/guide/swing/1.4/dnd.html. Table 10. Useful JComponent Methods (All of this API was introduced in release 1.4.)
Table 11. TransferHandler API (All of this API was introduced in release 1.4.)
Table 12. Transferable Classes
Table 13. Transferable Interface API
Table 14. DataFlavor API
Examples That Use Data Transfer
The following table lists examples that use data transfer.
|
< Day Day Up > |