Wrap it around a (J) Tree!

I don't mean you have to wrap your car around a tree like this. I'm talking of JTrees and Drag and Drop support.

I'm talking of JTrees and Drag and Drop support.

Inspired by the Swing Hack # 27 (drag and drop in JTrees), I've built a tiny library that automagically provides drag and drop support for those JTrees built using a DefaultTreeModel. As a plus this wrapper eases popup menu handling, performs autoscrolling, automagically expands nodes, does not require the JTree to be extended, cooks the breakfast for you in the mornings and, well, has many other interesting features you may find useful.

Swing Hack #27 (+ 0.02 EUR)

If you use Swing regularly you probably are aware of the excellent "Swing Hacks" book. If you aren't then you either live in the moon ;-) or you probably want to go take a look at these sample chapters.

Swing Hack #27 presents an excellent way to handle drag and drop with trees. Drag and drop with JTrees is quite useful, because it provides the user with a powerful way to modify hierarchical data structures. But there're some little issues with the hack that I didn't like. So I decided to hack a little bit more on Swing Hack #27 to solve these little issues. Just adding my 0.02 cents. The result (Swing Hack #27.02) is what I call a TreeWrapper.

Well, looking back I admit I've suffered a little bit of featuritis ;-). But, what the heck, this is a hobby for me so it's not bad having some fun with Swing from time to time, right? ;-)

Here's a list of requirements and features of this "TreeWrapper".

Requirement #1: No need to extend JTree

I don't want to extend JTrees. I don't like extending Swing components. That's a hassle. I prefer using wrappers around existing standard components. So the first requirement is that TreeWrapper doesn't ask you to extend your JTrees. You can use a TreeWrapper around any JTree (or derivative).

Requirement #2: Be respectful with existing cell renderers

I usually want to use custom TreeCellRenderers in my JTrees. For adding special icons to each node, for instance. And I didn't like Swing Hack #27 substituting my custom renderers with other ones that give visual feedback of drag and drop to the user.

How to solve the issue? Well, TreeWrapper uses a specific TreeCellRenderer that proxies all requests to the original renderer but that provides visual feedback to the user by attaching special borders to the original renderer. I called that a "DnDCellRendererProxy". This "DnDCellRendererProxy" draws a special border (taken from a small DnDBorderFactory class) around the original renderer depending on the node to be rendered.

So TreeWrapper respect existing cell renderers (but for borders, sorry).

Requirement #3: Automatic expansion of nodes

It's really annoying to perform a drag and drop operation on a close folder if that folder does not expand automatically when you drag over it. You'll have to cancel your drag and drop operation, go to that folder and expand it. Weird.

So the next requirement for TreeWrapper is that it performs automatic expansion of collapsed nodes during the drag and drop operation.

Requirement #4: Automatic scrolling

By default JTrees don't scroll during drag and drop operations. In order to allow autoscrolling you can extend the JTree as described in this blog entry by Santhosh Kumar.

But since I don't want to extend JTree I had to find a different way of doing it. My solution was to implement a custom DropTarget and implement the updateAutoscroll(Point) and initializeAutoscrolling( Point ) methods. That way I don't have to extend the JTree and I can meet requirement #1.

Requirement #5: Multi-Tree support

I also wanted to allow support for different trees. This is, I wanted to allow the user to drag and drop in-between different JTrees. This further enhances the possibilities to modify a hierarchical data structure (such as that you probably use to handle your RSS feeds in your favourite RSS Feed Reader).

You can, for instance, present the user with two trees so that the user can drag and drop nodes from one to the other.

Doing multi-tree support is a little bit tricky, because you have to handle a source node and a source tree (from which you drag), as well as a target node and a target tree (where you drop). And while you're dragging over your targets, how do you know which are the drag sources? After all you're dragging over another JTree instance (and listening on another listener)...

One could use static variables to hold the drag sources (the JTree and the node) but this is a very ugly solution...

But JDK5 comes to the rescue. Since JDK5 you can retrieve the Transferable) you're transferring while in a drag and drop operation. This is really good because then you can transfer the source tree and source node and take a look at them.

So that was the solution I devised: retrieve the Transferable during the dragOver in my DropTargetListener and take a look at those values. That's the easiest way I could find to support drag and drop between different trees.

Requirement #6: Automatic detection of parent and children

I don't think it makes sense at all to drag a parent into a children, right? So TreeWrapper detects automatically that a drop node is a child of the node you're dragging and automatically bans that operation.

Requirement #7: TreeNodes or MutableTreeNodes?

MutableTreeNodes are cool because this interface contains methods for inserting and deleting child nodes. But your JTree may contain also plain TreeNodes. The TreeWrapper handles this automatically: allows dropping only on nodes that are "instanceof" MutableTreeNodes (and bans drag and drop on the rest of nodes).

Requirement #8: DnDConstants.ACTIONCOPYOR_MOVE

TreeWrapper copes both with copying and moving nodes around. Press "Ctrl" before (and while) doing your drag and drop operation (well, at least in my Linux box) and you'll be copying nodes. TreeWraper creates copies in the form of DefaultMutableTreeNodes.

Requirement #9: Strings, too

You can also drag and drop Strings into JTrees wrapped with a TreeWrapper. This is cool because most operating system applications allow dragging and dropping Strings. Most text fields (such as that in the Mozilla URL bar) allow for drag and drop. The TreeWrapper handles this and inserts new nodes with the given String (well, after asking for permission to the SimpleDNDListeners, of course)

Requirement #10: I want to be under control

The TreeWrapper handles automatically copying and moving nodes. But what if I want to handle those myself?

And what if I want to decide which drag and drop operations are valid? It would be nice to be able to veto drag and drop operations, right?

So I decided to fire events to listeners to both ask for permission for a DnD operation and to inform when a drop operation is performed. The TreeTreeDnDListener and StringTreeDnDListener are responsible for that. TreeTreeDnDListeners are informed when a drag-and-drop operation is performed between trees, whereas StringTreeDnDListeners are informed when a string is dropped into a tree.

Requirement #11: Easy to use and reuse

And finally I wanted all this stuff to be easy to use and reuse. The idea was to fight with drag and drop for JTrees once and forall. And don't be reinveinting the wheel every once and then. I know drag and drop is changing in JDK6 (see Location-Sensitive Drag and Drop in Mustang) but, well, I'll fight with that in the future, whenever I move to Mustang.

Feature #1: Popup menus, too

Although this was not a requirement for me, the fact is that a TreeWrapper could be useful to handle pop up menus in trees. You may want to provide different popup menus depending on the node you're visiting.

So I decided to create a CustomPopupHandler interface I could extend. The TreeWrapper asks the CustomPopupHandler (if any) for a specific popup menu for a given node.

Feature #2: Drag images

JavaWorld's Tip 114 shows an interesting way to add drag images to JTrees. But it requires extending the JTree, and I didn't like that. Furthermore, the "paintImmediately" method call produces lots of flickering.

But anyway JavaWorld's Tip 114 did had a nice feature I wanted to explore: add drag images to drag-and-drop operations. It seems the JDK supports this in some platforms), although it seems to be disabled both in Linux and Windows :-(

How to use

Using a TreeWrapper requires a single line of code:

JTree myTree = ...
TreeWrapper myWrapper = new TreeWrapper( myTree );

If you need popup support then some more lines are required:

JPopupMenu menu = ...
JTree myTree = ...
CustomPopupHandler myHandler =
  new CustomPopupHandler()
  {
    public JPopupMenu getMenuAt( JTree aTree,
      TreeNode aNode )
    {
      return menu;
    }
  };
TreeWrapper myWrapper = new TreeWrapper( myTree );
myWrapper.setCustomPopupHandler( myHandler );

And if you need further control on drag and drop then you'll have to implement a SimpleDNDListener and add it to the TreeWrapper. Try it yourself

So I placed this stuff in my attic, under the LGPL license (java web start demo there, as well, for JDK5, of course). Just in case you're interested.

You can give it a try, mixing apples and oranges is permitted here! ;-)

In turn I'd appreciate feedback and code snippets that improve the TreeWrapper.

Happy tree-wrapping,

Antonio

blog comments powered by Disqus