I have been using JTree to get the multiple root directories of the system, using the example FileTreeModel.java to achieve it. And also trying to get the selected leaf (path) but it throws cast error, please give me some directions on this, have posted the code so far have tried. Thanks.
import java.io.File;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.tree.*;
import java.io.File;
public class FileTreeDemo {
public static void main(String[] args) {
// Figure out where in the filesystem to start displaying
File root;
if (args.length > 0) {
root = new File(args[0]);
//System.out.println(args.length);
}
else root = new File(System.getProperty("user.home"));
//System.out.println(args.length);
// Create a TreeModel object to represent our tree of files
FileTreeModel model = new FileTreeModel(root);
// Create a JTree and tell it to display our model
JTree tree = new JTree();
tree.setModel(model);
tree.setRootVisible(true);
tree.setShowsRootHandles(true);
//tree.setShowsRootHandles(true);
// The JTree can get big, so allow it to scroll.
JScrollPane scrollpane = new JScrollPane(tree);
// Display it all in a window and make the window appear
JFrame frame = new JFrame("FileTreeDemo");
frame.getContentPane().add(scrollpane, "Center");
frame.setSize(400,600);
frame.setVisible(true);
// Add a listener
tree.addTreeSelectionListener(new TreeSelectionListener() {
public void valueChanged(TreeSelectionEvent e) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) e
.getPath().getLastPathComponent();
System.out.println("You selected " + node);
}
});
}
}
class FileTreeModel implements TreeModel {
protected File root;
public FileTreeModel(File root) { this.root = root; }
public Object getRoot() { return root; }
public boolean isLeaf(Object node) { return ((File)node).isFile(); }
public int getChildCount(Object parent) {
String[] children = ((File)parent).list();
if (children == null) return 0;
return children.length;
}
public Object getChild(Object parent, int index) {
String[] children = ((File)parent).list();
if ((children == null) || (index >= children.length)) return null;
return new File((File) parent, children[index]);
}
public int getIndexOfChild(Object parent, Object child) {
String[] children = ((File)parent).list();
if (children == null) return -1;
String childname = ((File)child).getName();
for(int i = 0; i < children.length; i++) {
if (childname.equals(children[i])) return i;
}
return -1;
}
public void valueForPathChanged(TreePath path, Object newvalue) {}
public void addTreeModelListener(TreeModelListener l) {}
public void removeTreeModelListener(TreeModelListener l) {}
}
In your model you store File's instead of DefaultMutableTreeNode, because of you get ClassCastException here:
DefaultMutableTreeNode node = (DefaultMutableTreeNode)e.getPath().getLastPathComponent();
Change your listener like next:
tree.addTreeSelectionListener(new TreeSelectionListener() {
public void valueChanged(TreeSelectionEvent e) {
File node = (File) e.getPath().getLastPathComponent();
System.out.println("You selected " + node);
}
});
Related
I am beginner in Swings. Trying to build a small application on JTree. Stuck with this issue.
I am able to load a selected folder using JFileChooser to the tree, but if I open one more folder, previous folder is replaced with the new folder. Is there any way we can open multiple folders in tree? As I keep opening folders from file chooser, it has to keep adding to existing tree. Please suggest how to achieve this?
Here is the working example I tried (picked from other posts):
import javax.swing.*;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.io.File;
public class TreeFrame {
public static void main(String[] args) {
JFrame frame = createFrame();
JPanel browsePanel = new JPanel();
JButton open = new JButton();
open.setPreferredSize(new Dimension(70, 25));
open.setText("Open");
final JFileChooser fileChooser = new JFileChooser();
fileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
JTree tree = new JTree();
JScrollPane scrollPane = new JScrollPane(tree);
Action action = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent ae) {
int result = fileChooser.showOpenDialog(frame);
if (result == JFileChooser.APPROVE_OPTION) {
File file = fileChooser.getSelectedFile();
TreeModel model = new FileTreeModel(file);
tree.setModel(model);
scrollPane.add(tree);
}
}
};
open.addActionListener(action);
browsePanel.add(open);
frame.add(browsePanel, BorderLayout.WEST);
frame.add(scrollPane, BorderLayout.EAST);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private static JFrame createFrame() {
JFrame frame = new JFrame("JTree Expand/Collapse example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(new Dimension(500, 400));
frame.setLayout(new GridLayout(1, 2));
return frame;
}
static class FileTreeModel implements TreeModel {
protected File root;
public FileTreeModel(File root) { this.root = root; }
public Object getRoot() { return root; }
public boolean isLeaf(Object node) { return ((File)node).isFile(); }
public int getChildCount(Object parent) {
String[] children = ((File)parent).list();
if (children == null) return 0;
return children.length;
}
public Object getChild(Object parent, int index) {
String[] children = ((File)parent).list();
if ((children == null) || (index >= children.length)) return null;
return new File((File) parent, children[index]);
}
public int getIndexOfChild(Object parent, Object child) {
String[] children = ((File)parent).list();
if (children == null) return -1;
String childname = ((File)child).getName();
for(int i = 0; i < children.length; i++) {
if (childname.equals(children[i])) return i;
}
return -1;
}
public void valueForPathChanged(TreePath path, Object newvalue) {}
public void addTreeModelListener(TreeModelListener l) {}
public void removeTreeModelListener(TreeModelListener l) {}
}
}
Thanks for the suggestion, found solution:
DefaultTreeModel model = (DefaultTreeModel)tree.getModel();
DefaultMutableTreeNode root = (DefaultMutableTreeNode)model.getRoot();
root.add(addNodes(null, file));
model.reload(root);
Here is the run down on my program
Lists all folders, subfolders, and files of X extension from a specified directory. You can view, edit, copy, etc the contents of the files. Each file has a .dat file associated with it that contains information such as keywords and last use date.
Right now I'm creating a filter. The way it should filter is take the inputted text and return any folders and files with the filter in its name, files which keywords contain and files which contents contain.
Easy enough, right?
My only problem is that it will do one of the following (based on location of File)
if folder matches
-> only lists to that folder, but not contents
if file matches
-> only lists file if its in JTree root
if file matches but is in a folder which doesnt match
-> doesnt display
I apologize if that is hard to understand. Below is functional code, copied and cleaned up from my real program. Uses apache commons 2.4
import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.io.File;
import javax.swing.AbstractAction;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JTree;
import javax.swing.KeyStroke;
import javax.swing.UIManager;
import javax.swing.border.LineBorder;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeSelectionModel;
import org.apache.commons.io.FileUtils;
public class NGui {
JFrame frame;
private JTree tree;
private JTextField jtf;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager
.getSystemLookAndFeelClassName());
} catch (Exception ex) {
}
new NGui();
}
});
}
public NGui() {
initialize();
}
private void initialize() {
frame = new JFrame();
frame.getContentPane().setLayout(null);
frame.setSize(250, 400);
JScrollPane scrollPane = new JScrollPane();
scrollPane.setBounds(10, 50, 200, 300);
frame.getContentPane().add(scrollPane);
tree = new JTree(addNodes(new File(getWorkPath())));
tree.setRootVisible(true);
tree.setShowsRootHandles(true);
tree.setBorder(new LineBorder(new Color(0, 0, 0)));
tree.getSelectionModel().setSelectionMode(
TreeSelectionModel.SINGLE_TREE_SELECTION);
scrollPane.setViewportView(tree);
tree.getSelectionModel().addTreeSelectionListener(
new TreeSelectionListener() {
#Override
public void valueChanged(TreeSelectionEvent e) {
treeValueChanged(e);
}
});
tree.setCellRenderer(new FileTreeCellRenderer());
jtf = new JTextField();
jtf.setBorder(new LineBorder(new Color(0, 0, 0)));
jtf.setBounds(10, 10, 150, 25);
Object actionKey = jtf.getInputMap(JComponent.WHEN_FOCUSED).get(
KeyStroke.getKeyStroke("ENTER"));
jtf.getActionMap().put(actionKey, new AbstractAction() {
private static final long serialVersionUID = 1L;
#Override
public void actionPerformed(ActionEvent ae) {
try {
filterResults(ae);
} catch (Exception e) {
e.printStackTrace();
}
}
});
frame.getContentPane().add(jtf);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private void treeValueChanged(TreeSelectionEvent e) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree
.getLastSelectedPathComponent();
log("Selected Node: " + node);
}
private void log(Object o) {
System.out.println(o);
}
private String getWorkPath() {
return ".";
}
public void filterResults(ActionEvent ae) throws Exception {
final String str = jtf.getText();
reloadTree("."); // refreshes from any previous searches
DefaultMutableTreeNode root = (DefaultMutableTreeNode) tree.getModel()
.getRoot();
reloadTree(root.toString(), str);
}
public DefaultMutableTreeNode addNodes(File dir, String filter)
throws Exception {
DefaultMutableTreeNode node = new DefaultMutableTreeNode(dir);
for (File file : dir.listFiles()) {
if (file.isDirectory()) {
if (file.getPath().contains(filter)) {
log("adding: " + file.toString());
node.add(addNodes(file));
}
} else {
// apache commons 2.4
String tmpContent = FileUtils.readFileToString(file);
if (file.getPath().contains(filter)
|| tmpContent.contains(filter)) {
log("adding: " + file.toString());
node.add(new DefaultMutableTreeNode(file));
}
}
}
return node;
}
public DefaultMutableTreeNode addNodes(File dir) {
DefaultMutableTreeNode node = new DefaultMutableTreeNode(dir);
for (File file : dir.listFiles()) {
if (file.isDirectory()) {
node.add(addNodes(file));
} else {
node.add(new DefaultMutableTreeNode(file));
}
}
return node;
}
public void reloadTree(final String path) {
DefaultMutableTreeNode dn = addNodes(new File(path));
tree.setModel(new DefaultTreeModel(dn));
}
public void reloadTree(final String path, final String filter)
throws Exception {
DefaultMutableTreeNode dn = addNodes(new File(path), filter);
tree.setModel(new DefaultTreeModel(dn));
}
public class FileTreeCellRenderer extends DefaultTreeCellRenderer {
private static final long serialVersionUID = 1L;
#Override
public Component getTreeCellRendererComponent(JTree tree, Object value,
boolean sel, boolean expanded, boolean leaf, int row,
boolean hasFocus) {
if (value instanceof DefaultMutableTreeNode) {
value = ((DefaultMutableTreeNode) value).getUserObject();
if (value instanceof File) {
value = ((File) value).getName();
}
}
return super.getTreeCellRendererComponent(tree, value, sel,
expanded, leaf, row, hasFocus);
}
}
}
The problem lies in the filtering algorithm.
You missed out to call the filter on the subcomponents again (instead of addNodes(file) you have to call addNodes(file, filter)).
Directory nodes where only added if they match, not if subcomponents match.
A quick hack to make your code working looks like this (replace the addNodes method):
public DefaultMutableTreeNode addNodes(File dir, String filter)
throws Exception {
DefaultMutableTreeNode node = new DefaultMutableTreeNode(dir);
//true iff any subcomponent matches
boolean matchFound = false;
for (File file : dir.listFiles()) {
if (file.isDirectory()) {
if (file.getName().contains(filter)) {
matchFound = true;
}
//always call addNodes on directories, because subcomponents may match
//null return value indicates no match (with Java 8 use Optional for this)
DefaultMutableTreeNode maybeNode = addNodes(file, filter);
if(maybeNode != null) {
matchFound = true;
node.add(maybeNode);
}
} else {
// apache commons 2.4
String tmpContent = FileUtils.readFileToString(file);
if (file.getName().contains(filter)
|| tmpContent.contains(filter)) {
matchFound = true;
log("adding file: " + file.toString());
node.add(new DefaultMutableTreeNode(file));
}
}
}
//only return node if node itself or subcomponent matches
if(matchFound || dir.getName().contains(filter)) {
return node;
}
//return null otherwise
return null;
}
I am comfortable with Java but to swing API. After googling for sometime, I have got below program working fine so far. Now what I want is, on double clicking on any file in the tree, below function to be executed.
Can someone suggest me how can I add a listener to understand double click event? Example would really help.
public boolean getSomeData(String fileName){
//I will make JDBC call here
}
My working program is as below,
/*http://www.chka.de/swing/tree/FileTreeModel.html
*/
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import javax.swing.event.TreeModelListener;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.EventListenerList;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.io.File;
import java.io.Serializable;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
public class FileTreeModel
implements TreeModel, Serializable, Cloneable
{
protected EventListenerList listeners;
private static final Object LEAF = new Serializable() { };
private Map map;
private File root;
public FileTreeModel(File root)
{
this.root = root;
if (!root.isDirectory())
map.put(root, LEAF);
this.listeners = new EventListenerList();
this.map = new HashMap();
}
public Object getRoot()
{
return root;
}
public boolean isLeaf(Object node)
{
return map.get(node) == LEAF;
}
public int getChildCount(Object node)
{
List children = children(node);
if (children == null)
return 0;
return children.size();
}
public Object getChild(Object parent, int index)
{
return children(parent).get(index);
}
public int getIndexOfChild(Object parent, Object child)
{
return children(parent).indexOf(child);
}
protected List children(Object node)
{
File f = (File)node;
Object value = map.get(f);
if (value == LEAF)
return null;
List children = (List)value;
if (children == null)
{
File[] c = f.listFiles();
if (c != null)
{
children = new ArrayList(c.length);
for (int len = c.length, i = 0; i < len; i++)
{
children.add(c[i]);
if (!c[i].isDirectory())
map.put(c[i], LEAF);
}
}
else
children = new ArrayList(0);
map.put(f, children);
}
return children;
}
public void valueForPathChanged(TreePath path, Object value)
{
}
public void addTreeModelListener(TreeModelListener l)
{
listeners.add(TreeModelListener.class, l);
}
public void removeTreeModelListener(TreeModelListener l)
{
listeners.remove(TreeModelListener.class, l);
}
public Object clone()
{
try
{
FileTreeModel clone = (FileTreeModel)super.clone();
clone.listeners = new EventListenerList();
clone.map = new HashMap(map);
return clone;
}
catch (CloneNotSupportedException e)
{
throw new InternalError();
}
}
public static void main(String[] args)
{
if (args.length != 1)
{
System.err.println("Usage: java FileTreeModel path");
System.exit(1);
}
File root = new File(args[0]);
if (!root.exists())
{
System.err.println(root+ ": No such file or directory");
System.exit(2);
}
JTree tree = new JTree(new FileTreeModel(root));
JFrame f = new JFrame(root.toString());
f.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
f.getContentPane().add(new JScrollPane(tree));
f.pack();
f.setVisible(true);
}
}
I think you should add MouseListener and implement the mousePressed method
MouseListener ml = new MouseAdapter() {
public void mousePressed(MouseEvent e) {
if(e.getClickCount() == 2) {
getSomeData(...);
}
}
};
tree.addMouseListener(ml);
i want to build a tree in java. operations such as insert delete update. how to go about it? I dont want a binary tree, i want to add children based on user input
i have implemented the following
import java.util.ArrayList;
public class Tree {
private Node root;
public Tree(String rootData)
{
root=new Node();
root.data=rootData;
root.children=new ArrayList<Node>();
}
public void addChild(String name)
{
}
}
import java.util.*;
class Node {
String data;
Node parent;
List<Node> children;
public Node()
{
data=null;
children=null;
parent=null;
}
public Node(String name)
{
Node n=new Node(name);
n.data=name;
n.children=new ArrayList<Node>();
}
}
Not entirely sure what you want. You might want to look into the DefaultTreeModel implementation or the TreeModel interface for the JTree, perhaps. No need to reinvent the wheel. More often than not the wheel will look distinctly triangular.
Anyway, this might help a bit:
import java.util.ArrayList;
public class Tree {
private Node root;
public Tree(String rootData)
{
root=new Node();
root.data=rootData;
root.children=new ArrayList<Node>();
}
public List<Node> getPathToNode(Node node) {
Node currentNode = node;
List<Node> reversePath = new ArrayList<Node>();
reversePath.add(node);
while (!(this.root.equals(currentNode)) {
currentNode = currentNode.getParentNode();
reversePath.add(currentNode);
}
Collections.reverse(reversePath); // now the list is root -> node
return reversePath;
}
}
import java.util.*;
class Node {
String data;
Node parent;
List<Node> children;
/* I would remove this constructor or at least initialize the field to non-null defaults. This is bloody dangerous. */
public Node()
{
data=null;
children=null;
parent=null;
}
public Node(String name)
{
Node n=new Node(name);
n.data=name;
n.children=new ArrayList<Node>();
}
public void addChild(String name) {
this.addChild(new Node(name));
}
public void addChild(Node child) {
this.children.add(child);
}
public void removeChild(Node child) {
this.children.remove(child);
}
public void removeChild(String name) {
this.removeChild(this.getChild(name));
}
public Node getChild(int childIndex) {
return this.children.get(childIndex);
}
public Node getChild(String childName) {
for (Node child : this.children) {
if (child.getName().equals(childName)) { return child; }
}
return null;
}
public Node getParentNode() {
return this.parent;
}
}
Here is the solution
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
public class MyTree extends JFrame{
private JTree tree;
private DefaultTreeModel model;
private DefaultMutableTreeNode rootNode;
public MyTree()
{
DefaultMutableTreeNode philosophersNode = getTree();
model = new DefaultTreeModel(philosophersNode);
tree = new JTree(model);
JButton addButton = new JButton("Add ");
addButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
addNode();
}
});
JButton removeButton = new JButton("Delete");
removeButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
removeSelectedNode();
}
});
JPanel inputPanel = new JPanel();
inputPanel.add(addButton);
inputPanel.add(removeButton);
Container container = getContentPane();
container.add(new JScrollPane(tree), BorderLayout.CENTER);
container.add(inputPanel, BorderLayout.NORTH);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(400, 500);
setVisible(true);
}
private DefaultMutableTreeNode getSelectedNode() {
return (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
}
private DefaultMutableTreeNode getTree() {
rootNode = new DefaultMutableTreeNode("Root");
return rootNode;
}
private void removeSelectedNode() {
DefaultMutableTreeNode selectedNode = getSelectedNode();
if (selectedNode != null){
if(selectedNode.toString()=="Root")
{
JOptionPane.showMessageDialog(MyTree.this, "Cannot delete root element", "Error",
JOptionPane.ERROR_MESSAGE);
}else{
model.removeNodeFromParent(selectedNode);
}
}
}
private void addNode() {
DefaultMutableTreeNode parent = getSelectedNode();
if (parent == null) {
JOptionPane.showMessageDialog(MyTree.this, "Select an area.", "Error",
JOptionPane.ERROR_MESSAGE);
return;
}
String name = JOptionPane.showInputDialog(MyTree.this, "Enter Name:");
model.insertNodeInto(new DefaultMutableTreeNode(name), parent, parent.getChildCount());
}
public static void main(String args[]) {
new MyTree();
}
}
I have a class structure as follows:
DefaultMutableTreeNode
|_
BCStructure
|_
| Module
|_
| Section
|_
Page
Module, Section, and Page are what I actually use in my JTree. If you run the sample code below, the drag and drop works. Notice it is using DefaultMutableTreeNodes. If however, I place the code in my real applicaiont which uses the subclasses of DefualtMutableTreeNode, it does not work.
While stepping through the code, I have noticed two areas of interest. The first is this:
class TreeTransferHandler extends TransferHandler {
DataFlavor nodesFlavor;
DataFlavor[] flavors = new DataFlavor[1];
DefaultMutableTreeNode[] nodesToRemove;
public TreeTransferHandler() {
try {
String mimeType = DataFlavor.javaJVMLocalObjectMimeType
+ ";class=\""
+ javax.swing.tree.DefaultMutableTreeNode[].class.getName()
+ "\"";
nodesFlavor = new DataFlavor(mimeType);
flavors[0] = nodesFlavor;
} catch (ClassNotFoundException e) {
System.out.println("ClassNotFound: " + e.getMessage());
}
}
This is where it sets the DataFlavor. It is setting it to a DefualtMutableTreeNode array which I assume is correct since DefaultMutableTreeNode is the super class of my nodes. But this was one area where I thought the problem could lie.
The other point is while debugging this code, I get as far as:
public DataFlavor[] getTransferDataFlavors() {
return flavors;
}
before the drag and drop fails. I assume that it returns a DataFlavor that is not liked to well. This is why I think there is something wrong with the DataFlavor. Is there something more I need to do/modify in order for this to work with subclasses? Any ideas or suggestions?
Thanks!
Complete sample code:
import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.dnd.*;
import java.util.*;
import java.util.List;
import javax.swing.*;
import javax.swing.tree.*;
public class TreeDragAndDrop {
private JScrollPane getContent() {
JTree tree = new JTree();
tree.setDragEnabled(true);
tree.setDropMode(DropMode.ON_OR_INSERT);
tree.setTransferHandler(new TreeTransferHandler());
tree.getSelectionModel().setSelectionMode(
TreeSelectionModel.CONTIGUOUS_TREE_SELECTION);
expandTree(tree);
return new JScrollPane(tree);
}
private void expandTree(JTree tree) {
DefaultMutableTreeNode root =
(DefaultMutableTreeNode)tree.getModel().getRoot();
Enumeration e = root.breadthFirstEnumeration();
while(e.hasMoreElements()) {
DefaultMutableTreeNode node =
(DefaultMutableTreeNode)e.nextElement();
if(node.isLeaf()) continue;
int row = tree.getRowForPath(new TreePath(node.getPath()));
tree.expandRow(row);
}
}
public static void main(String[] args) {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new TreeDragAndDrop().getContent());
f.setSize(400,400);
f.setLocation(200,200);
f.setVisible(true);
}
}
class TreeTransferHandler extends TransferHandler {
DataFlavor nodesFlavor;
DataFlavor[] flavors = new DataFlavor[1];
DefaultMutableTreeNode[] nodesToRemove;
public TreeTransferHandler() {
try {
String mimeType = DataFlavor.javaJVMLocalObjectMimeType +
";class=\"" +
javax.swing.tree.DefaultMutableTreeNode[].class.getName() +
"\"";
nodesFlavor = new DataFlavor(mimeType);
flavors[0] = nodesFlavor;
} catch(ClassNotFoundException e) {
System.out.println("ClassNotFound: " + e.getMessage());
}
}
public boolean canImport(TransferHandler.TransferSupport support) {
if(!support.isDrop()) {
return false;
}
support.setShowDropLocation(true);
if(!support.isDataFlavorSupported(nodesFlavor)) {
return false;
}
// Do not allow a drop on the drag source selections.
JTree.DropLocation dl =
(JTree.DropLocation)support.getDropLocation();
JTree tree = (JTree)support.getComponent();
int dropRow = tree.getRowForPath(dl.getPath());
int[] selRows = tree.getSelectionRows();
for(int i = 0; i 0 &&
target.getLevel() 0 && selRows.length == 1)
return false;
// first may have children.
for(int i = 1; i selRows.length-1) {
// Not all children of first are selected.
return false;
}
}
}
return true;
}
protected Transferable createTransferable(JComponent c) {
JTree tree = (JTree)c;
TreePath[] paths = tree.getSelectionPaths();
if(paths != null) {
// Make up a node array of copies for transfer and
// another for/of the nodes that will be removed in
// exportDone after a successful drop.
List copies =
new ArrayList();
List toRemove =
new ArrayList();
DefaultMutableTreeNode node =
(DefaultMutableTreeNode)paths[0].getLastPathComponent();
DefaultMutableTreeNode copy = copy(node);
copies.add(copy);
toRemove.add(node);
for(int i = 1; i node.getLevel()) { // child node
copy.add(copy(next));
// node already contains child
} else { // sibling
copies.add(copy(next));
toRemove.add(next);
}
}
DefaultMutableTreeNode[] nodes =
copies.toArray(new DefaultMutableTreeNode[copies.size()]);
nodesToRemove =
toRemove.toArray(new DefaultMutableTreeNode[toRemove.size()]);
return new NodesTransferable(nodes);
}
return null;
}
/** Defensive copy used in createTransferable. */
private DefaultMutableTreeNode copy(TreeNode node) {
return new DefaultMutableTreeNode(node);
}
protected void exportDone(JComponent source, Transferable data, int action) {
if((action & MOVE) == MOVE) {
JTree tree = (JTree)source;
DefaultTreeModel model = (DefaultTreeModel)tree.getModel();
// Remove nodes saved in nodesToRemove in createTransferable.
for(int i = 0; i
If you think the problem is that you subclass the DefaultMutableTreeNode, try making your DataFlavor to be an array of Object, or even better, an ArrayList:
DataFlavor.javaJVMLocalObjectMimeType + ";class=java.util.ArrayList"
This way you can return your copies list along with your transferable. Maybe it will avoid the problem.
here's a rough scetch of how to do this with lists:
import javax.swing.*;
import javax.swing.tree.*;
import java.awt.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;
import java.util.*;
import java.util.List;
/**
* Author: Denis Tulskiy
* Date: 1/5/11
*/
public class Test extends JFrame {
class NodeA extends DefaultMutableTreeNode {
NodeA(Object userObject) {
super(userObject);
}
}
class NodeB extends DefaultMutableTreeNode {
NodeB(Object userObject) {
super(userObject);
}
}
class NodeC extends DefaultMutableTreeNode {
NodeC(Object userObject) {
super(userObject);
}
}
private static class MyTransferHandler extends TransferHandler {
#Override
public int getSourceActions(JComponent c) {
return MOVE;
}
#Override
protected Transferable createTransferable(JComponent c) {
JTree tree = (JTree) c;
ArrayList<TreeNode> nodes = new ArrayList<TreeNode>();
for (TreePath path : tree.getSelectionPaths()) {
DefaultMutableTreeNode component = (DefaultMutableTreeNode) path.getLastPathComponent();
nodes.add(component);
}
return new NodesTransferable(nodes);
}
#Override
public boolean canImport(TransferSupport support) {
return support.isDataFlavorSupported(NodesTransferable.getDataFlavor());
}
#Override
public boolean importData(TransferSupport support) {
if (!canImport(support)) {
return false;
}
JTree tree = (JTree) support.getComponent();
List<DefaultMutableTreeNode> data = null;
try {
data = (List<DefaultMutableTreeNode>) support.getTransferable().getTransferData(NodesTransferable.getDataFlavor());
} catch (UnsupportedFlavorException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
if (data != null) {
Point dropPoint = support.getDropLocation().getDropPoint();
TreePath path = tree.getPathForLocation(dropPoint.x, dropPoint.y);
DefaultMutableTreeNode parent = (DefaultMutableTreeNode) path.getLastPathComponent();
for (DefaultMutableTreeNode node : data) {
node.removeFromParent();
parent.add(node);
}
DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
model.reload();
}
return true;
}
}
static class NodesTransferable implements Transferable {
private static DataFlavor dataFlavor;
public static DataFlavor getDataFlavor() {
if (dataFlavor == null) {
try {
dataFlavor = new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType + ";class=java.util.ArrayList");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
return dataFlavor;
}
private java.util.List<TreeNode> nodes;
NodesTransferable(List<TreeNode> nodes) {
this.nodes = nodes;
}
#Override
public DataFlavor[] getTransferDataFlavors() {
return new DataFlavor[]{getDataFlavor()};
}
#Override
public boolean isDataFlavorSupported(DataFlavor flavor) {
return flavor.match(dataFlavor);
}
#Override
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
return nodes;
}
}
public Test() {
JTree tree = new JTree();
tree.setDragEnabled(true);
tree.setDropMode(DropMode.ON_OR_INSERT);
DefaultMutableTreeNode root = new DefaultMutableTreeNode("Root");
NodeA child = new NodeA("nodea");
root.add(child);
child.add(new NodeB("nodeb"));
child.add(new NodeC("nodec"));
tree.setModel(new DefaultTreeModel(root));
tree.setTransferHandler(new MyTransferHandler());
setLayout(new BorderLayout());
add(tree, BorderLayout.CENTER);
setSize(300, 400);
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
public static void main(String[] args) {
new Test().setVisible(true);
}
}