The Swing HTML Package

Java > Core SWING advanced programming > 8. DRAG-AND-DROP > Architecture of the Java Drag-and-Drop Subsystem

 

Architecture of the Java Drag-and-Drop Subsystem

To illustrate the architecture of the Java drag-and-drop subsystem and to get a clearer idea of how the pieces fit together, let's walk through a specific drag-and-drop example. Whereas up to now the discussion has been fairly abstract, here we'll get right to specifics and describe exactly what happens from the point of view of the Java developer implementing both the drag source and the drop target. Because we're going to describe this from the programmer's viewpoint, we're no longer going to talk in generic terms the description here assumes that the developer is using the Java 2 drag-and-drop feature. It's worth bearing in mind, however, that it is by no means always the case that both the drag source and drop target will be implemented in Java either of them could be provided as part of a native application and, as we go through the scenario we're about to describe, we'll point out what differences might exist if one party in the transfer operation is a native application. Indeed, in the next section, when we start implementing a drop target in Java, we'll assume that you have available a native drag source to test it with.

The scenario that we're going to explore involves a graphical file system viewer, such as Windows Explorer, which will act as a drag source, and a file editor that can open a file dropped onto it for display or for editing. Let's assume for now that the file system viewer is implemented in Java rather than being a platform-native application. Building an application that displays a file system using a JTree component is not too difficult a task and, later in this chapter, you'll see how to add drag source functionality to such a component. The editor in this example will be an instance of the Swing JEditorPane component, which, as we've already seen, can handle text files encoded in various different ways. Opening a file in a JEditorPane can be done by supplying a URL, reading the file into a String, and passing that to the JEditorPane or passing the content via an Inputstream or a Reader.JEditorPane does not, however, directly support opening a file by dropping it from a drag source. Our aim here is to sketch out what is involved in providing this feature and to introduce the most important classes in the java.awt.dnd package. In the next section, we'll go a step further and show the actual implementation.

Drag Source Architecture

As we said earlier, the drag source is the object that is responsible for providing the data that will be dragged by the user to a drop target. You can think of the drag source either in terms of a region of the GUI that will react to the platform-specific gesture to initiate a drag, or as the set of classes within the application (both application-specific and those provided by the Java platform) that implement the drag source functionality. For clarity, in this chapter we'll use the term drag source component when we mean the former and drag source for the latter. A drag source is implemented using a set of objects, most of which are instances of classes in the java.awt.dnd package. In most cases, to create a drag source that you can use in your application you only need to supply two classes of your own implementations of the DragGestureListener and DragSourceListener interfaces that we'll see later in this section. In practice, the DragGestureListener is very dependent on the nature of the drag source component and you will probably create a single DragGestureListener implementation for a component that you can reuse whenever you need a drag source component of that type. Later in this chapter, you'll see how to create a DragGestureListener that responds to user requests to drag information from a JTree component. The specific example that well show will use a JTree populated with nodes that represent a view of a file system, but the DragGestureListener will be general enough to be reused with any kind of JTree. By contrast, the DragSourceListener is more likely to be tied to the specifics of the drag source component, in that it might behave differently for a JTree containing file system objects than for one containing library records. However, as we'll see, it is often possible to implement a DragSourceListener that does nothing, because all the methods of the DragSourceListener convey information that need not be acted upon. The architecture of the drag source, in terms of the most important classes and the runtime relationships between them, is shown in Figure 8-1.

Figure 8-1. Architecture of the drag source.
graphics/08fig01.gif

This diagram shows an application with two drag source components concurrently active. For the sake of this example, these components will be assumed to be JTrees showing a view of a file system, like the one shown in Figure 8-2. JTree does not have any built-in support for drag-and-drop. To add it, you need to register the JTree with an instance of the class java.awt.dnd.DragSource, as shown in Figure 8-1.

Figure 8-2. A JTree showing a view of a file system.
graphics/08fig02.gif

The DragSource class provides the following functionality:

  • The ability to create a DragGestureRecognizer.

  • Methods to allow the developer to specify whether a drag that the user has requested should actually be initiated.

  • A repository for mappings between the types of data that the host platform can use in a drag-and-drop operation to the DataFlavors that the Java drag-and-drop subsystem uses to represent them.

Of these, you'll usually only be directly concerned with the first two; the flavor mapping is used internally by the drag-and-drop subsystem and is of little interest to most applications. As we go through the description of this example, you'll see how these features of the DragSource are used.

There are two ways to get a DragSource. The most obvious way is just to create one for your dedicated use:

 DragSource ds = new DragSource ( ); 

This is perfectly acceptable, but it is strictly unnecessary. A single DragSource can support interaction with any number of drag source components in fact, it has no direct relationship with a drag source component, as you can see from Figure 8-1. Because of this, you only need a single DragSource no matter how many components you have with drag capability. The easiest way to ensure that you only ever use one DragSource is to use the static DragSource getDefaultDragSource method:

 DragSource ds = DragSource.getDefaultDragSource ( ); 

You can invoke this method as many times as you like and you'll get the same DragSource reference each time. In most cases, you'll only need the DragSource reference for long enough to register a drag source component. As the drag operation proceeds, events will be delivered to report on its progress. Should you need a reference to the DragSource while handling these events, you can use the getDefaultDragSource method to recover it. If, for some reason you elect to create your own DragSource objects, you can get a reference to the one involved in the operation that caused the event from information provided by the event itself. Because of this it will not usually be necessary to keep a long-term reference to the DragSource in an application object.

Obtaining a DragSource object is not enough to enable a Component to serve as a source for drag operations because there is, as yet, no connection between the two. To enable the Component, you have to create a DragGestureRecognizer. The DragGestureRecognizer is associated with both the DragSource and the Component and so forms the bridge between the two and is tied to a particular Component. In other words, as Figure 8-1 shows, if you have two drag source components, you need to create a separate DragGestureRecognizer for each of them.

DragGestureRecognizer is an abstract class in the java.awt.dnd package, whose job is to monitor the drag source Component for events that constitute a request by the user to initiate a drag. What exactly will be recognized as a request to start a drag depends on the concrete implementation of DragGestureRecognizer. You probably think of drag-and-drop as an operation that is initiated and controlled entirely using the mouse and, not surprisingly, the drag-and-drop subsystem supplies a DragGestureRecognizer subclass that monitors mouse events on its associated Component to detect a drag request. It is, however, perfectly possible to implement a DragGestureRecognizer that triggers a drag based on some other events, such as a combination of key strokes, or on command from the application. Such an implementation would not be very useful, however, because the user would still need to drag with the mouse to continue the operation.

If you're happy to use the built-in DragGestureRecognizer, you can get one using the DragSource createDefaultDragGestureRecognizer method:

 public DragGestureRecognizer createDefaultDragGestureRecognizer(                Component c, int actions, DragGestureListener 1); 

The Component argument is, of course, the drag source component that is to be monitored. The second argument (actions) is a mask that indicates which of the three possible drag actions the drag source component can support. It consists of an OR of any of the following values:

  • DnDConstants.ACTION_NONE

  • DnDConstants.ACTION_COPY

  • DnDConstants.ACTION_MOVE

  • DnDConstants.ACTION_LINK

  • DnDConstants.ACTION_REFERENCE

  • DnDConstants.ACTION_COPY_OR_MOVE

ACTION_NONE has value 0 and indicates that no action is being offered. This, of course, is useless when creating a drag source. The values ACTION_LINK and ACTION_REFERENCE are synonyms referring to the link operation, while ACTION_COPY_OR_MOVE is shorthand for (ACTION_COPY| ACTION_MOVE). The last object passed when creating a DragGestureRecognizer is a DragGestureListener. DragGestureListener is an interface with one method:

 public interface DragGestureListener extends EventListener {      void dragGestureRecognized(DragGestureEvent dge); } 

You are responsible for writing a class that implements this interface; its dragGestureRecognized method will be called when the events that trigger the start of the drag have been detected by the DragGestureRecognizer. You'll see a typical implementation of a DragGestureListener in "Implementing a Drag Source".

Assuming that you use the standard mouse gesture recognizer, the events that are recognized as a request to start a drag are platform-dependent. The first thing that must happen is for the user to press a mouse button over the drag source component. The exact state of the mouse when the MOUSE_PRESSED event is generated must be as follows:

  • On Solaris, any single mouse button must be pressed.

  • On Win32, the left mouse button must be pressed. (Strictly speaking, the button recognized as button 1 is required, which may not be the left button if you have reconfigured your mouse because, perhaps, you are left-handed).

This event on its own is not enough for a drag request to be recognized. In addition, the user must do one of the following:

  • Drag the mouse outside the boundary of the drag source component.

  • Move the mouse more than a certain number of pixels away from the point at which the initial event occurred. The exact distance is obtained as the value of a desktop property called DnD.gestureMotionThreshold. If this property is not defined, it defaults to five pixels.

If the mouse is released or the ESCAPE key is pressed before these conditions are met, the drag will not be initiated at all.

When the conditions for the drag to start have been met, your DragGestureListener's dragGestureRecognized method will be called. The job of this method is to decide whether a drag operation will actually be initiated. If this method does nothing, the drag will not proceed; to actually begin a drag operation, the DragSource startDrag method must be called. If you look at the definition of the dragGestureRecognized method, however, you'll see that it doesn't get the DragSource as a parameter instead, it gets a DragGestureEvent. Because you don't have a direct reference to the DragSource, how can you invoke any of its methods? One way is to call the static getDefaultDragSource method, but that only works if you used this method to create your DragSource in the first place. Fortunately, the drag-and-drop application programming interface (API) is designed with programmer convenience in mind and here we see the first example of a very useful design pattern that is repeated for all the events that you'll need to handle when implementing drag-and-drop. Instead of requiring you to jump through hoops to get a reference to the DragSource, the DragGestureEvent has its own dragstart method that you can call instead. In fact, it has a whole host of cover methods that save you having to obtain any external state at all when deciding whether to start the drag. If you look at the API reference for DragGestureEvent, you'll find the following amongst the set of useful methods that it defines:

  • public Component getComponent ( ) ;

  • public int getDragAction ( );

  • public DragSource getDragSource ( );

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

  • public void startDrag(Cursor dragCursor, Image dragImage, Transferable transferable,DragSourceListener dsl) throwsInvalidDnDOperationException;

Why are you required to invoke the startDrag method to approve the drag? Surely if the user makes the correct platform-dependent gesture over a part of the GUI that's occupied by a drag source component, that should be reason enough to start the drag? That's not the view taken by the drag-and-drop subsystem, and with good reason. To see why you might not want to automatically start a drag operation, look back to Figure 8-2. Suppose the user clicks in the window using the mouse and starts a drag operation. The JTree itself occupies almost all of the window shown in Figure 8-2, but if the user clicked in the empty area to the right of the set of directories shown in the visible part of the JTree, you wouldn't want to permit a drag, because the file or directory to drag would not have been selected. In fact, you only want to allow a drag operation to begin if the user attempts to drag from an area of the component occupied by one of the nodes of the tree. Even then, you may want to make further checks. You may, for example, not want to allow the user to drag certain types of files or disallow attempts to move or link directories. Because these decisions are dependent not on the drag-and-drop mechanism nor on the JTree itself, they can only be made by code that is aware of what the drag source component represents and this is why the decision must be made in the dragGestureRecognized method. Refusing a drag operation is, by the way, very easy just return from the dragGestureRecognized method without invoking startDrag.

Suppose that the user has selected a file and made the gesture to start the drag and that you want to check whether the user wants to perform a copy, a move, or a link to enforce the restrictions mentioned in the previous paragraph. You can get find out which operation is being performed from the DragGestureEvent getDragAction method, which returns ACTION_COPY, ACTION_MOVE, or ACTION_LINK. But how does the user indicate which operation is required? The selected action is actually determined by the DragGestureRecognizer and so is, theoretically, platform-dependent (as well as being dependent on the gesture type if you use a custom recognizer). In fact, though, the action is selected in the same way on both Solaris and Windows and is determined by the state of the SHIFT and CTRL keys, as follows:

Neither pressed ACTION_MOVE
CTRL pressed, SHIFT not ACTION_COPY
CTRL and SHIFT both pressed ACTION_LINK

If you press SHIFT without CTRL, the default recognizer will not signal the start of a drag operation and your dragGestureRecognized method will not be called.

Core Warning

The drag gesture and the conventions used to select an operation as described here apply only when the drag source is implemented using the Java drag-and-drop mechanism. If you want to drag from a native application to a Java drop target, you may find that different conventions apply. You'll see an example of this later in this chapter.



As you can see from the API extract shown earlier, the startDrag method has several arguments that you can supply. Two of them, cursor and image, allow you control over the way the cursor appears as the user drags it toward a drop target. We won't say any more about those two arguments until we look in detail later in this chapter at how to customize the feedback that is provided to the user. The other two arguments are, however, worth mentioning at this point. The Transferable argument is a reference to an object that can provide the data that is actually being dragged to the drop target. The fact that you have to provide this argument when the drag is initiated might seem surprising, but there is a good reason for it. Usually, the user will select what is to be dragged using the mouse and then initiate the drag. Because the drag operation may continue for some time before a drop occurs and is asynchronous to the source application, there is the possibility, depending on the way in which the application is implemented, that the source data within the application might change before the drop takes place. Supplying the Transferable at the start of the operation makes it possible to take a frozen copy of the data that represents exactly what the user selected to be transferred. It also makes the actual drop operation somewhat simpler, because the drag-and-drop subsystem doesn't have to go back to the source application to request the data when the drop itself occurs.

Core Note

Transferable is just an interface that requires its implementing class to indicate the forms in which the data can be delivered and has a method that can be used to obtain the data on demand. Theoretically, if you implement your own Transferable, you have the choice as to whether to copy the data into an internal buffer when the Transferable is created and supply the value from the buffer when asked, or whether to simply save a reference to the data within the application and fetch it on demand. Choosing the latter might be confusing for the user if the original data can change during the drag. It may be more efficient, however, if you know that the source data will remain unchanged until it is dropped.



The last argument to startDrag is an object of type DragSourceListener. DragSourceListener is another interface that you are required to supply an implementation of to start a drag operation. The methods of this interface, which we'll see later in this chapter, are entered to report the operations progress once the user has dragged the cursor over a valid drop target. As with the DragGestureListener, the events that are passed to the methods of this interface contain all the state information to process them, so your DragSourceListener does not need to be permanently associated with a particular drag source component or DragSource; it is sufficient to create only one DragSourceListener for all drag source components of a particular type.

Core Note

In fact, it can actually be better than that In the simplest cases, you won't actually need to do anything at all to handle the events that the DragSourceListener receives because the default handling provided by the drag-and-drop subsystem is perfectly satisfactory. As a result, a trivial implementation in which all the methods have empty bodies will be good enough and such an implementation can be shared among an arbitrary number of different drag source components.



Once the startDrag method has been called, the drag operation has been initiated and nothing more happens from the point of view of the drag source until the drag is ended by the user or encounters a drop target.

Figure 8-1 shows two other classes that we haven't yet mentioned DragSourceContext and DragSourceContextPeer. In most cases, you won't need to interact with these two classes at all they are used by the drag-and-drop subsystem itself. DragSourceContext does have some useful methods, but the ones that you commonly need to use are accessible indirectly via cover methods in the events that are delivered to your DragSourceListener, so there is little reason to access the DragSourceContext directly. The only operation that absolutely requires you to bypass the cover methods and use the DragSourceContext itself is changing the drag cursor to provide custom feedback to the user, a topic we'll cover later. Every DragSourceContext has a DragSourceContextPeer associated with it. This object is part of the platform-specific drag-and-drop implementation and cannot be accessed directly from within application code.

Core Note

Only one drag-and-drop operation can be active at any given time, so there will never be more than one set of DragSourceContext/DragSourceContextPeer objects concurrently active, even though Figure 8-1 would seem to suggest that there can be. The diagram is intended to emphasize the point that each drag operation has its own dedicated DragSourceContext/DragSourceContextPeer pair which is created when the drag starts and discarded when it ends. The next drag operation does not reuse the previous objects.



Drop Target Architecture

As the user drags the mouse over the desktop, eventually a drop target will be encountered. Like the drag source, in Java a drop target is always a Component. To avoid confusion, we'll use the term drop target component when referring to the region of the screen occupied by the drop target and reserve the term drop target to mean the collection of objects that work together to provide the drop target functionality. The architecture of the drop target is shown in Figure 8-3.

Figure 8-3. Architecture of the drop target.
graphics/08fig03.gif

You can see that this is a little different from the arrangement of objects that make up the drag source. Whereas you could use one DragSource object to manage as many different drag sources as you have, you need a dedicated DropTarget object for each drop target component. There is no convenience method to create a DropTarget instead, you use one of its five constructors:

  • public DropTarget();

  • public DropTarget(Component c, DropTargetListener dt1);

  • public DropTarget(Component c, int ops, DropTargetListener dtl);

  • public DropTarget(Component c, int ops,DropTargetListener dtl, boolean act);

  • public DropTarget(Component c, int ops, DropTargetListener dtl, boolean act, FlavorMap fm) ;

The last of these constructors is the most general. If you use one of the others, the arguments that you don't specify are defaulted and can be changed later if required using a mutator method. For example, if you use the default constructor, you get a DropTarget that is not associated with any Component and is therefore not of any immediate use. You can connect it a Component and set the other attributes using the following methods:

  • public void addDropTargetListener (DropTargetListener dtl) throwsTooManyListenersException;

  • public void setActive(boolean isActive);

  • public void setComponent(Component c);

  • public void setDefaultActions(int ops);

  • public void setFlavorMap(FlavorMap fm);

The ops argument specifies the actions that this drop target will accept. Like the similar parameter to the DragSource startDrag method, it is an OR of the values ACTION_COPY, ACTION_LINK, and ACTION_MOVE. For example, if you want to allow the user to copy or move data to your drop target but you can't support the link operation, you should set the ops argument to ACTION_COPY_OR__MOVE or use the setDefaultActions method to set the same value. If you use one of the constructors that does not have an ops argument, this is, in fact, the default value that will be used.

As noted earlier, the drag-and-drop system uses a FlavorMap to map between DataFlavors that have meaning only inside the Java VM and the native data transfer formats used on the host platform. Usually, you don't need to concern yourself with the details of the FlavorMap because a default one is installed automatically for you. If you wish to create your own FlavorMap for a specific DropTarget, you can use the setFlavorMap method to arrange for it to be used.

A drop target may be either active or inactive. By default, the drop target is active, but you can use the setActive method to temporarily or permanently disable it or re-enable it at any time. When the drop target is inactive, the drop target component is not considered a valid target for a drop and the user will not get the usual feedback from the cursor when it encounters the inactive drop target component. Depending on the nature of your application, you might want to tie the active or inactive state of the drop target to the enabled state of the component itself. If, for example, your drop target component is a text component, you might not want to allow the user to drag text into it if it is inactive or if it is not editable. By setting the initial state of the drop target from that of the component and monitoring PropertyChangeEvents for the editable and enabled properties, you can keep the states of the component and the drop target synchronized.

Core Note

This is only possible for Swing components, because AWT components do not have bound properties for the editable and enabled states.



The last attribute of the DropTarget, and the most important one, is the DropTargetListener. DropTargetListener is an interface that has methods that allow you to receive notification of events that relate to a drag in progress over the drop target component. To construct a DropTarget, you need to create a class that implements DropTargetListener and register an instance of it with the DropTarget either via the constructor or with the addDropTargetListener method. As is often the case with the drag-and-drop API, the state and cover methods that are available from the events delivered to the DropTargetListener are sufficiently comprehensive that you don't need to retain any state information with your DropTargetListener, which means that you can share a single instance of it with more than one DropTarget. However, each DropTarget can have only one DropTargetListener at any given time; if you try to add a new listener to a DropTarget that already has one, you'll get a TooManyListenersException. This restriction is applied because the primary function of the listener is to cooperate with the drag-and-drop subsystem to control the progress of the drag operation. The listener will have to decide whether a drop from the current drag source and with the current conditions is acceptable. Obviously, it makes no sense to allow more than one listener to try to control the same operation.

Having seen how to create a DropTarget, let's look at how the drop part of the drag-and-drop operation works in practice. Suppose that the user has selected a file from a Windows Explorer-like program and is dragging it toward a Java application containing a JEditorPane that has been enabled as a drop target component by connecting a custom DropTarget object to it (you'll see the actual implementation of this later in this chapter). When the user drags the cursor over the boundary of the JEditorPane, the drag-and-drop subsystem creates a DropTargetContext, which it will use to manage the drop while it is over the JEditorPane, and a corresponding DropTargetContextPeer which is used by the native code part of the subsystem. These objects are associated one-to-one with a DropTarget but, as was the case with the DragSourceContext and the DragSourceContextPeer, you don't really need to deal with either of them.

Core Note

A DropTarget only ever has one DropTargetContext that is created the first time a drag moves over its associated Component. The DropTargetContextPeer is connected to the DropTargetContext at the same time, but is disconnected when the drop completes or when the user drags the cursor outside the component. The next drag to enter the component will use the same DropTargetContext, but a new DropTargetContextPeer will be created.



To signal the fact that the drag operation has entered the drop target component, the drag-and-drop subsystem calls the DropTargetListener's dragEnter method, passing it a DropTargetDragEvent. There are three types of event that can be delivered to the DropTargetListener:

  • A DropTargetDragEvent, which is used when the drag first moves over the drop target component, as the cursor moves within the boundary of the component, and when the user changes the state of the shift and ctrl keys, thus affecting the drag action.

  • A DropTargetEvent, which is delivered just before a drop occurs or when the cursor is dragged outside the bounds of the drop target component without a drop having taken place.

  • A DropTargetDropEvent, used when the user requests a drop over the drop target.

These three events are related by the class hierarchy shown in Figure 8-4.

Figure 8-4. Hierarchy of drop target event classes.
graphics/08fig04.gif

The principal job of the DropTargetListener dragEnter method is to determine whether a drop operation from the drag source that initiated it would be allowed. To make this decision, the listener can use several pieces of information, including the state of the drop target component itself and the following attributes that can be obtained from the DropTargetDragEvent:

  • The current location within the drop target component of the cursor.

  • The set of actions offered by the drag source.

  • The action currently selected by the user.

  • The list of DataFlavors in which the data being transferred can be obtained.

In deciding whether a drag operation should be considered valid, the DropTarget is most likely to take into account the format in which the data is available and the action that the user wants to perform. The cursor location may not seem very important at the point at which the drag enters the component because there is no suggestion that a drop will definitely occur at this position. However, while the cursor is over the drop target component, movement of the mouse will cause the DropTargetListener's dragOver method to be called. This method is also given a DropTargetDragEvent and may also decide to accept or reject the drag operation on the same basis as the dragEnter method. In fact, there is no real difference between the dragEnter and dragOver methods in this regard, except that the dragEnter method is called only once while the cursor is over the drop target and therefore is the logical place to examine the data types available, which do not change over the entire course of the drag operation.

Let's consider the case of our JEditorPane. When the drag operation moves over the JEditorPane, the DropTarget implementation will extract the list of DataFlavors and decide whether the JEditorPane can handle data of that type. If it can't, the drag should be immediately rejected by calling the DropTargetDragEvent's rejectDrag method. Calling this method does not terminate the drag operation from the point of view of the drag source, because the user might choose to drag the cursor to a different program that might be able to accept data of that type. Neither does it stop the DropTarget's dragOver method being called while the cursor remains over the drop target component. It does, however, cause the drag-and-drop subsystem to ensure that the cursor shows that the drop target has rejected the drag.

Core Note

You'll see an example that shows how to check the list of DataFlavors for acceptable types in "Implementing a Drop Target".



Assuming that the data format is acceptable, the DropTargetListener will also check that the proposed operation, obtained using the getDropAction method, is valid. For a JEditorPane, it is likely that both ACTION_COPY and ACTION_MOVE would be acceptable but that ACTION_LINK would not, because there is no sensible meaning for the link operation in connection with an editor. If the current operation is not acceptable, the DropTargetListener has two choices:

  • Reject the drag outright by calling rejectDrag.

  • Accept the drag but suggest that a different operation would be more acceptable.

If the user suggests a link operation, the DropTargetListener for our JEditorPane could determine whether an alternative, more acceptable operation is available by calling the getSourceActions method of the DropTargetDragEvent, which returns the complete set of operations that the DragSource is prepared to perform with the available data. If this set contains either ACTION_COPY or ACTION_MOVE, the DropTargetListener can invoke the acceptDrag method passing its preference from these two operations. If not, it will call rejectDrag. Both of these actually belong to the DropTargetContext, but there are cover methods provided by DropTargetDragEvent that allow more convenient access to them. Here is how they are defined:

  • public void acceptDrag(int dragOperation);

  • public void rejectDrag ( );

The parameter supplied to acceptDrag is the drop target's preferred action, which may or may not coincide with the operation that the user is currently trying to perform. As we said earlier, if the rejectDrag method is called, the cursor is changed to indicate that the drop operation is not valid under the current conditions. At the time of writing, calling acceptDrag with an operation that is different to the one implied by the user's drag gesture does not affect the cursor if the drag source is a native application (at least not on the Windows platform), so the user has no clue that the drop, if were performed now, would actually fail. For this reason, it might be preferable to reject the drag under these circumstances. If you choose this option, you can still accept the drag later if users change their gestures to request an acceptable operation because the DropTargetListener's dragOver method will still be called. Note that the cursor is changed to signal rejection if you offer an alternative operation when the drag source is a Java application.

What about considering the state of the underlying drop target component when deciding whether to accept or reject a drag? This looks like it might be a problem, because the list of attributes available from the DropTargetDragEvent does not include the original Component, which seems inconvenient it you use one DropTargetListener to manage several drop target components. Fortunately, there are two ways to get a reference to the Component under the cursor:

  1. As shown in Figure 8-4, the DropTargetDragEvent is subclassed (via DropTargetEvent) from java.util EventObject, which provides a getSource method to obtain the source of the event; for these events, the event source is the DropTarget object associated with the drop target component. You can use the DropTarget's getComponent method to obtain a reference to the component itself.

  2. DropTargetEvent (and hence DropTargetDragEvent) also has a getDropTargetContext method that returns a reference to the DropTargetContext object associated with the drop operation. This class also has a getComponent method that you can use to get a reference to the component. This is a more type-safe method than using the source of the event because you don't need to cast the return value of getSource to DropTarget to use it.

In the case of our JEditorPane example, you might want to examine the JEditor Pane to determine whether it is editable before accepting a drag that would copy or move text from another editor into the JEditor Pane's current content. However, you might not want to apply this restriction if the user were trying to drag an entire file, because you would probably want to allow the user to open a new file in read-only mode by dragging it over the JEditorPane.

After the dragEnter method has completed, as long as the cursor remains over the drop target component, the DropTargetListener's dragOver method will be entered as the mouse is moved around, perhaps because the user is moving it to a suitable location before activating the drop. As we said earlier, this method also receives a DropTargetDragEvent and you should examine the attributes available from this event to determine whether the drag should be accepted and call either acceptDrag or rejectDrag as before. It is not worth wasting time checking the available data flavors in the dragOver method, however, because they will remain the same as the ones offered by dragEnter. It follows that if you reject the drag for this reason in the dragEnter method, you should continue to do so from dragOver. On the other hand, if your decision to accept or reject the drag depends on the location of the cursor or on the action being offered, you can use the dragOver method to re-examine whether a drop would be acceptable given the position returned from the DropTargetDragEvent getLocation method or the action returned from getDropAction.

If the user drags the cursor outside the bounds of the drop target component, the DropTargetListener's dragExit method is invoked. Unlike dragEnter and dragOver, this method is passed a DropTargetEvent, which only has methods that allow access to the DropTargetContext or to the event source. However, there is usually very little to do in the dragExit method. If you allocated any resources for the potential drop in the dragEnter method, you should free them here. Note carefully, though, that the dragExit method is also called immediately before the user performs a drop and you can't tell whether it has been called because the user has elected to drop or because the cursor has left the area controlled by the DropTarget. Another possible task for the dragExit method is to remove any "drag-under feedback" effects that might have been applied to the drop target component to make it obvious to the user that the cursor is over a valid drop target (in addition to cursor feedback, which is the job of the drag-and-drop subsystem and the DragSource). You'll see an example of this in "Providing Drag-Under Feedback".

There is one more method provided by the DropTargetListener that we haven't yet mentioned. The dropActionChanged method is called when users change the operation that they want to be performed. On both Solaris and Windows, this method is called when the state of the CTRL or SHIFT keys is changed so that a new operation would be selected. If, for example, the drag begins with neither key pressed, an ACTION_MOVE action will be offered. If, while the cursor is over the drop target component, the user pressed the CTRL key, the DropTargetListener's dropActionChanged method would be called. This method, like dragEnter and dragOver, is given a DropTargetDragEvent and, typically, it will call getDropAction to examine the action now being offered which, in this example, will now be ACTION_COPY. The dropActionChanged method may, like the other two methods, call either rejectDrag to reject the drag or acceptDrag to accept it or to offer a different operation. In fact, it probably won't surprise you to know that a typical DropTargetListener implementation will share the same code for the dragOver and dropActionChanged methods and will probably reuse it in dragEnter, while adding an extra check for the DataFlavors available from the DragSource.

Note carefully, however, that the action returned from the getDropAction method will be ACTION_NONE if the user selects an action that is not one of those in the source actions offered by the drag source. Thus, if the drag source is prepared to perform ACTION_COPY or ACTION_MOVE and the user presses both CTRL and SHIFT to select a link action, getDropAction will return ACTION_NONE instead of ACTION_LINK.

Transferring Data

If the user drags the cursor over a drop target and releases the mouse buttons, a drop will occur and the drag-and-drop subsystem will call the DropTargetListener's dragExit method followed by its drop method. We've already described the dragExit method and how you should handle it; the drop method is where you actually perform the data transfer itself. This method is defined as follows:

 public void drop (DropTargetDropEvent dtde); 

As you can see from the class hierarchy shown in Figure 8-4, DropTargetDropEvent is, like DropTargetDragEvent, derived from DropTargetEvent. Many of the methods of DropTargetDropEvent are cover methods for those of DropTargetContext and almost all of them are the same as those of DropTargetDragEvent. This is partly because the drop method, like dragEnter and dragOver, has first to decide whether the drop should be accepted before it can transfer the data and so it needs access to the same information as the other two methods.

Core Note

If the user elects to drop over a drop target, the DropTargetListener's drop method will be called, even if the drop target has previously accepted the "drop only on condition that an alternative drag operation be selected." This means that you should check that the correct conditions for the drop apply before attempting the data transfer itself and call rejectDrop if the drop is not acceptable. The drop method is not called, however, if the last dragOver or dragEnter invocation called rejectDrag.



The most important DropTargetDropEvent methods that might be used in the drop method are:

  • public void dropComplete (boolean success);

  • public DataFlavor [ ] getCurrentDataFlavors ( );

  • public List getCurrentDataFlavorsAsList ( );

  • public int getDropAction ( );

  • public Point getLocation ( );

  • public int getSourceActions ( );

  • public Transferable getTransferable (DataFlavor df)

  • public boolean isLocalTransfer ( );

  • public void rejectDrop ( );

The first thing that the drop method will usually do is to check whether to accept the drop. At this stage, the user is committed to the drop and the operation will end whether the data is transferred, but the drop method is still able to refuse to perform the transfer operation. In most cases, the initial checks in the drop method will be the same as those in the dragOver method that is, the drop operation will be checked, along with perhaps the location of the cursor if the drop target uses this to decide where to put the data from the drag source. If the conditions that relate to these checks are not met, the drop method can simply call rejectDrop and return.

The next criterion of interest will be the DataFlavor to be used to transfer the data. The complete set of DataFlavors was made available to the DropTargetListener in the dragEnter and dragOver methods and the listener will already have rejected the drop in these methods if there is no suitable transfer format. However, rejecting the drop in this way does not cancel it the user can still go ahead and attempt the drop, so it is still necessary to verify, using the DropTargetDropEvent getCurrentDataFlavors or getCurrentDataFlavorsAsList methods, that there is a data transfer format that is compatible with the needs of the drop target component. In the drop method, however, this check will also result in the selection of the transfer format to be used. If there is no mutually agreeable DataFlavor, the rejectDrop method should be called.

Having decided on the transfer flavor, the drop method will use the DropTargetDropEvent getTransferable method to obtain a reference to the Transferable that can provide the drag source's data and then invoke its getTransferData method to obtain the data content:

 public Object getTransferData (DataFlavor flavor); 

Once the data transfer is complete, the DropTargetDropEvent dropComplete method should be called with argument true if the drag source is to consider the operation to have been a success, or false if it should not. Completing the operation and deciding whether to notify a success or a failure is not always a straightforward matter, however. You'll see some examples that demonstrate exactly how this is done in the rest of this chapter. For now, bear the following points in mind:

  • The data returned from the getTransferData method is in the form of an Object. In fact, what this Object actually is depends on the DataFlavor. In some cases, such as when transferring a Unicode string using DataFlavor.stringFlavor, the Object returned is the actual data which simply needs to be cast to the appropriate type (String) and used directly. In other cases, though, the Object returned is an inputstream which the DropTargetListener must arrange to read from to get the actual data. Any time you read from an Inputstream, there is the possibility of an IOException that would cause the drop to fail and, in this case, you should call dropComplete (false) to indicate this to the drag source. We'll say more about the various data formats later in this chapter.

  • If you're transferring a file or a set of files from a program like Windows Explorer, the transfer takes place using a flavor called DataFlavor.javaFileListFlavor. The Object returned from getTransferData is of type java.util.List and contains a list of objects of type File, one for each file involved in the transfer. Having obtained the file list, the DropTargetListener might apply additional checks to some or all of the files, such as verifying that it is dealing with files that it can read or is not dealing with directories if this is not appropriate in the context of the operation. Only if these checks succeed will the actual operation proceed and the DropTargetListener call dropComplete (true).

When the drop operation is completed and the dropComplete or rejectDrop method has been called, the drag source is notified of the result in the dragDropEnd method of its DragSourceListener. We haven't said much about the events delivered to the DragSourceListener so far in this chapter because they are usually of more importance to the drag-and-drop subsystem than to application code. If the application needs to find out whether the operation succeeded, however, it can do so in the dragDropEnd method by calling the getDropSuccess method of the DragSourceDropEvent delivered to it, which returns the same value passed by the DropTargetListener to the dropComplete method. The drag source can also use the getDropAction method to find out which of its offered actions was actually accepted. This information is important if the drag source is a text editor and the user has dragged text elsewhere. If the ACTION_MOVE operation was selected and the operation succeeded (and under no other conditions), the DragSourceListener will have to arrange for the source text component to delete the selected text.

 

 



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