Skip to main content

Location-Sensitive Drag and Drop in Mustang

Posted by shan_man on January 17, 2006 at 3:19 PM EST

Hello again friends!

Prompted by an excellent question from wrandelshofer in the comments section of my recent Swing Drag and Drop blog entry, I'm jumping on the opportunity to tell you more about the enhancements made in Mustang. In particular, I'd like to talk about how these changes have enabled location-sensitive Drag and Drop.

In order to clarify the concept, I'll paraphrase the question that I've just referred to: Is it possible to allow the user to drop into some locations of a JTree and not others, and have Swing indicate this by only showing the drop location when the mouse is over acceptable drop locations? The answer is a resounding YES; this is the default behavior in Mustang! Let's take a look at how this has been enabled.

Prior to Mustang, developers could not implement this very important behavior due to oversights in the Swing implementation. The first problem, as I discussed in my previous blog entry, was that the drop location wasn't easily available to the TransferHandler during a drag operation. The astute developer could often overcome this limitation, however, after discovering that Swing changed the selection instead. But it was actually a second more subtle and hidden problem in the implementation that was the limiting factor: a problem with when TransferHandler called the canImport method. As you know, the canImport method gives you control over whether or not a component should accept a particular drag operation. Prior to Mustang, though, canImport was only called when a drag operation entered a component, and the returned value was then used for the duration of the mouse's visit to that component. This gave the developer absolutely no mechanism to return different values based on the drop location, or any other factors that might change throughout the operation.

Enter Mustang! With the recent enhancements made to Swing Drag and Drop, canImport is now called continuously by a component's TransferHandler while a drag and drop operation is occuring over it. As such, the acceptability of a transfer can change as many times, and based on whatever criteria (including the drop location), you choose. You need only return a different value from canImport and Swing will respond. I'll elaborate on what that response is. When canImport returns true to accept a transfer, Swing shows the normal drag and drop mouse cursor and visually indicates the drop location, as discussed in my previous blog entry. When canImport rejects a drag, on the other hand, the "no-drag" mouse cursor is shown and no drop location is indicated. Let's see this in action - click here to launch a Java Webstart demo (requires build 76 or greater of Mustang).

Below you'll see two screen shots of how the demo appears. At the very top is a drag source, with the label "Drag from here:". You'll use this component to initiate drags when playing with the demo. The middle component is a JTree that's been configured to accept drops on any node except for "names" and its descendants. At the bottom is a JComboBox that we'll talk about a bit later, when I introduce another new method in Mustang to allow further customization of when drop locations are shown. For now, simply note that it is set to "Default", meaning that nothing has been changed from the default behavior of showing drop locations when accepted, and not showing them when rejected.

Initiate a drag by pressing on top of the drag source and dragging the mouse a short distance. Drag into the tree and start moving down on top of the nodes. As you hover the mouse over most of the nodes, the drag acceptibility is indicated by both the mouse cursor and by the node becoming highlighted. This is portrayed in Screen Shot 1, which shows an acceptable drag over the "yellow" node. Likewise, Screen Shot 2 shows what happens when you drag over the "names" node or any descendant, which I've mentioned have been configured to reject the drag. The "no-drop" cursor is shown, and the drop location isn't indicated.

Screen Shot 1 Screen Shot 2
 

Here's the implementation of canImport from the demo, showing how drops on top of "names" and descendants has been prevented:


Important Note: This blog was published before finalization of the Swing drag and drop API for Java SE 6. As such, there have since been minor modifications. In particular, TransferInfo, as shown in the following code snippet, has been renamed to TransferSupport. For full details on these changes, please see my later blog entitled Choosing the Drop Action, and Further Changes to Swing Drag and Drop.

public boolean canImport(TransferHandler.TransferInfo info) {
    // for the demo, we'll only support drops (not clipboard paste)
    if (!info.isDrop()) {
        return false;
    }

    // we only support importing Strings
    if (!info.isDataFlavorSupported(DataFlavor.stringFlavor)) {
        return false;
    }

    // fetch the drop location (it's a JTree.DropLocation for JTree)
    JTree.DropLocation dl = (JTree.DropLocation)info.getDropLocation();

    // fetch the path from the drop location
    TreePath dropPath = dl.getPath();

    // we'll reject invalid paths, and descendants of the "names" node
    if (dropPath == null || NAMESPATH.isDescendant(dropPath)) {
        return false;
    }

    return true;
}

So what if you want to change an aspect of this behavior from the default? What if you want to always show the potential drop location, regardless of the acceptability of the transfer, so that the user can see what item they're hovering over? Or say for some reason you prefer not to indicate the drop location for some items, even though you want to accept the transfer itself?


Important Note: This blog was published before finalization of the Swing drag and drop API for Java SE 6. As such, there have since been minor modifications. In particular, shouldIndicate, as mentioned in the remainder of this document, has been removed in favor of a new setShowDropLocation method on TransferSupport. For full details on these changes, please see my later blog entitled
Choosing the Drop Action, and Further Changes to Swing Drag and Drop.

TransferHandler now calls a new method, allowing you to control this too:

boolean shouldIndicate(TransferInfo info, boolean canImport)

This method returns whether or not the drop location should be indicated for the given transfer, and is called after the canImport method. The arguments are the TransferInfo for the transfer in question, and the return value from the preceding call to canImport. The default implementation simply returns the value of the canImport argument, causing the drop location to be shown if and only if the TransferHandler has already said that it can accept the import; the behavior that I've described above. By simply overriding this method, you can customize the drop location indication as it works for your application. For example, the following simple override will cause the drop location to always be shown, even if the canImport method rejects the transfer:

public boolean shouldIndicate(TransferInfo info, boolean canImport) {
    return true;
}

Likewise, returning false causes the drop location to never be shown. Of course, the implementation can be more involved, possibly inspecting the TransferInfo, depending on how specific your logic needs to be. An important thing to note is that this method is designed specifically for configuring the drop location indication, and it does not affect how the mouse cursor appears. The mouse cursor needs to tell the user whether or not the drag is acceptable and, as such, needs to stay true to the return value of canImport. Let's have a look at this in practice, by bringing up the demo again.

This time we will play with the JComboBox at the bottom, which has been set up to dictate how shouldIndicate will return. Switch the option to "Always" to have shouldIndicate always return true. Dragging over the JTree you'll see that the drop location is shown for all nodes, including "names" and its descendants which won't actually accept the transfer. This can be seen in Screen Shot 3 below. Switching the JComboBox to "Never" configures shouldIndicate to always return false. This causes the drop location to not be indicated for any node, regardless of whether or not the transfer is acceptable. Screen Shot 4 shows this case, where the "yellow" node is not highlighted even though the mouse cursor is showing an acceptable transfer.

Screen Shot 3 Screen Shot 4
 

Pretty neat! Location-sensitive Drag and Drop for Swing, new in Mustang. I encourage you to keep those questions and comments coming. You may just prompt my next blog entry! All the best :)

Related Topics >>