Some reader favorites:
EJB fundamentals and session beans
Create a scrollable virtual desktop in Swing
Wizard API updated!
Tim Boudreau has released a new version of the Swing Wizard library (version 0.997) that fixes the WizardException bug reported in JavaWorld's recent Open Source Java Project profile. The article's examples have been reworked to test out the new, improved WizardException. Thanks, Tim, for this helpful fix!
Open Source Java Projects: The Wizard API
Unlike simple GUI components, JTree objects contain substructure, which makes implementing drag and drop for JTree controls more difficult than for simpler GUI components. In fact, I read in one newsgroup thread that adding drag and drop to a JTree is impossible.
The big stumbling block is that since the JTree's nodes are the objects being dragged, it appears that they should implement the drag-and-drop interfaces.
The truth is that each node is completely unaware that it is even contained in a JTree; there is no getTree() in the TreeNode interface. Hence, the drag and drop interfaces must be implemented by the class listening for the JTree events.
For illustration purposes, let's consider a JTree that displays a family tree. All females in the tree may have children.
Males in the tree don't have children. To distinguish between node types, you can force the male node icon to be the leaf
icon and females to be the internal node icon (which is a folder and implies the ability to have children). To do that, you
need to override the isLeaf() and getAllowsChildren() methods of the DefaultMutableTreeNode class (there are other ways, but that's probably the easiest). Only one class is used for all the node objects, male and
female. If a node is male, the isLeaf() method will always return true and the getAllowsChildren() method will always return false. The code will look similar to the following:
public boolean isLeaf() {
return isMale;
}
public boolean getAllowsChildren() {
return !isMale;
}
I'll continue by examining the specific drag-and-drop interfaces. From the java.awt.dnd package, there are DragGestureListener (drag gesture recognizer), DragSourceListener (drag source), and DropTargetListener (drop target). From the java.awt.datatransfer package, there is Transferable.
The Transferable interface will be implemented by the object that encapsulates the node data, but the rest of the interfaces will be implemented
by the class that listens to the JTree events (presumably a class that subclasses JTree).
The DragGestureListener interface is invoked when a DragGestureRecognizer object detects a drag-initiating gesture.
Below is an example code listing of this interface's only method. Its code will start the drag action.
1 public void dragGestureRecognized(DragGestureEvent e) {
2 DefaultMutableTreeNode dragNode = getSelectedNode();
3
4 if (dragNode != null) {
5 Transferable transferable =
6 (Transferable) dragNode.getUserObject();
7
8 Cursor cursor = selectCursor (e.getDragAction())
9
10 dragSource.startDrag(e, cursor, transferable, this);
11 }
12 }
13
14 private Cursor selectCursor (int action) {
15 return (action == DnDConstants.ACTION_MOVE) ?
16 DragSource.DefaultMoveDrop : DragSource.DefaultCopyDrop;
17 }
The following list explains the steps you take in Lines 2 through 10 in the above code:
getSelectedNode().)
Transferable object later).
e is the DragGestureEventcursor is the cursor you wish to use
transferable is the object to be transferred
dragSourceListener is the component that has implemented the DragSourceListener interface
The DragSourceListener interface is implemented by the component that contains the draggable object. There are five possible drag actions, and the
DragSourceListener has a method to handle each one. The code in those methods is not required. Some of them can be left empty, but the dragEnter and dragOver are useful for setting the cursor type to indicate whether the drop target is valid. I'll revisit how you do this later in
the article.
The DropTargetListener interface is implemented by the component that will drop the object. Once again, there are five possible drop actions and
the DropTargetListener has a method to handle each one. You use the dragOver method to obtain the cursor location and decide if the drop location is valid, but the most interesting method is the drop method. Below is an example code listing of that method. Its code completes the desired action to take when a drop action
occurs.
//The Drag operation has terminated with a Drop on this DropTarget
1 void drop(DropTargetDropEvent dtde) {
2 Transferable transferable = dtde.getTransferable();
3
4 //flavor not supported, reject drop
5 if (!transferable.isDataFlavorSupported( <DATA FLAVOR> )) {
6 e.rejectDrop();
7 return;
8 }
9
10 DefaultMutableTreeNode oldParent =
getSelectedNode().getParent();
11
12 Point loc = dtde.getLocation();
13 TreePath destinationPath = getPathForLocation(loc.x, loc.y);
14 DefaultMutableTreeNode newParent =
15 (DefaultMutableTreeNode)
destinationPath.getLastPathComponent();
16
17 DefaultMutableTreeNode newChild = null;
18 if (dtde.getDropAction() == DnDConstants.ACTION_COPY) {
//make a new child
19 Object data = tranferable.getTransferData( <DATA FLAVOR> );
20 DefaultMutableTreeNode newChild = new
DefaultMutableTreeNode(data.clone());
21 }
22 else { //move
23 newChild = getSelectedNode();
24 oldParent.remove(newChild);
25 }
26
27 newParent.add(child);
28 }
Here's an explanation of the steps taken in the above code:
Transferable object.
<DATAFLAVOR> to indicate the flavor. That constant is of DataFlavor type and can be user-defined or one of the flavors found within the DataFlavor class. This is just sample code, so you don't know what flavor it is.
newChild. If it's a copy, then clone the data and make newChild a new DefaultMutableTreeNode with the cloned data as the user object. Otherwise, set newChild to be the old child and remove it from the old parent.
Note that the code above is shortened for the sake of clarity and neglects error-handling code. If you want to cut and paste, refer to DnDJTree.java in the source code zip in Resources for a more production-worthy example.
The Transferable interface is where the supported DataFlavors are defined and is used to obtain the transferable data. The data flavors are simply the different formats of data that can
be transferred. They are most useful when transferring data, via drag and drop, out of or into the JVM environment. Transfers
outside of the JVM use MIME formats, which are an Internet standard system for identifying types of encapsulated data.
Here are some examples of data flavors taken from java.awt.datatransfer.DataFlavor:
javaFileListFlavor: To transfer a list of files to/from Java (and the underlying platform), you use a DataFlavor of this type/subtype and representation class of java.util.List.
javaJVMLocalObjectMimeType: This flavor is used to transfer an arbitrary Java object reference with no associated MIME type within the same JVM.
javaRemoteObjectMimeType: To pass a live link to a remote object via a drag-and-drop ACTION_LINK operation, you should use a Mime Content Type of application/x-java-remote-object, in which the representation class of the
DataFlavor represents the type of remote interface to be transferred.
javaSerializedObjectMimeType: A MIME Content Type of application/x-java-serialized-object represents a graph of Java object(s) that have been made persistent.
plainTextFlavor: The DataFlavor representing plain text with unicode encoding, where: representationClass = InputStream
mimeType = "text/plain; charset=unicode"
stringFlavor: The DataFlavor representing a Java Unicode String class, where: representationClass = java.lang.String
mimeType = "application/x-java-serialized-object"
The Transferable interface is implemented by any object hat can be transferred using drag and drop. However, if you are only transferring
text, you can use the Java-predefined DataFlavor, java.awt.datatransfer.StringSelection. Otherwise, your new Transferable object must contain implementations of several methods, similar to the code fragments below:
1 public Object getTransferData(DataFlavor df)
2 throws UnsupportedFlavorException, IOException {
3 if (df.equals( <SUPPORTED FLAVOR> ) == false)
4 throw new UnsupportedFlavorException(df)
5 return this;
6 }
Line 3 compares the DataFlavor parameter with the flavor that class supports. The class only supports one flavor, so it makes one comparison. However, it
could support several flavors. If that were the case, it would cycle through checking for equality. Upon finding the right
flavor, it would return the corresponding data. In line 5, you return the data associated with the DataFlavor parameter. In this example, you simply return this, but that assumes the class is the data. The data could have been anything
you wanted (as described by the DataFlavor).
The two other methods to implement are getTransferDataFlavors and isDataFlavorSupported. The purpose of those two methods is to return an array of supported flavors and return whether or not a specified flavor
is supported.
//Returns an array of DataFlavor objects indicating the
//flavors the data can be provided in.
DataFlavor[] getTransferDataFlavors() {
return Flavors;
}
//Returns whether or not the specified data flavor is supported for
this object.
boolean isDataFlavorSupported(DataFlavor flavor) {
for (int i=0; i < Flavors.length; i++)
if ( Flavor[i].equals(flavor) ) return true;
return false;
}
Using DataFlavor objects can be tricky. So here is an example of what Flavors and <SUPPORTED FLAVOR> might look like:
//<SUPPORTED FLAVOR> example: Assumes that the transferable data in of type Integer
final public static DataFlavor INTEGER_FLAVOR = new
DataFlavor(Integer.class, "Integer Type");
//An array of supported Flavors.
static DataFlavor Flavors[] = {INTEGER_FLAVOR, <OTHER SUPPORTED FLAVORS> };
For each supported flavor in getTransferData, you should have a statement to return the flavor if it is requested.
Now that you understand the basic elements, let's study how they work together. First, I'll take a closer look at drop-location-dependent
cursor changes. For my example to be consistent, a male node may never be a valid drop-location, and hence you ought to change
the cursor appropriately. Unfortunately in JTrees, that is not so simple. It is possible to change the cursor, using the DragSourceEvent received in the DragSourceListener interface methods, but the DragSourceContext also implements that interface and automatically updates the cursor. (I checked the DragSourceContext source code, and it does check to determine whether the cursor has been manually changed. However, that change was not evident
in the cursor after running a test program). To fix the problem, you override the updateCurrentCursor method from DragSourceContext when you create the DragSource object. Lines 8 through 9 of the following code illustrate the patch.
1 dragSource = new DragSource() {
2 protected DragSourceContext createDragSourceContext(
3 DragSourceContextPeer dscp, DragGestureEvent dgl, Cursor
dragCursor,
4 Image dragImage, Point imageOffset, Transferable t,
5 DragSourceListener dsl) {
6 return new DragSourceContext(dscp, dgl, dragCursor,
dragImage,
7 imageOffset,
t, dsl) {
8 protected void updateCurrentCursor(int dropOp,
9 int targetAct, int status) {}
10 };
11 }
12 };
You must overcome a second obstacle to make drop-location-dependent cursor changes; you need both the DragSourceListener and DropTargetListener events to change the cursor, based on location. You get the DragSourceContext object from the DragSourceListener dragOver event, which is the object used to set the cursor type, and you get the location of the cursor from the DropTargetListener dragOver event, which is used to determine the appropriate cursor type. My solution isn't elegant, but it works. I set a class global
variable to the point obtained from DropTargetListener dragOver and then use that point to determine and set the cursor type in the DragSourceListener dragOver and dragEnter methods.
To carry out your drag-and-drop operations, you will build three classes called PersonalInfo, PersonNode, and DnDJTree. PersonalInfo will contain the Transferable data and PersonNode will subclass DefaultMutableTreeNode and implement the gender-specific GUI characteristics. Lastly, DnDJTree will subclass JTree and implement the drag-and-drop capabilities. You can implement a drag-and-drop JTree with fewer classes,
but using a three-class scheme leaves less gray area between the GUI and data layers. The GUI layer consists of the PersonNode and DnDJTree classes. They concern only visual representation and contain no data about the content of each node. The data layer consists
of just the PersonalInfo class, which is the Transferable object, and is the only data that gets transferred (see code>DnDJTree.dragGestureRecognized). The PersonNode class could subclass DefaultMutableTreeNode and include the PersonalInfo attributes. However, my scheme makes it easy to use a GUI other than JTree to represent the data, and since PersonalInfo is the Transferable class, it will be easy to implement drag and drop in an alternate GUI.
Lastly, if you really don't care about the inner workings of drag-and-drop JTrees, then it doesn't really matter if you understand it all. If that is the case, you will quite easily be able to copy and paste from the bundled code.
This tip proves drag and drop is possible in JTrees. I hope you find this explanation useful.
Free Download - 5 Minute Product Review. When slow equals Off: Manage the complexity of Web applications - Symphoniq
![]()
Free Download - 5 Minute Product Review. Realize the benefits of real user monitoring in less than an hour. - Symphoniq