Skip to Main Content

Java SE (Java Platform, Standard Edition)

Announcement

For appeals, questions and feedback about Oracle Forums, please email oracle-forums-moderators_us@oracle.com. Technical questions should be asked in the appropriate category. Thank you!

A simple easy-to-use D&D JTree with mouse-over and drop-under feedback

843804Mar 20 2005 — edited Apr 8 2009
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;
    }
	}
}
Comments
Locked Post
New comments cannot be posted to this locked post.
Post Details
Locked on May 6 2009
Added on Mar 20 2005
5 comments
496 views