The Source for Java Technology Collaboration
User: Password:



Shannon Hickey

Shannon Hickey's Blog

Location-Sensitive Drag and Drop in Mustang

Posted by shan_man on January 17, 2006 at 12:19 PM | Comments (14)

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


Bookmark blog post: del.icio.us del.icio.us Digg Digg DZone DZone Furl Furl Reddit Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment

  • Hello,

    I have to object, it is possible to implement this feature prior to mustang. We did this in our software. We also had several other cool tree features. But a location sensitive feedback in a jtree is clearly possible. even under jdk 1.4.

    Greetings, Christian

    Posted by: landrus on January 19, 2006 at 06:01 AM

  • Very very useful feature. With more and more things like this happening in Swing I can't see Ajax being anything more than a fad.

    Posted by: c_armstrong on January 19, 2006 at 07:54 AM

  • hi where is the source samples ?

    Posted by: afei on February 22, 2006 at 02:13 AM

  • hi have you tried dropping a file from a native resource into your JTree. That's gonna add more functionality !!

    Posted by: sauravj on March 15, 2006 at 04:10 AM

  • I think the point of the article was implementing the drop location feature using the built-in swing drag and drop, not a custom one.

    Personally, I've always found the built-in support inappropriate or insufficient, especially for trees.

    Posted by: twalljava on April 15, 2006 at 07:12 AM


  • sauravj, you most definitely can drag a file (or any other content) from a native platform into Swing components. You just need to implement a TransferHandler to handle the import. Thanks!

    Posted by: shan_man on April 18, 2006 at 09:17 AM


  • twalljava: I completely agree with your assessment of the previous built-in drag and drop support. That's why we set out to make it first class in Mustang. The hope is that it now provides evereything you could need. But please do let us know if anything's missing. Thanks!

    Posted by: shan_man on April 18, 2006 at 09:21 AM

  • I think I've seen mention of this, but one thing I've been working on to support in 1.4 is changing the drop action to the first one supported between source and target if no modifiers are used, and refusing to fall back to a default if modifiers *are* used.

    The idea here is that if the user doesn't request anything specific, s/he should get the first reasonable match. If the user is explicitly requesting a copy, s/he should most definitely *not* get a move.

    For 1.4, I had to resort to a global modifier setting on the drag handler that can be checked by the drop handler, since you can't otherwise detect whether a modifier is in use during a native drag (is there any plan for a keyboard state class similar to the mouse state class?).

    There is also some difficulty in changing drop acceptability on the fly; I've seen inconsistent results (running under 1.5) where sometimes the drop action seems to get stuck in the "not allowed" state and cannot be "re-accepted".

    Posted by: twalljava on April 29, 2006 at 07:17 AM

  • I wished landrus gave some more information on his solution to this problem for jdk 1.4. I've been searching for it for some time. Does anyone else know how to make it.

    I also get stuck in canImport when deciding whether I can drop a node on a target node. One "possible" solution could be to save the current dragged node and to retrieve the target node by the current mouse position + tree.getPathForLocation. But how to get the the mouse position when a drag action is active? It is not possible to use a MouseMotionListener that tracks the position, because the drag action blocks the MouseMotionListener. The only way I see is to use some kind of (static) method such as getCurrentMousePosition(). Unfortunatelly, I could not find any. Maybe any of you?

    Posted by: majax1 on May 08, 2006 at 07:14 AM

  • The mouse location is included in the dragOver/dragMouseMoved methods for DragSourceListener/DragSourceMotionListener and DropTargetListener.dragOver.

    Posted by: twalljava on June 26, 2006 at 01:27 PM

  • Hello all and Shannon particularly, Your blog is very interesting I'd only wish you to update the source code, i.e. to replace the TransferHandler.TransferInfo with TransferHandler.TransferSupport as it has already been apparently renamed in mustang. I would also appreciate if you would post the full source code for the little application above. Hope you're still watching this blog. Kind regards.

    Posted by: xlinuks on August 14, 2006 at 10:20 AM


  • twalljava: While I can't help you out with 1.4, I can tell you that you can now explicitly choose the drop action in Java SE 6. Please see my later blog entitled Choosing the Drop Action, and Further Changes to Swing Drag and Drop for details.


    With respect to your mention of the drop getting stuck in the "not allowed" state, that's one of the things I've addressed in this blog. Prior to Java SE 6, canImport was only called on a drag enter. It's now called repeatedly during dragging.

    Posted by: shan_man on November 22, 2006 at 12:18 PM


  • xlinuks: While I want to preserve what I originally publish in my blogs, I agree that there needs to be mention when an API is updated. As such, I've gone back and added notes where the API has subsequently changed. Also, if you haven't read it already, I've published a later blog detailing the API changes: Choosing the Drop Action, and Further Changes to Swing Drag and Drop.

    Posted by: shan_man on November 22, 2006 at 12:37 PM

  • Friends, I've now seen multiple requests for the full source code for the demos used in my blogs. I'll see what I can do about making it available. Thanks!

    Posted by: shan_man on November 22, 2006 at 12:39 PM



Only logged in users may post comments. Login Here.


Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds