24.5 Rearranging Trees


The JTree class is a ripe candidate for the DnD system. Adding nodes to a tree, moving nodes from one tree to another, or even just rearranging the nodes within a single tree can all be accomplished quite easily with DnD gestures. Unfortunately, JTree does not have any built-in support for these actions. The next few examples take a look at extending JTree to include such features.

24.5.1 A TransferHandler Example

At the beginning of the chapter we mentioned that 1.4 made several parts of the DnD API obsolete at least for simple tasks. That point is worth reiterating. If you have a simple drop target or a simple drag source that requires custom handling, you can use the TransferHandler class and the transferHandler property of JComponent to do most of your work.

One common feature of graphical trees is the ability to add nodes by dropping them into an existing tree. While the TransferHandler approach cannot handle all of the intricacies of adding and rearranging trees, it can make basic imports very straightforward.

Here's an example of a tree that can accept incoming lists of files from the native windowing system. You can drag icons from your desktop or file manager and drop them into the tree. They are appended to the root folder.

/*  * FSTree.java  * A sample component for dragging & dropping a collection of files  * into a tree.  */ import javax.swing.*; import javax.swing.tree.*; import java.awt.dnd.*; import java.awt.datatransfer.*; import java.util.List; import java.util.Iterator; import java.awt.Point; import java.awt.Rectangle; import java.awt.Insets; import java.io.File; import java.io.IOException; public class FSTree extends JTree {   public FSTree( ) { super( ); init( ); }   public FSTree(TreeModel newModel) { super(newModel); init( ); }   public FSTree(TreeNode root) { super(root); init( ); }   public FSTree(TreeNode root, boolean asks) { super(root, asks); init( );}   private void init( ) {     // We don't want to export anything from this tree, only import.     setDragEnabled(false);     setTransferHandler(new FSTransfer( ));   }   public class FSTransfer extends TransferHandler {     public boolean importData(JComponent comp, Transferable t) {       // Make sure we have the right starting points.       if (!(comp instanceof FSTree)) {         return false;       }       if (!t.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {         return false;       }       // Grab the tree, its model, and the root node.       FSTree tree = (FSTree)comp;       DefaultTreeModel model = (DefaultTreeModel)tree.getModel( );       DefaultMutableTreeNode root = (DefaultMutableTreeNode)model.getRoot( );       try {         List data = (List)t.getTransferData(DataFlavor.javaFileListFlavor);         Iterator i = data.iterator( );         while (i.hasNext( )) {           File f = (File)i.next( );           root.add(new DefaultMutableTreeNode(f.getName( )));         }         model.reload( );         return true;       }       catch (UnsupportedFlavorException ufe) {         System.err.println("Ack! we should not be here.\nBad Flavor.");       }       catch (IOException ioe) {         System.out.println("Something failed during import:\n" + ioe);       }       return false;     }          // We support only file lists on FSTrees.     public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {       if (comp instanceof FSTree) {         for (int i = 0; i < transferFlavors.length; i++) {           if (!transferFlavors[i].equals(DataFlavor.javaFileListFlavor)) {             return false;           }         }         return true;       }       return false;     }   } }

As you can see, the majority of the work goes into overriding the two import methods from TransferHandler. The tree component is quite simple. We disable dragging and install our new DnD handler. The FSTransfer inner class iterates through the files dropped on the tree and appends them to the root folder. This example is more for show, so we just store the name of the file in the tree. A slightly more advanced FSTree would probably store the file itself.

While it's not exciting in static mode, after adding a few entries from the desktop of a Windows system, our new tree looks like Figure 24-7.

Figure 24-7. A drag operation resulting in a filled-out, drop-enabled JTree
figs/swng2.2407.gif

And here's the simple program that builds the instance of FSTree:

/*  * FSTest.java  * A quick test environment for the FSTree component.  */   import javax.swing.*; import javax.swing.tree.*; public class FSTest extends JFrame {   public FSTest( ) {     super("FSTree Component Test");     setSize(300,300);     setDefaultCloseOperation(EXIT_ON_CLOSE);     FSTree fst = new FSTree(new DefaultMutableTreeNode("Starter"));     getContentPane( ).add(new JScrollPane(fst));     setVisible(true);   }   public static void main(String args[]) {     new FSTest( );   } }

Of course, a slightly more advanced FSTree would allow you to add nodes to something other than the root folder. If you want to manage drops based on things such as the (x,y) coordinates where the drop occurred, the TransferHandler won't do the trick. You'll need to rely on the lower-level classes and events that we looked at in this chapter. The next section looks into that low-level control in more detail. If you use a 1.2 or 1.3 system, the process presented in the next section is the only way to go. But again, even if you have the TransferHandler mechanism from 1.4, it might not always be sufficient.

24.5.2 A Rearranging Example

We can apply all this lower-level DnD functionality to JTree objects to overcome a deficiency in the user interface. On its own, JTree has no facility for visually rearranging its leaves and branches. We can create appropriate DnD event handlers from scratch and our own transferable type to implement this functionality for any JTree we build. The code in this example works with SDK 1.2 and higher. Figure 24-8 shows the resulting tree.

Figure 24-8. An editable tree before and after items have been dragged and dropped
figs/swng2.2408.gif

"Before" and "after" screenshots still don't do this application justice. But notice that you can move entire folders as well as individual items. Any node can be dragged and dropped into any folder. You are, however, limited to dropping items into a folder. As with the other examples in this chapter, you must compile the code and play with the application to get the full effect. In the meantime, here's the source code for the Drag half of our example. Don't worry too much about the TransferableTreeNode class (we discuss this in greater detail in the next section). For now, just think of it as a DnD wrapper for a tree node.

/*  * TreeDragSource.java  * A more involved test of the DnD system to see if  * we can create a self-contained, rearrangable JTree. This is  * the Drop half.  */ import java.awt.*; import java.awt.dnd.*; import java.awt.datatransfer.*; import java.awt.event.*; import java.io.*; import java.util.*; import javax.swing.*; import javax.swing.tree.*; public class TreeDragSource  implements DragSourceListener, DragGestureListener {   DragSource source;   DragGestureRecognizer recognizer;   TransferableTreeNode transferable;   DefaultMutableTreeNode oldNode;   JTree sourceTree;   public TreeDragSource(JTree tree, int actions) {     sourceTree = tree;     source = new DragSource( );     recognizer = source.createDefaultDragGestureRecognizer(        sourceTree, actions, this);   }   // Drag gesture handler   public void dragGestureRecognized(DragGestureEvent dge) {     TreePath path = sourceTree.getSelectionPath( );     if ((path == null) || (path.getPathCount( ) <= 1)) {       // We can't really move the root node (or an empty selection).       return;     }     // Remember which node was dragged off so we can delete it to complete a move     // operation.     oldNode = (DefaultMutableTreeNode)path.getLastPathComponent( );     // Make a version of the node that we can use in the DnD system.     transferable = new TransferableTreeNode(path);     // And start the drag process. We start with a no-drop cursor, assuming that the     // user won't want to drop the item right where she picked it up.     source.startDrag(dge, DragSource.DefaultMoveNoDrop, transferable, this);     // If you support dropping the node anywhere, you should probably start with a     // valid move cursor:     //     source.startDrag(dge, DragSource.DefaultMoveDrop, transferable, this);   }   // Drag event handlers   public void dragEnter(DragSourceDragEvent dsde) { }   public void dragExit(DragSourceEvent dse) { }   public void dragOver(DragSourceDragEvent dsde) { }   public void dropActionChanged(DragSourceDragEvent dsde) { }   public void dragDropEnd(DragSourceDropEvent dsde) {     if (dsde.getDropSuccess( )) {       // Remove the node only if the drop was successful.       ((DefaultTreeModel)sourceTree.getModel( ))             .removeNodeFromParent(oldNode);     }   }   } 

This allows us to recognize a drag and build a valid Transferable object to accompany the drag. The drop side needs to take the drop, figure out where it landed, and decide what to do with it. If the drop occurred over a folder and contained one of our special tree node objects, we accept the drop. If it was over a file or was not a tree node, we reject the drag. (And we finally get to put some of those weird JTree methods to use!)

/*  * TreeDropTarget.java  * A more involved test of the DnD system to see if  * we can create a self-contained, rearrangable JTree. This is  * the Drop half.  */ import java.awt.*; import java.awt.dnd.*; import java.awt.datatransfer.*; import java.awt.event.*; import java.io.*; import java.util.*; import javax.swing.*; import javax.swing.tree.*; public class TreeDropTarget implements DropTargetListener {   DropTarget target;   JTree targetTree;   public TreeDropTarget(JTree tree) {     targetTree = tree;     target = new DropTarget(targetTree, this);   }   // Drop event handlers   public void dragEnter(DropTargetDragEvent dtde) { }   public void dragOver(DropTargetDragEvent dtde) { }   public void dragExit(DropTargetEvent dte) { }   public void dropActionChanged(DropTargetDragEvent dtde) { }   public void drop(DropTargetDropEvent dtde) {     // Figure out where the drop occurred (in relation to the target tree).     Point pt = dtde.getLocation( );     TreePath parentpath = targetTree.getClosestPathForLocation(pt.x,pt.y);     // For simplicity's sake, we'll assume that the tree uses the DefaultTreeModel     // and DefaultMutableTreeNode classes.     DefaultMutableTreeNode parent =        (DefaultMutableTreeNode)parentpath.getLastPathComponent( );     // Now check to see if it was dropped on a folder. If not, reject it.     if (parent.isLeaf( )) {       dtde.rejectDrop( );       return;     }     try {       // Grab the data.       Transferable tr = dtde.getTransferable( );       DataFlavor[] flavors = tr.getTransferDataFlavors( );       for (int i = 0; i < flavors.length; i++) {         if (tr.isDataFlavorSupported(flavors[i])) {           // It's a usable node, so pull it out of the transferable object and add it           // to our tree.           dtde.acceptDrop(DnDConstants.ACTION_MOVE);           TreePath p = (TreePath)tr.getTransferData(flavors[i]);           DefaultMutableTreeNode node =              (DefaultMutableTreeNode)p.getLastPathComponent( );           DefaultTreeModel model=(DefaultTreeModel)targetTree.getModel( );           model.insertNodeInto(node, parent, 0);           // Last but not least, mark the drop a success.           dtde.dropComplete(true);           return;         }       }       dtde.rejectDrop( );     } catch (Exception e) {       e.printStackTrace( ); // Just for debugging, really        dtde.rejectDrop( );     }   } }

Putting it all together does not take much effort. If we want to create a single rearrangeable tree, we simply make it both a drag source and a drop target. With the separation of our DnD handlers, we can also enforce a "source-only" tree by attaching only the drag source half. Without the drop half, drops on the tree would simply be ignored.

/*  * TreeDragTest.java  * A more involved test of the DnD system to see if  * we can create a self-contained, rearrangable JTree.  This is  * the test framework that builds a tree and attaches both the   * drag and the drop support.  */ import java.awt.*; import java.awt.dnd.*; import java.awt.datatransfer.*; import java.awt.event.*; import java.io.*; import java.util.*; import javax.swing.*; import javax.swing.tree.*; public class TreeDragTest extends JFrame {   TreeDragSource ds;   TreeDropTarget dt;   JTree tree;   public TreeDragTest( ) {     super("Rearrangeable Tree");     setSize(300,200);     setDefaultCloseOperation(EXIT_ON_CLOSE);     // Create a quick JTree to use for demonstration purposes. The default     // constructor for JTree creates just such a tree with a few categories such as     // food and sports to play around with.     tree = new JTree( );     getContentPane( ).add(new JScrollPane(tree), BorderLayout.CENTER);     ds = new TreeDragSource(tree, DnDConstants.ACTION_MOVE);     dt = new TreeDropTarget(tree);     setVisible(true);   }   public static void main(String args[]) {     new TreeDragTest( );   } }  


Java Swing
Graphic Java 2: Mastering the Jfc, By Geary, 3Rd Edition, Volume 2: Swing
ISBN: 0130796670
EAN: 2147483647
Year: 2001
Pages: 289
Authors: David Geary

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