Implementing a Drop Target

Java > Core SWING advanced programming > 8. DRAG-AND-DROP > Implementing a Drag Source

 

Implementing a Drag Source

Now that you've seen how to implement a drop target, the next step is to create a drag source. We've already covered most of the information that you need to build a drag source (refer back to "Architecture of the Java Drag-and-Drop Subsystem" if you need to remind yourself of drag source basics) and, in the next section, we'll see how to build a drag source for a tree that's showing a graphical view of a file system. Here, we'll implement a simple (but not very useful) drag source to show you the basics and to demonstrate how to create a customized Transferable object to allow data to be moved to the drop target.

Before looking at the code, let's first see the drag source in action. Type the following command:

 java AdvancedSwing.Chapters.JLabelDragSource 

This creates a small frame with a single JLabel mounted on it, as shown in Figure 8-13.

Figure 8-13. A drag source for a JLabel.
graphics/08fig13.gif

The JLabel in this example is functioning as a drag source, making its text available to drop targets that can accept either a Unicode string or plain text. To demonstrate this, start a text editor, and then click with the mouse on the JLabel and drag it over the text editor. If the editor supports drag-and-drop, the cursor should change to indicate that there is data to be dropped and releasing the mouse will copy the label's text into the editor. The result of performing this operation on the Windows platform with WordPad as the drop target is shown in Figure 8-14.

Figure 8-14. Text dragged from a JLabel to WordPad.
graphics/08fig14.gif

In this case, the data that has actually been transferred is the label's text plus a description of the flavor used to perform the data transfer. As you'll see, the Transferable that this example uses offers two flavors, the choice between these two being made by the drop target. So that you can see which one was chosen, the Transferable adds this debugging information to the data that it is asked to move. WordPad accepts the data in plain text format, but if you start our last JEditorPane drop target example using the command

 java AdvancedSwing.Chapter8.EditorDropTarget4 

and drop the text over the JEditorPane, you'll see that it chooses to accept the data as a Unicode string, as shown in Figure 8-15. The fact that this choice is made is determined entirely by the order in which the available flavors are presented to our drop target implementation, which, as you saw in Listing 8-4, chooses the first flavor that it understands from the list presented to it.

Figure 8-15. Text dragged from a JLabel to JEditorPane.
graphics/08fig15.gif

Implementing the JLabe1 Drag Source

The code for the drag source is shown in Listing 8-9. As was the case with the drop target, we choose to implement the drag source as a separate class instead of subclassing JLabel to produce a label with built-in drag source support. As noted earlier, this makes it easier to plug drag-and-drop functionality into existing GUI interfaces without having to replace any of the components.

Listing 8-9 A Drag Source for a JLabel
 package AdvancedSwing. Chapter8; import java.awt.*; import java.awt.datatransfer.*; import java.awt.dnd.*; import java.util.*; import javax.swing.*; public class JLabelDragSource implements DragGestureListener,                            DragSourceListener {    public JLabelDragSource(JLabel label) {       this.label = label;       // Use the default DragSource       DragSource dragSource = DragSource.getDefaultDragSource();       // Create a DragGestureRecognizer and       // register as the listener       dragSource.createDefaultDragGestureRecognizer(                  label, DnDConstants.ACTION_COPY_OR_MOVE, this);    }    // Implementation of DragGestureListener interface.    public void dragGestureRecognized(DragGestureEvent dge) {       if (DnDUtils.isDebugEnabled()) {           DnDUtils.debugPrintln("Initiating event is " +                                 dge.getTriggerEvent());           DnDUtils.debugPrintln("Complete event set is:");           Iterator iter = dge.iterator();           while (iter.hasNext()) {              DnDUtils.debugPrintln("\t" + iter.next());           }       }       Transferable transferable = new JLabelTransferable(label);       dge.startDrag(null, transferable, this);    }    // Implementation of DragSourceListener interface    public void dragEnter(DragSourceDragEvent dsde) {       DnDUtils.debugPrintln("Drag Source: dragEnter,                       drop action = " + DnDUtils.showActions(                       dsde.getDropAction()));    }    public void dragOver(DragSourceDragEvent dsde) {       DnDUtils.debugPrintln("Drag Source: dragOver,                    drop action = " + DnDUtils. showActions(                    dsde.getDropAction()));    }    public void dragExit(DragSourceEvent dse) {       DnDUtils.debugPrintln("Drag Source: dragExit");    }    public void dropActionChanged(DragSourceDragEvent dsde) {       DnDUtils.debugPrintln("Drag Source: dropActionChanged,                    drop action = " + DnDUtils.showActions(                    dsde.getDropAction()));    }    public void dragDropEnd(DragSourceDropEvent dsde) {       DnDUtils.debugPrintln("Drag Source: drop completed,                    drop action = " + DnDUtils.showActions(                    dsde.getDropAction()) + ", success: " +                    dsde.getDropSuccess());    }    public static void main(String[] args) {       JFrame f = new JFrame("Draggable JLabel");       JLabel label = new JLabel("Drag this text", JLabel.CENTER);       label.setFont(new Font("Serif", Font.BOLD, 32));       f.getContentPane().add(label);       f.pack();       f.setVisible(true);       // Attach the drag source       JLabelDragSource dragSource = new JLabelDragSource(label);    }    protected JLabel label; // The associated Jlabel } 

The basic support from the drag-and-drop subsystem for a drag source is provided by the DragSource class, which was described in outline earlier in this chapter. To implement a drag source for a component, you need to create a DragSource and then register a DragGestureRecognizer that ties together the DragSource and the component itself.

As we said in our earlier discussion of the DragSource class, you can opt to use a single DragSource for each component that you want to add drag source capability to. To do this, you simply use the default constructor:

 DragSource dragSource = new DragSource (); 

However, the DragSource does not need to have a one-to-one relationship with the drag source component and, in fact, there is no direct connection to the drag source component at all. Because of this, it is possible to use a single DragSource to manage all the drag source components in an application, and the DragSource class provides a static method that returns a reference to a default instance that is created automatically for you:

 DragSource dragSource = DragSource.getDefaultDragSource (); 

The connection between a DragSource and a specific drag source component is made by a DragGestureRecognizer. The job of the DragGestureRecognizer is to monitor the drag source component for events that indicate that the user wants to start a drag operation. You need a different instance of DragGestureRecognizer for each drag source component; when you create it, it registers itself to receive the events from the component that will enable it to recognize the user's drag gesture. The drag-and-drop subsystem provides a subclass of DragGestureRecognizer that listens to mouse events delivered to the drag source component and triggers notification according to the platform-specific criteria described in "The Drag Source". Using this predefined class is simple you just use the DragSource createDefaultDragGestureRecognizer method:

 dragSource.createDefaultDragGestureRecognizer(               label, DnDConstants.ACTION_COPY_OR_MOVE, this); 

The first argument specifies the drag source component on which the default DragGestureRecognizer will register to receive mouse events. The second argument supplies the list of operations that the drag source will support. In this example, we allow the user to drag text from the label using either a copy or a move gesture although, in fact, the semantics of the operation will be the same whichever operation the user chooses, because we won't delete the label's text in the case of a move operation. We make this choice because it is more convenient for the user to select a move than a copy, which requires the CTRL key to be held down in addition to the mouse gesture.

The third argument to this method is the most important one because it supplies a reference to the DragGestureListener that will be notified when the DragGestureRecognizer detects a request by the user to start a drag operation. The DragGestureListener interface is a very simple one, consisting of a single method. As shown in Figure 8-1, it is possible to associate a single DragGestureListener instance with more than one DragGestureRecognizer (and therefore with more than one drag source component), but in this case the DragGestureListener functionality is implemented directly within the JLabelDragSource object itself, so that the third argument to createDefaultDragGestureRecognizer is this.

Core Note

The DragSource class has another method that you can use to create a DragGestureRecognizer:

 public DragGestureRecognizer createDragGestureRecognizer (                Class recognizerAbstractClass, Component c,                int actions, DragGestureListener dgl); 

The interesting thing about this method is that its first argument is described as an abstract subclass of DragGestureRecognizer and the returned object is described as an instance of a class derived from this abstract subclass. What does this mean? In practice, in the current implementation of drag-and-drop, this method only works if the first argument has value java.awt.dnd.MouseDragGestureRecognizer.class and, when you call it with this value, it returns an instance of the platform-specific class that recognizes mouse gestures. This is, of course, the same as if you had simply called createDefaultDragGestureRecognizer. Any other value passed as the first argument will cause createDragGestureRecognizer to return null. This method will only be potentially useful to application developers when the drag-and-drop subsystem supplies more than one drag gesture recognizer.



Implementing the DragGestureListener interface requires you to provide only the dragGestureRecognized method, which is called when the user has made the appropriate gesture to initiate a drag operation. This method must decide whether to allow the drag operation to start and to invoke the startDrag method if it should. Returning from dragGestureRecognized without calling startDrag will cause the user's drag gesture to be ignored. Here is how this method is implemented in JLabelDragSource:

 // Implementation of DragGestureListener interface. public void dragGestureRecognized (DragGestureEvent dge) {    if (DnDUtils.isDebugEnabled ()) {        DnDUtils.debugPrintln("Initiating event is " +                              dge.getTriggerEvent ());        DnDUtils.debugPrintln("Complete event set is:");        Iterator iter = dge.iterator ();        while (iter.hasNext ()) {           DnDUtils.debugPrintln("\t" + iter.next ());           Transferable transferable =                         new JLabelTransferable (label);     }     dge.startDrag(null, transferable, this); } 

As you can see, this implementation always accepts the drag operation because there are no extra conditions to be met before we allow the user to drag the label's text. In a more complex drag source, the dragGestureRecognized method might want to perform some extra checks before allowing the drag to proceed. We said earlier that it is possible to share a single DragGestureListener among multiple drag sources, but you will almost certainly need some context to carry out validity checks. The DragGestureEvent passed to dragGestureRecognized has methods that provide the following state information:

  • The component acting as the drag source.

  • The DragSource object associated with the DragGestureListener.

  • The user's selected drag action(copy, move, or link).

  • The position of the mouse within the drag source component at the point that the drag was initiated.

  • The DragGestureRecognizer handling the event.

  • The first event that triggered the gesture that started the drag.

  • An iterator that can be used to retrieve all the events that were part of the gesture recognition process.

In the next section, you'll see a drag source that makes use of some of this state information to decide whether to allow a user to drag entries from a JTree. The most obvious check to make in this case is whether the mouse is over a tree node and whether that node represents something that could be dragged. Making this check requires access to a reference to the drag source component and the coordinates of the mouse within that component, both of which can be obtained from the DragGestureEvent.

In some circumstances, it can be useful to have the set of events that caused the drag gesture to be recognized. As far as the mouse gesture recognizer is concerned, the DragGestureEvent getTriggerEvent method returns a MouseEvent for the mouse press that started the gesture, which includes the initial location of the cursor. You can also get this information more easily from the getDragOrigin method. If you need all the events, you can get them using the iterator method, which allows you to step through the events in the order in which they were delivered. The dragGestureRecognized method of JLabelDragSource prints the complete list of events if you run it with the DnDExamples. debug property defined:

 java -DDnDExamples.debug AdvancedSwing.Chapter8.JLabelDragSource 

If you start a drag and release the mouse as soon as the drag has been recognized, you'll see a stream of events printed in the window in which you typed the above command. The event list always begins with a MOUSE_PRESSED event (the same event returned by getTriggerEvent) and contains all the MOUSE_DRAGGED events up to the point at which the gesture was recognized which, as described earlier, occurs when the cursor has moved five pixels horizontally or vertically from its initial location, or when the mouse is dragged outside the component.

The most important DragGestureEvent method is startDrag, which has two variants:

  • public void startDrag (Cursor dragCursor,Image draglmage, Point imageOffset,Transferable transferable, DragSourceListener dsl)throws InvalidDnDOperationException

  • public void startDrag (Cursor dragCursor, Transferable transferable, ragSourceListener dsl) throws InvalidDnDOperationException

These two methods achieve the same thing, with the exception that the second version does not specify an image or an image offset. In our simple drag source, we use this second variant. The arguments are used as follows:

dragCursor Specifies the cursor shape to be used when the drag is not accepted by the drop target or when the cursor is not over a drop target. You can specify any cursor here, including an animated cursor. If you don't want to use a special cursor, you can use the platform default one (which is usually a no-entry sign) by supplying this argument as null.
dragImage Some platforms enable you to supply an image that is displayed in addition to the drag cursor when a drag is in progress. If you need to provide extra feedback to the user in this way, you can use this argument to supply the Image object. If you don't need to use this feature, supply null for this argument (or use the simpler variant of startDrag).
imageOffset If an image is supplied, this argument gives the initial location at which the image is to be drawn. The position is given relative to the origin of the coordinate system of the drag source component and will usually be calculated as a fixed offset from the location of the cursor at the point at which the drag gesture is recognized. If the platform supports the rendering of the image, the intent is that its position relative to the cursor will remain fixed as the cursor moves, with the separation determined by this initial offset. If you don't want to supply an image, this argument should be null.
transferable This argument supplies the Transferable that can be used to get the data to be supplied to the drop target. Depending on the type of data you need to transfer, you may be able to use one of the standard Transferables supplied by the java.awt.datatransfer package, or you may need to create your own. Shortly, you'll see an example of a custom Transferable.
dsl The DragSourceListener that will monitor the drag operation on behalf of the drag source.

Not all platforms support the rendering of an image in addition to normal cursor feedback. You can find out whether the platform your application is running on supports this by calling the DragSource isDraglmageSupported method.

In our example, the startDrag method is called with the Cursor argument set to null so that the default cursor is used and the DragSourceListener argument is supplied as this, because JLabelDragSource implements this interface itself. The Transferable is an instance of the class JLabelTransferable, the implementation of which we'll show later in this section.

The rest of the JLabelDragSource class is the implementation of the DragSourceListener interface. The methods of this interface are very similar to this of DropTargetListener and, as you might expect, there is a direct parallel between the activities of the drop target and those of the drag source while a drag operation is in progress. Here are the methods that make up the DragSourceListener interface:

  • public void dragEnter(DragSourceDragEvent dsde);

  • public void dragOver(DragSourceDragEvent dsde);

  • public void dragExit(DragSourceEvent dse);

  • public void dropActionChanged(DragSourceDragEvent dsde);

  • public void dragDropEnd (DragSourceDropEvent dsde);

Once the drag has been initiated by calling the DragGestureEvent startDrag method, nothing happens until the cursor moves over a valid drop target and the DropTargetListener for that drop target calls acceptDrag. If both of these events happen, the DragSourceListener dragEnter method is called, to inform the drag source that the cursor is now over a drop target. The dragOver method is called as the cursor moves while it remains within the drop target and dragExit is invoked when the cursor leaves the drop target, if the ESCAPE key is pressed to cancel the operation, or just before the drop occurs. The dropActionChanged method will be invoked if the user's gesture changes the operation being offered to the drop target and, finally, the dragDropEnd method informs the DragSourceListener that the drop occurred, but does not imply that it was successful.

The events that are passed to the DragSourceListener methods have the same hierarchy as those used by the drop target, as shown in Figure 8-16.

Figure 8-16. Hierarchy of drag source event classes.
graphics/08fig16.gif

All the events delivered are instances of DragSourceEvent or one of its two subclasses. DragSourceEvent itself has only one method:

 public DragSourceContext getDragSourceContext(); 

The relationship between the DragSourceContext and the other classes that make up the drag source was shown in Figure 8-1. Most applications will not need to directly access the DragSourceContext because the DragSourceDragEvent and DragSourceDropEvent classes have cover methods that allow you to access the functionality of the DragSourceContext without needing to explicitly get a reference to it.

The DragSourceDragEvent that is passed to dragEnter, dragOver, and dropActionChanged has four methods that allow you to get the current state of the drag operation:

  • public int getGestureModifiers();

  • public int getTargetActions();

  • public int getUserAction();

  • public int getDropAction();

The first of these methods just returns the state of the modifiers in the mouse event corresponding to the event being delivered to the DragSourceListener. You can use this information to determine which mouse buttons are pressed should you need to. In practice, there should be little reason to do this because the relationship between mouse button state and the implied user gesture should be considered to be platform-dependent, so assumptions about the meanings of particular buttons should not be hard-coded into your application.

The getTargetActions method returns the drop action last suggested by the drop target, while getUserAction gives you the action implied by the user's current gesture. Finally, getDropAction is the action implied by the combination of the drag source actions, the user action, and the target action. This will, of course, be ACTION_NONE if there is no common action among these three. For example, if the drop target suggests ACTION_COPY and the user action is currently ACTION_MOVE, getDropAction will return ACTION_NONE. On the other hand, if the drop target suggested ACTION_MOVE instead and the drag source actions include ACTION_MOVE, getDropAction will return ACTION_MOVE.

After the drag has started, the DragSourceListener has two main duties:

  1. To change the cursor shape to provide drag-over feedback to the user.

  2. When the drop completes, to perform any actions on the drag source that are implied by the operation that was actually carried out.

Although you can explicitly change the cursor as the drag operation proceeds, the drag-and-drop subsystem will do this for you automatically based on whether the cursor is currently over a drop target, whether the drop target considers the operation acceptable, and what the selected operation actually is. If you want to change the cursor, you can do this from the dragEnter, dragOver, dropActionChanged, and dragExit methods by obtaining a reference to the DragSourceContext using the getDragSourceContext method of DragSourceEvent and then calling its setCursor method. Note that this is probably the only case that requires you to obtain a direct reference to the DragSourceContext, because the drag source events do not have cover methods for the cursor control functionality. By default, the cursor state is set based on the intersection of the current drop action and the drop targets suggested action, as returned by the DragSourceDragEvent getDropAction and getTargetActions methods.

When the drag operation completes, the dragDropEnd method is called with a DragSourceDropEvent. This class has two methods that allow the drag source to determine the outcome of the drop:

  • public boolean getDropSuccess();

  • public int getDropAction();

The first of these returns true if the drop target performed the data transfer without error. In this case, the getDropAction method can be used to determine the actual operation (copy, move, or link) that the drop target performed. Although the example code in Listing 8-9 does nothing in the dragDropEnd method other than print debugging information (if the DnDExamples.debug property is defined), in many cases you will need to take some action here. For example, if you implement a drag source for a text component, you'll need to delete the text that has been dragged if, and only if, the getDropSuccess method returns true and getDropAction returns ACTION_MOVE.

The implementation of the DragSourceListener in our example is very simple it only displays diagnostic information if debugging is enabled. You'll see a more complete example when we look at creating a drag source for a JTree component.

Creating a Transferable for a JLabel

The last step in implementing our drag source is providing the Transferable. Actually, because all we strictly need to transfer in this example is the text from the label, you might think that we could simply have used the StringSelection class from the java.awt.datatransfer package, which implements Transferable, and passed the object created as the result of the following statement to the startDrag method:

 new StringSelection (label.getText()) 

This, however, does not work in all cases. If you modify the source of JLabelDragSource to use a StringSelection, you'll find that you can successfully drag the label's text onto our JEditorPane drop target, but not onto platform-native applications. This happens because, although StringSelection claims to be able to supply its data in both String and plain text formats, the representation classes that it uses for both of these flavors is not compatible with export to the platform drag-and-drop subsystem:

  • In the case of the String flavor, the representation is a Java class, which is only suitable for transfer to another Java application (such as JEditorPane).

  • The plain text flavor supplies its data as a java.io.stringReader so that it returns Unicode characters when read. This is inconsistent with the Java drag-and-drop implementation, which requires data exported to the platform to be in the form of a java.io.InputStream.

To transfer the label text to platform native applications, we are forced to implement our own Transferable. Because we're taking the trouble to do this, we might as well take the next logical step and provide the code necessary to all the JLabel itself to be transferred, so that the user can drag the complete label and drop it onto a suitable drop target. If the drop target is a container, this might result in a copy of the JLabel being added to the container. If you've used a Java integrated development environment that allows you to build a user interface, you'll recognize this as the beginnings of an implementation of a component toolbar; although the example shown here will be a trivial one, all the code you need for creating your own toolbar with draggable components is present here.

The Transferable will support three flavors:

  • The label text supplied as a Unicode string. The object returned from the getTransferData method for this flavor will be of type java.lang.String.

  • The label text supplied as plain text characters in the platform's default encoding. This flavor is provided specifically to allow the text to be exported to native applications and, therefore, the getTransferData method must return an object of type java.io.Inputstream when asked for the data in this flavor.

  • A copy of the JLabel itself. Here, the object returned from getTransferData will be of type javax.swing.JLabel. This flavor is only usable when dragging the label between two Java VMs.

To create a Transferable, you need to implement the following three methods:

getTransferDataFlavors to provide a list of the supported flavors
isDataFlavorSuppored to indicate whether the Transferable supports a given flavor
getTransferData to return the data in the requested flavor

All three of these methods either require a DataFlavor object as an argument or return objects of type DataFlavor, so we need a DataFlavor object to represent each of the three flavors that the Transferable supports. The first of these is easy for a Unicode string, we just use DataFlavor.stringFlavor, which is already defined in the java.awt.datatransfer package. The other two are not so obvious, however.

Getting a type for plain text sounds simple, but in fact there is no predefined DataFlavor that expresses the actual type of the data that our Transferable will return. The obvious choice would be DataFlavor.plainTextFlavor, but this is defined to return an object of type java.io.Inputstream, which supplies its data in Unicode, as we saw earlier when creating the JEditorPane drop targets. In fact, what we want to return is an object of type java.io.Inputstream in which the character stream is in the platform local encoding. The drag-and-drop subsystem uses the MIME type

 text/plain; charset=ascii 

to represent text with this encoding, so a suitable DataFlavor for the plain text variant of our Transferable can be created like this:

 new DataFlavor ("text/plain; charset=ascii", "ASCII Text"); 

Finally, the DataFlavor that corresponds to transferring the whole JLabel is created using the following statement:

 new DataFlavor (JLabel.class, "Swing JLabel"); 

Here, the first argument supplies the flavor's representation class and the second is its human-readable name. A DataFlavor created in this way actually has a MIME type of application/x-java-serialized-object because the data will be transferred using the Java object serialization mechanism, which requires that the object itself be serializable. Like all Swing components, JLabel is serializable.

Now that we have definitions for the three flavors, the implementation of the Transferable is straightforward. The code is shown in Listing 8-10.

Listing 8-10 A Transferable for a JLabel
 package AdvancedSwing. Chapter8; import java.awt.datatransfer.*; import java.io.*; import javax.swing.*; public class JLabelTransferable implements Transferable {    public JLabelTransferable(JLabel label) {       this.label = label;    }    // Implementation of the Transferable interface    public DataFlavor[] getTransferDataFlavors() {       return flavors;    }    public boolean isDataFlavorSupported(DataFlavor fl) {       for (int i = 0; i < flavors.length; i++) {          if (fl.equals(flavors[i])) {             return true; }         }       }       return false;    }    public Object getTransferData(DataFlavor fl) {       if (!isDataFlavorSupported(f1)) {           return null;       }       if (fl.equals(DataFlavor.stringFlavor)) {          // String - return the text as a String          return label.getText() + " (DataFlavor. stringFlavor) ";       } else if (fl.equals(jLabelFlavor)) {          // The JLabel itself - just return the label. return label;       } else {          // Plain text - return an InputStream          try {             String targetText = label.getText() + "                     (plain text flavor)";             int length = targetText.length();             ByteArrayOutputStream os =                     new ByteArrayOutputStream();             OutputStreamWriter w = new OutputStreamWriter(os);             w.write(targetText, 0, length);             w.flush();             byte[] bytes = os.toByteArray();             w.close();             return new ByteArraylnputStream(bytes);          } catch (IOException e) {                return null;          }       }    }    //A flavor that transfers a copy of the JLabel    public static final DataFlavor jLabelFlavor =                   new DataFlavor(JLabel.class, "Swing JLabel");    private JLabel label;         // The label being transferred    private static final DataFlavor[] flavors =       new DataFlavor[] {          DataFlavor.stringFlavor,          new DataFlavor("text/plain; charset=ascii",                         "ASCII text"), jLabelFlavor    }; } 

The constructor takes as its only argument a reference to the JLabel being transferred. This reference is stored until the transfer data is requested in the getTransferData method. The getTransferDataFlavors method is trivial it simply returns an array containing DataFlavor objects for the three flavors that are supported by the Transferable. Similarly, isDataFlavor-Supported compares the DataFlavor supplied as its argument to the three supported flavors and returns true if a match is found and false otherwise.

The getTransferData method, however, is a little more complex. Because the Transferable recognizes three different flavors, there are three different cases to consider, based on the DataFlavor passed by the caller. The two easiest cases are where the flavor requested is DataFlavor.stringFlavor or the flavor for the JLabel itself because this require us to return the String or the JLabel as an object. In the case of stringFlavor, we extract the text from the label and add the string " (DataFlavor.stringFlavor)" so that you can see that this flavor was requested; if the JLabel is required, it is just returned directly from the reference held within the JLabelTransferable.

Returning the label in plain text form is a little more difficult. Here, we need to create and return an object of type java.io.inputstream which, when read, will return the characters of the text label. There is no InputStream class in the java.io package that takes a Unicode string as input and returns bytes in the platform local encoding (apart from StringBufferInputstream, which is deprecated), so we need to simulate one using two streams. This is done by taking the text label (adding the extra string "plain text flavor)" for diagnostic purposes) and converting it to an array of bytes by using a ByteArrayOutputStream, wrapped with an OutputStreamWriter. The OutputstreamWriter takes the input string and converts each character to a byte in the platform's local encoding, and then passes them to the ByteArrayOutputStream, which stores them in an internal byte array. Because the getTransferData method has to return an Inputstream, the final step is to create and return a ByteArrayInputstream, which takes the byte array from the ByteArrayOutputStream as its input.

This completes the implementation of the Transferable for the JLabel. You've already seen that it works when dragging text to either a platformnative application or to a JEditorPane, which demonstrates the transfer both of plain text and a Unicode string. To see that it also correctly transfers the complete JLabel, you need to start two Java VMs in two separate command windows using the following commands:

 java AdvancedSwing.Chapter8.JLabelDragSource java AdvancedSwing.Chapters.PanelDropTarget 

The first of these commands brings up the draggable JLabel, while the second creates a frame with a JPanel in it, to which a drop target has been added. This drop target is capable of accepting dragged objects derived from java.awt.Component; when such an object is dropped on it, it simply adds it to the JPanel. To see this in action, click the mouse in the JLabel and drag it over the frame, and then drop it. You should see a copy of the label appear in the frame. You can repeat this as many times as you like each time you do so, another copy of the label appears in the frame, as shown in Figure 8-17.

Figure 8-17. Dragging a JLabel onto a drop target.
graphics/08fig17.gif

The implementation of the drop target for the JPanel in the Panel-DropTarget class is very similar to the one for JEditorPane except that, instead of looking for a list of files or text, it accepts a transferable whose representation class is java,awt.Component or a subclass thereof. We're not going to show the complete source code for this drop target here because most of it has been shown already; if you want to see the complete implementation, you'll find it on the book's CD-ROM. The main differences between PanelDropTarget and EditorDropTarget are in the checkTransferType and drop methods, the PanelDropTarget versions of which are both shown in Listing 8-11, with the changes from the EditorDropTarget implementation highlighted in bold.

Listing 8-11 A Drop Target That Can Accept Components
 protected void checkTransferType(DropTargetDragEvent dtde) {   // Only accept a flavor that returns a Component    acceptableType = false;    DataFlavor[] fl = dtde.getCurrentDataFlavors();    for (int i = 0; i < fl.length; i++) {       Class dataClass = f1 [i] .getRepresentationdass () ;          if (Component.class.isAssignableFrom(dataClass)) {          // This flavor returns a Component - accept it.          targetFlavor = fl[i];          acceptableType = true;          break;       }    }    DnDUtils.debugPrintln("File type acceptable - " +                          acceptableType); } public void drop (DropTargetDropEvent dtde) {    DnDUtils.debugPrintln("DropTarget drop, drop action = "            + DnDUtils.showActions(dtde.getDropAction()));    // Check the drop action    if ((dtde.getDropAction() &                    DnDConstants.ACTION_COPY_OR_MOVE) != 0) {       // Accept the drop and get the transfer data       dtde.acceptDrop(dtde.getDropAction());       Transferable transferable = dtde.getTransferable();       try {         boolean result = dropComponent(transferable);          dtde.dropComplete(result);          DnDUtils.debugPrintln("Drop completed,                                success: " + result);       } catch (Exception e) {          DnDUtils.debugPrintln("Exception while handling                                drop " + e) ;          dtde.dropComplete(false);       }    } else {       DnDUtils.debugPrintln("Drop target rejected drop");       dtde.rejectDrop();    } } protected boolean dropComponent(Transferable transferable)       throws IOException, UnsupportedFlavorException {    Object o = transferable.getTransferData(targetFlavor);    if (o instanceof Component) {       DnDUtils.debugPrintln("Dragged component class is                             " + o.getClass().getName());       pane.add((Component)o);       pane.validate();       return true;    }    return false; } 

The role of the checkTransferType is to determine whether the drag source and drop target have any compatible transfer flavors. In this case, the drop target will only accept the transfer of an object derived from java.awt.Component. To determine whether it is possible to get such an object from the drag source, checkTransferType gets the list of available DataFlavors and calls the getRepresentationClass method on each of them in turn, checking whether the class that is returned is java.awt.Component or a subclass of it, using the Class method isAssignableFrom. If any of the available flavors can return a Component, this method will set acceptableType to true and the drag operation will be accepted (provided also that the operation is either copy or move). As a side effect, it also saves a reference to the DataFlavor that can supply the Component, because this will be needed when handling the drop operation.

The drop method must select an appropriate flavor from those available and then transfer the data. Because only one flavor is acceptable and it is known that it is being offered by the drag source, drop just calls the dropComponent method, which is specific to PanelDropTarget. This method, which is also shown in Listing 8-11, uses the DataFlavor stored by checkTransferType to get the dragged object by calling getTransferData on the Transferable. If this object is of type Component (which it will be unless there is an inconsistency in the implementation), it adds it to the JPanel and invokes the validate method to have the panel's layout manager size it and allocate space for it. We are assuming here that the JPanel has its default FlowLayout manager installed, so that the JLabel will be given its preferred size. In a real application, you would do whatever is required here in the context of the operation being performed.

 

 



Core Swing
Core Swing: Advanced Programming
ISBN: 0130832928
EAN: 2147483647
Year: 1999
Pages: 55
Authors: Kim Topley

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net