Hi,
I just implemented a D&D JTree, and want to share with the fellow Javaers.
Use it exactly the same way you use a JTree. No gimmicks of Transferable, TransferHandler ...
This version only supports MOVE within the tree.
To make a node not accepting drop, simply let its getAllowsChildren() return false.
To customize TreeCellRenderer, extends the DnDTree.DnDTreeCellRenderer instead of the DefaultTreeCellRenderer.
Jimmy
import javax.swing.JLabel;
import javax.swing.JTree;
import javax.swing.border.Border;
import javax.swing.BorderFactory;
import javax.swing.border.LineBorder;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import java.awt.Color;
import java.awt.Component;
import java.awt.Point;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.*;
/** A simple easy-to-use Drag&Drop JTree with mouse-over and drop-under feedback.
* <p>
* Use it exactly the same way you use a JTree. No gimmicks of Transferable,
* TransferHandler ...
*
* This version only supports MOVE within the tree.
*
* To make a node not accepting drop, simply let its getAllowsChildren() return false.
* To customize TreeCellRenderer, extends the DnDTree.DnDTreeCellRenderer instead of
* the DefaultTreeCellRenderer.
*
* Have to use many back doors. So make no sense to support a DataFlavor.
* It is more useful to provide visual feedback during D&D within a JTree,
* than to accept object from other sources.
*
* @version 0.01 03/20/2005
* @author Jing Ding
*/
public class DnDTree extends JTree{
private DropTarget dropTarget; // The Drop position.
private DragSource dragSource; // The Drag node.
private DefaultTreeModel treemodel; // The TreeModel for the tree.
private DragSourceContext dsc; // Back door for cursor change.
private int highlightRow = -1; // Back door for cellrenderer.
private DefaultMutableTreeNode dragnode; // Back door for transfer node.
// dummy data to start drag.
private static Transferable dummy = new StringSelection("");
/* Initiate drag events */
private DragGestureListener dgListener = new DragGestureListener(){
public void dragGestureRecognized(DragGestureEvent event){
TreePath selectedPath = getSelectionPath();
if(selectedPath != null){
dragnode = (DefaultMutableTreeNode) selectedPath.getLastPathComponent();
dragSource.startDrag(event, DragSource.DefaultMoveDrop, dummy, dsListener);
}
}
};
/* Handle drag events, especially cursor change. */
private DragSourceListener dsListener = new DragSourceAdapter(){
/* Leave a back door for cursor change. During a D&D process, only the
* DragSourceContext (dsc) can change cursor. However, the DropTargetListener,
* which is responsible to determine if drop is accepted, doesn't know the dsc.
* For D&D implementations involving many different components, the back door
* should be accessible to all drop targets.
* (non-Javadoc)
* @see java.awt.dnd.DragSourceListener#dragEnter(java.awt.dnd.DragSourceDragEvent)
*/
public void dragEnter(DragSourceDragEvent event){
dsc = event.getDragSourceContext();
}
/*
* Move tree source node through a back door to the target node.
* So D&D's cleanup is not necessary.
*/
// public void dragDropEnd(DragSourceDropEvent event){
// if(event.getDropSuccess()){
// int act = event.getDropAction();
// if(act == DnDConstants.ACTION_MOVE){
// System.err.println("Remove " + dragnode + " from parent");
// treemodel.removeNodeFromParent(dragnode);
// }
// }
// }
};
/* Handle drop events */
private DropTargetListener dtListener = new DropTargetAdapter(){
/* Check if the node accepts drop.
* (non-Javadoc)
* @see java.awt.dnd.DropTargetListener#dragOver(java.awt.dnd.DropTargetDragEvent)
*/
public void dragOver(DropTargetDragEvent event){
DefaultMutableTreeNode n = mouseOnNode(event);
if(n != null && n.getAllowsChildren()){
dsc.setCursor(DragSource.DefaultMoveDrop);
event.acceptDrag(event.getDropAction());
highlight(event);
}else{
dsc.setCursor(DragSource.DefaultMoveNoDrop);
event.rejectDrag();
highlightRow = -1;
}
repaint(); // evoke TreeCellRenderer to draw a red border.
}
public void drop(DropTargetDropEvent event){
DefaultMutableTreeNode parent = mouseOnNode(event);
System.err.println(dragnode + " dropped on " + parent);
if(parent != null){
try{
highlightRow = -1;
/* Get around D&D's transferable mechnism to re-use the dragnode.
* The transferable mechnism creates a clone of the original node.
*/
treemodel.removeNodeFromParent(dragnode);
treemodel.insertNodeInto(dragnode, parent, parent.getChildCount());
event.dropComplete(true);
}catch(Exception e){ e.printStackTrace(); }
}else
event.rejectDrop();
}
};
/** Returns a new instance of the DNDTree for the specified TreeModel.*/
public DnDTree(DefaultTreeModel model){
super(model);
treemodel = model;
dropTarget = new DropTarget(this, dtListener);
dragSource = DragSource.getDefaultDragSource();
dragSource.createDefaultDragGestureRecognizer(this,
DnDConstants.ACTION_MOVE, dgListener);
setCellRenderer(new DnDTreeCellRenderer());
}
/* (non-Javadoc)
* @see javax.swing.JTree#setModel(javax.swing.tree.TreeModel)
*/
public void setModel(DefaultTreeModel model){
super.setModel(treemodel = model);
}
/*
* Leave a back door for TreeCellRenderer to highlight the node.
*/
public int getHighlightedRow(){ return highlightRow; }
/**
* @param event
*/
private void highlight(DropTargetDragEvent event) {
Point loc = event.getLocation();
highlightRow = getRowForLocation(loc.x, loc.y);
}
private DefaultMutableTreeNode mouseOnNode(DropTargetDropEvent event){
Point loc = event.getLocation();
TreePath path = getPathForLocation(loc.x, loc.y);
if(path != null)
return (DefaultMutableTreeNode)path.getLastPathComponent();
else
return null;
}
private DefaultMutableTreeNode mouseOnNode(DropTargetDragEvent event){
Point loc = event.getLocation();
TreePath path = getPathForLocation(loc.x, loc.y);
if(path != null)
return (DefaultMutableTreeNode)path.getLastPathComponent();
else
return null;
}
public static class DnDTreeCellRenderer extends DefaultTreeCellRenderer{
private Border highlightFrame = new LineBorder(Color.RED);
private Border noFrame = BorderFactory.createEmptyBorder();
public Component getTreeCellRendererComponent(JTree tree, Object value,
boolean sel, boolean expanded, boolean leaf, int row, boolean focus) {
Component com = super.getTreeCellRendererComponent(tree, value, sel, expanded,
leaf, row, focus);
int highlightRow = ((DnDTree)tree).getHighlightedRow();
if(row == highlightRow)
((JLabel)com).setBorder(highlightFrame);
else
((JLabel)com).setBorder(noFrame);
return com;
}
}
}