Jtree Drag&drop

Can anyone offer any insight on how to implement drag & drop in Jtree, so I can drag a node into a different location in the tree? (Java2)
tia for any help.
LVL 1
acsmithAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

JodCommented:
Try this example from the mining company...it is made up of a number of parts:

Draggable Tree
TransferableTreeNode
DroppableList
CustomCellRenderer

and a test program TreeTester.

Examples and explanation follows...




Draggable Tree

The DraggableTree implements the DragGestureListener interface and has a dragGestureRecognized(DragGestureEvent) method. This basically says when the start of the dragging has been recognized, find the selected node to drag and setup what you are dragging. I still haven't found a good reason for the DragSourceListener, so provide an empty stub. Unfortunately, you cannot just pass a null listener without getting a few exceptions thrown when dragging.

import javax.swing.*;
import javax.swing.tree.*;
import java.awt.*;
import java.awt.dnd.*;
import java.io.IOException;

public class DraggableTree extends JTree
    implements DragGestureListener {
  DragSource dragSource =
    DragSource.getDefaultDragSource();

  final static DragSourceListener dragSourceListener
    = new MyDragSourceListener();

  static class MyDragSourceListener
      implements DragSourceListener {
    public void dragDropEnd(
        DragSourceDropEvent DragSourceDropEvent) {
    }
    public void dragEnter(
        DragSourceDragEvent DragSourceDragEvent) {
    }
    public void dragExit(
        DragSourceEvent DragSourceEvent) {
    }
    public void dragOver(
        DragSourceDragEvent DragSourceDragEvent) {
    }
    public void dropActionChanged(
        DragSourceDragEvent DragSourceDragEvent) {
    }
  }

  public DraggableTree () {
    dragSource.createDefaultDragGestureRecognizer(
      this, DnDConstants.ACTION_COPY_OR_MOVE, this);
  }

  public DraggableTree (TreeModel model) {
    super (model);
    dragSource.createDefaultDragGestureRecognizer(
      this, DnDConstants.ACTION_COPY_OR_MOVE, this);
  }

  // DragGestureListener

  public void dragGestureRecognized(
      DragGestureEvent dragGestureEvent) {
    TreePath path = getSelectionPath();
    if (path == null) {
      // Nothing selected, nothing to drag
      System.out.println ("Nothing selected - beep");
      getToolkit().beep();
    } else {
      DefaultMutableTreeNode selection =
        (DefaultMutableTreeNode)
        path.getLastPathComponent();
      TransferableTreeNode node =
        new TransferableTreeNode(selection);
      dragSource.startDrag(
        dragGestureEvent,
        DragSource.DefaultCopyDrop,
        node,
        dragSourceListener);
    }
  }
}


TransferableTreeNode

The TransferableTreeNode is what is dragged around. Any DefaultMutableTreeNode can be dragged around, so you do not have to do anything special to create tree nodes just for draggable trees. Its primary job is to return the proper object from public Object getTransferData(DataFlavor flavor), based upon the requested flavor from the drop point. To support our custom flavor, we need to create a DataFlavor and be sure public DataFlavor[] getTransferDataFlavors() returns it, along with the other supported flavors.

Contrary to a popular rumor that is floating around, the draggable item (implementing the Transferable interface) does not have to support DataFlavor.stringFlavor. It can support only your custom flavor.

import javax.swing.tree.*;
import java.awt.dnd.*;
import java.awt.datatransfer.*;
import java.io.*;

public class TransferableTreeNode
    extends DefaultMutableTreeNode
    implements Transferable {
  final static int TREE = 0;
  final static int STRING = 1;
  final static int PLAIN_TEXT = 2;

  final public static DataFlavor
    DEFAULT_MUTABLE_TREENODE_FLAVOR =
      new DataFlavor(
        DefaultMutableTreeNode.class,
        "Default Mutable Tree Node");

  static DataFlavor flavors[] = {
    DEFAULT_MUTABLE_TREENODE_FLAVOR,
    DataFlavor.stringFlavor,
    DataFlavor.plainTextFlavor};
/* The following works fine
  static DataFlavor flavors[] = {
    DEFAULT_MUTABLE_TREENODE_FLAVOR
  };
*/

  private DefaultMutableTreeNode data;
                                       
  public TransferableTreeNode(
      DefaultMutableTreeNode data) {
    this.data = data;
  }

  public DataFlavor[] getTransferDataFlavors() {
   return flavors;
  }

  public Object getTransferData(DataFlavor flavor)
      throws UnsupportedFlavorException,
      IOException {
    Object returnObject;
    if (flavor.equals(flavors[TREE])) {
      Object userObject = data.getUserObject();
      if (userObject == null) {
        returnObject = data;
      } else {
        returnObject = userObject;
      }
    } else if (flavor.equals(flavors[STRING])) {
      Object userObject = data.getUserObject();
      if (userObject == null) {
        returnObject = data.toString();
      } else {
        returnObject = userObject.toString();
      }
    } else if (flavor.equals(flavors[PLAIN_TEXT])) {
      Object userObject = data.getUserObject();
      String string;
      if (userObject == null) {
        string = data.toString();
      } else {
        string = userObject.toString();
      }
      returnObject = new ByteArrayInputStream(
        string.getBytes());
    } else {
      throw new UnsupportedFlavorException(flavor);
    }
    return returnObject;
  }
  public boolean isDataFlavorSupported(
      DataFlavor flavor) {
    boolean returnValue = false;
    for (int i=0, n=flavors.length; i<n; i++) {
      if (flavor.equals(flavors[i])) {
        returnValue = true;
        break;
      }
    }
    return returnValue;
  }
}


DroppableList

The DroppableList has changed a little from the last example. Instead of managing the data model within a Vector, it now uses a DefaultListModel to manage the elements. In addition, the list now supports the dropping of our custom flavor as well as DataFlavor.javaFileListFlavor. When getting a file list, the object dropped is a java.util.List of File objects.

import java.awt.*;
import java.awt.dnd.*;
import java.awt.datatransfer.*;
import javax.swing.*;
import java.io.*;
import java.util.*;
import java.util.List;

public class DroppableList extends JList
    implements DropTargetListener {

  DropTarget dropTarget;

  public DroppableList() {
    dropTarget = new DropTarget (this, this);
    setModel(new DefaultListModel());
  }

  public void dragEnter (
      DropTargetDragEvent dropTargetDragEvent) {
    dropTargetDragEvent.acceptDrag (
      DnDConstants.ACTION_COPY_OR_MOVE);
  }

  public void dragExit (
      DropTargetEvent dropTargetEvent) {
  }

  public void dragOver (
      DropTargetDragEvent dropTargetDragEvent) {
  }

  public void dropActionChanged (
      DropTargetDragEvent dropTargetDragEvent) {
  }

  public synchronized void drop (
      DropTargetDropEvent dropTargetDropEvent) {
    try {
      Transferable tr =
        dropTargetDropEvent.getTransferable();
      if (tr.isDataFlavorSupported(
          TransferableTreeNode.
            DEFAULT_MUTABLE_TREENODE_FLAVOR)) {
        dropTargetDropEvent.acceptDrop (
          DnDConstants.ACTION_COPY_OR_MOVE);
        Object userObject = tr.getTransferData(
          TransferableTreeNode.
            DEFAULT_MUTABLE_TREENODE_FLAVOR);
        ((DefaultListModel)getModel()).
          addElement(userObject);
        dropTargetDropEvent.getDropTargetContext().
          dropComplete(true);
      } else if (tr.isDataFlavorSupported (
          DataFlavor.stringFlavor)) {
        dropTargetDropEvent.acceptDrop (
          DnDConstants.ACTION_COPY_OR_MOVE);
        String string = (String)tr.getTransferData (
          DataFlavor.stringFlavor);
        ((DefaultListModel)getModel()).addElement(
          string);
        dropTargetDropEvent.getDropTargetContext().
          dropComplete(true);
      } else if (tr.isDataFlavorSupported (
          DataFlavor.plainTextFlavor)) {
        dropTargetDropEvent.acceptDrop (
          DnDConstants.ACTION_COPY_OR_MOVE);
        Object stream = tr.getTransferData(
          DataFlavor.plainTextFlavor);
        if (stream instanceof InputStream) {
          InputStreamReader isr =
            new InputStreamReader((InputStream)stream);
          BufferedReader reader =
            new BufferedReader(isr);
          String line;
          while ((line = reader.readLine()) != null) {
            ((DefaultListModel)getModel()).
              addElement(line);
          }
          dropTargetDropEvent.getDropTargetContext().
            dropComplete(true);
        } else if (stream instanceof Reader) {
          BufferedReader reader =
            new BufferedReader((Reader)stream);
          String line;
          while ((line = reader.readLine()) != null) {
            ((DefaultListModel)getModel()).
              addElement(line);
          }
          dropTargetDropEvent.getDropTargetContext().
            dropComplete(true);
        } else {
          System.err.println ("Unknown type: " +
            stream.getClass());
          dropTargetDropEvent.rejectDrop();
        }
      } else if (tr.isDataFlavorSupported (
          DataFlavor.javaFileListFlavor)) {
        dropTargetDropEvent.acceptDrop (
          DnDConstants.ACTION_COPY_OR_MOVE);
        List fileList = (List)tr.getTransferData(
          DataFlavor.javaFileListFlavor);
        Iterator iterator = fileList.iterator();
        while (iterator.hasNext()) {
          File file = (File)iterator.next();
          Hashtable hashtable = new Hashtable();
          hashtable.put("name", file.getName());
          hashtable.put("url",
            file.toURL().toString());
          ((DefaultListModel)getModel()).
            addElement(hashtable);
        }
        dropTargetDropEvent.getDropTargetContext().
          dropComplete(true);
      } else {
        System.err.println ("Rejected");
        dropTargetDropEvent.rejectDrop();
      }
    } catch (IOException io) {
      io.printStackTrace();
      dropTargetDropEvent.rejectDrop();
    } catch (UnsupportedFlavorException ufe) {
      ufe.printStackTrace();
      dropTargetDropEvent.rejectDrop();
    }
  }
}


CustomCellRenderer

The CustomCellRenderer is what displays the tree and list cells. It handles the display of Hashtable for the tree's user data within the JTree or JList.

import java.awt.Component;
import java.util.Hashtable;
import javax.swing.*;
import javax.swing.tree.*;

public class CustomCellRenderer
    implements ListCellRenderer, TreeCellRenderer {
  DefaultListCellRenderer listCellRenderer =
    new DefaultListCellRenderer();
  DefaultTreeCellRenderer treeCellRenderer =
    new DefaultTreeCellRenderer();
  public Component getListCellRendererComponent(
      JList list, Object value, int index,
      boolean selected, boolean hasFocus) {
    listCellRenderer.getListCellRendererComponent(
      list, value, index, selected, hasFocus);
    listCellRenderer.setText(getValueString(value));
    return listCellRenderer;
  }
  public Component getTreeCellRendererComponent(
    JTree tree, Object value, boolean selected,
    boolean expanded, boolean leaf, int row,
    boolean hasFocus) {
    treeCellRenderer.getTreeCellRendererComponent(
      tree, value, selected, expanded, leaf, row,
      hasFocus);
    if (value instanceof DefaultMutableTreeNode) {
      DefaultMutableTreeNode node =
        (DefaultMutableTreeNode)value;
      value = node.getUserObject();
    }
    treeCellRenderer.setText(getValueString(value));
    return treeCellRenderer;
  }
  private String getValueString(Object value) {
    String returnString = "null";
    if (value != null) {
      if (value instanceof Hashtable) {
        Hashtable h = (Hashtable)value;
        String name = (String)h.get("name");
        String url = (String)h.get("url");
        returnString = name + " ==> " + url;
      } else {
        returnString = "X: " + value.toString();
      }
    }
    return returnString;
  }
}


TreeTester

The TreeTester testing program is a little different this time. The tree nodes need to be created and a renderer applied to the JTree and JList. Also, this time the draggable and droppable components are placed within a JSplitPane.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.tree.*;
import java.util.Hashtable;

public class TreeTester {
  public static void main (String args[]) {
    JFrame f = new JFrame("Tree Dragging Tester");
    CustomCellRenderer renderer =
      new CustomCellRenderer();
    DraggableTree tree = new DraggableTree();
    tree.setModel(getDefaultTreeModel());
    tree.setCellRenderer(renderer);
    JScrollPane leftPane = new JScrollPane(tree);
    DroppableList list = new DroppableList();
    list.setCellRenderer(renderer);
    JScrollPane rightPane = new JScrollPane(list);
    JSplitPane splitPane = new JSplitPane(
      JSplitPane.HORIZONTAL_SPLIT, leftPane,
      rightPane);
    f.getContentPane().add (splitPane,
      BorderLayout.CENTER);
    f.setSize (400, 300);
    f.addWindowListener (new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
        System.exit(0);
      }
    });
    f.setVisible (true);
  }
  private static TreeModel getDefaultTreeModel() {
    DefaultMutableTreeNode root =
      new DefaultMutableTreeNode("Drag Me");
    DefaultMutableTreeNode parent;

    parent = new DefaultMutableTreeNode("Auctions");
    root.add(parent);
    parent.add(new DefaultMutableTreeNode(
      makeNode("eBay", "http://www.ebay.com")));
    parent.add(new DefaultMutableTreeNode(
      makeNode("EggHead", "http://www.egghead.com")));
    parent.add(new DefaultMutableTreeNode(
      makeNode("First Auction",
        "http://www.firstauction.com")));
    parent.add(new DefaultMutableTreeNode(
      makeNode("uBid", "http://www.ubid.com")));

    parent = new
      DefaultMutableTreeNode("Search Engines");
    root.add(parent);
    parent.add(new DefaultMutableTreeNode(
      makeNode("HotBot",
        "http://www.hotbot.com")));
    parent.add(new DefaultMutableTreeNode(
      makeNode("Infoseek",
        "http://www.infoseek.com")));
    parent.add(new DefaultMutableTreeNode(
      makeNode("Lycos",
        "http://www.lycos.com")));
    parent.add(new DefaultMutableTreeNode(
      makeNode("Yahoo",
        "http://www.yahoo.com")));

    parent = new DefaultMutableTreeNode("Java");
    root.add(parent);
    parent.add(new DefaultMutableTreeNode(
      makeNode("Focus on Java",
        "http://java.about.com")));
    parent.add(new DefaultMutableTreeNode(
      makeNode("JavaWorld",
        "http://www.javaworld.com")));
    parent.add(new DefaultMutableTreeNode(
      makeNode("Sun",
        "http://java.sun.com")));

    return new DefaultTreeModel(root);
  }
  private static Hashtable makeNode(String name,
      String url) {
    Hashtable hashtable = new Hashtable();
    hashtable.put("name", name);
    hashtable.put("url", url);
    return hashtable;
  }
}
0
acsmithAuthor Commented:
Thanks for this Jod. It's certainly a good starting point and has helped me understand a bit more about what's going on. I'll reject for the moment though, (although some points are coming your way!) because although I can now drag a node from a tree to another component, I need to be able to drag a node to a different place in the same tree. I have made the tree a drop target, and in the drop method, I have the node I'm dragging, but I can't work out how to add it to the node i'm dropping it onto (that is, I can't seem to get a reference to the node i'm dropping onto). Could you offer any more help?
TIA.
0
Cloud Class® Course: MCSA MCSE Windows Server 2012

This course teaches how to install and configure Windows Server 2012 R2.  It is the first step on your path to becoming a Microsoft Certified Solutions Expert (MCSE).

shaveriCommented:
0
acsmithAuthor Commented:
thanks shaveri, but i'm afraid this doesn't tell me anything that jod's original answer didn't.
Jod: please repost an answer to collect your points.
0
JodCommented:
I'm gonna have to build and develop this to get it to do exactly what you want, but I'm a bit short of time at the mo...I'll be back soon...
0
JodCommented:
Bad news and Good news acsmith...first the bad news...

Firstly I have not had time to try and get this working. I also get the feeling it will not be as easy as it should be.


Secondly, there is a DnD bug in JTree, so check this out first before continuing:

http://developer.java.sun.com/developer/bugParade/bugs/4165577.html


Finally, though, if you need to know more about how to do it, the answer is in here - just needs to be implemented in the above example:

http://java.sun.com/products/jfc/tsc/articles/jtree/index.html
0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
acsmithAuthor Commented:
Thanks Jod. I've managed to glean enough from your original answer to get me up and running. Now i've got my head round it, it's relatively easy.
cheers!
0
JodCommented:
As with all things Swing it's just a case of finding out which listeners to attach to what to generate the appropriate events.

The info could be more accessible though with some clearer examples.
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Java

From novice to tech pro — start learning today.