I've implemented drag and drop functionality in my JTree so I can reorder the nodes. I also have two methods for adding nodes; one is with a button, and another is a context menu that is triggered by the node. Button method works fine, but once I add a node with the context menu, drag and drop starts malfunctioning (nodes start dissapearing, arrayindexoutofbounds exceptions..). I don't understand why its doing this because both codes are pretty much the same.
DnDProblem.java (main)
package dndproblem; import java.awt.BorderLayout; import java.awt.EventQueue; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; public class DnDProblem extends JFrame{ private DnDJTree tree; private JButton addButton; public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { @Override public void run() { DnDProblem dndprb = new DnDProblem(); } }); } public DnDProblem() { setSize(300, 400); setLocationRelativeTo(null); setDefaultCloseOperation(EXIT_ON_CLOSE); setLayout(new BorderLayout()); tree = new DnDJTree(); add(tree, BorderLayout.CENTER); addButton = new JButton("Add"); addButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { ((DefaultTreeModel)tree.getModel()).insertNodeInto(new DefaultMutableTreeNode(new NodeData("New Node")), (DefaultMutableTreeNode)tree.getModel().getRoot(), 0); } }); add(addButton, BorderLayout.SOUTH); setVisible(true); } }
DnDJTree.java
package dndproblem; import javax.swing.DropMode; import javax.swing.JTree; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; public class DnDJTree extends JTree{ public DnDJTree() { setDragEnabled(true); setDropMode(DropMode.ON_OR_INSERT); setTransferHandler(new DnDTransferHandler()); DefaultMutableTreeNode root = new DefaultMutableTreeNode("Root"); root.add(new DefaultMutableTreeNode(new NodeData("Child 1"))); root.add(new DefaultMutableTreeNode(new NodeData("Child 2"))); root.add(new DefaultMutableTreeNode(new NodeData("Child 3"))); root.add(new DefaultMutableTreeNode(new NodeData("Child 4"))); setModel(new DefaultTreeModel(root)); addMouseListener(new MouseMenuAdapter()); Common.tree = this; } }
DnDTransferHandler.java
package dndproblem; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import javax.swing.JComponent; import javax.swing.JTree; import javax.swing.TransferHandler; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; public class DnDTransferHandler extends TransferHandler{ public static final DataFlavor NODE_FLAVOR = new DataFlavor(PackedNode.class, "Node Flavor"); public DnDTransferHandler() { super(); } @Override public int getSourceActions(JComponent c) { return MOVE; } @Override public boolean canImport(TransferSupport support) { try { //get the nodes involved in the move DefaultTreeModel model = (DefaultTreeModel) ((JTree)support.getComponent()).getModel(); JTree.DropLocation dloc = (JTree.DropLocation) support.getDropLocation(); DefaultMutableTreeNode child = (DefaultMutableTreeNode) support.getTransferable().getTransferData(NODE_FLAVOR); DefaultMutableTreeNode parent = (DefaultMutableTreeNode) dloc.getPath().getLastPathComponent(); //I dont want to be able to drag the root node, and the root has no parent, so... if(child.getParent() == null) return false; //I dont want to be able to drag a node inside itself ... if(parent.isNodeAncestor(child)) return false; } catch (IOException e) { } catch (UnsupportedFlavorException e) { } return true; } @Override public Transferable createTransferable(JComponent c) { DefaultMutableTreeNode node = (DefaultMutableTreeNode)((JTree)c).getLastSelectedPathComponent(); return new PackedNode(node); } @Override public boolean importData(TransferSupport support) { if(canImport(support)) { try { //get the nodes involved in the move DefaultTreeModel model = (DefaultTreeModel) ((JTree)support.getComponent()).getModel(); JTree.DropLocation dloc = (JTree.DropLocation) support.getDropLocation(); DefaultMutableTreeNode child = (DefaultMutableTreeNode) support.getTransferable().getTransferData(NODE_FLAVOR); DefaultMutableTreeNode parent = (DefaultMutableTreeNode) dloc.getPath().getLastPathComponent(); //drop index int destIndex = dloc.getChildIndex(); if(destIndex == -1) destIndex = 0; //get a deep copy of the node being moved. Then I can delete the node referenced in //the Transferable safely ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(bos); out.writeObject(child); out.flush(); out.close(); ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray())); Object obj = in.readObject(); in.close(); //insert node model.insertNodeInto((DefaultMutableTreeNode)obj, parent, destIndex); } catch(IOException e) { return false; } catch(ClassNotFoundException e) { return false; } catch(UnsupportedFlavorException e) { return false; } return true; } else { return false; } } @Override public void exportDone(JComponent c, Transferable t, int action) { if(action == MOVE) { try { DefaultTreeModel model = (DefaultTreeModel)((JTree)c).getModel(); DefaultMutableTreeNode child = (DefaultMutableTreeNode) t.getTransferData(NODE_FLAVOR); model.removeNodeFromParent(child); } catch(UnsupportedFlavorException e) { } catch(IOException e) { } } } }
PackedNode.java
package dndproblem; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.io.IOException; import javax.swing.tree.DefaultMutableTreeNode; public class PackedNode implements Transferable{ private DefaultMutableTreeNode data; public PackedNode(DefaultMutableTreeNode node) { data = node; } @Override public DataFlavor[] getTransferDataFlavors() { return new DataFlavor[] {DnDTransferHandler.NODE_FLAVOR}; } @Override public boolean isDataFlavorSupported(DataFlavor flavor) { if(flavor == DnDTransferHandler.NODE_FLAVOR) { return true; } else { return false; } } @Override public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException { if(flavor == DnDTransferHandler.NODE_FLAVOR) { return data; } else { throw new UnsupportedFlavorException(flavor); } } }
NodeData.java
package dndproblem; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.Serializable; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPopupMenu; import javax.swing.JTextField; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; public class NodeData implements ActionListener, Serializable{ private String name; private JPopupMenu context; private JMenuItem newNode; public NodeData(String n) { name = n; //setup context menu for this data type context = new JPopupMenu(); newNode = new JMenuItem("New folder"); newNode.addActionListener(this); context.add(newNode); } public void showMenu(int x, int y) { context.show(Common.tree, x, y); } @Override public String toString() { return name; } @Override public void actionPerformed(ActionEvent e) { if(e.getSource() == newNode) { JTextField tf = new JTextField(); String n = JOptionPane.showInputDialog(tf, "Folder name:", "New Folder", JOptionPane.PLAIN_MESSAGE); ((DefaultTreeModel)Common.tree.getModel()).insertNodeInto(new DefaultMutableTreeNode(new NodeData(n)), (DefaultMutableTreeNode)Common.tree.getModel().getRoot(), 0); } } }
MouseMenuAdapter.java
package dndproblem; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import javax.swing.JTree; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.TreePath; public class MouseMenuAdapter extends MouseAdapter{ @Override public void mousePressed(MouseEvent event) { if(event.getButton() == 3) { int x = event.getX(); //get mouse cooridnates int y = event.getY(); JTree tree = (JTree) event.getSource(); //get the tree that was clicked TreePath path = tree.getPathForLocation(x, y); //get a path to the clicked node if(path == null) return; //if we missed, return tree.setSelectionPath(path); //set the selected node to the one we clicked DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent(); //get the node we clicked Object data = node.getUserObject(); //get the data object from node if(data instanceof NodeData) ((NodeData)data).showMenu(x, y); //show the context menu } } }
Common.java
package dndproblem; import javax.swing.JTree; public class Common { public static JTree tree; }