I'm exploring How to do the implementation of the Copy & Paste of JTree.
Because, I want Copy from DefaultMutableTreeNode like toString() to paste in another application like Sublime Text.
I was viewing the code in order to view how copy & paste is implemented and how is used drag and drop in JTree.
My first thougth was, Copy and Paste between DefaultMutableTreeNode of JTree must be tested, later how paste from Clipboard to another application, but my code is not working and Don't know why is failing, and I need to solve.
NOTE: Sorry my code is a bit long, because if I put only a snippet code will be non functional. For me all code here is needed to do tests.
Here my code:
import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.event.ActionEvent;
import java.util.*;
import javax.swing.*;
import javax.swing.tree.*;
public class JTreeTransferHandler extends JFrame {
public JTreeTransferHandler() {
initiate();
}
private void initiate() {
setTitle("Copy from JTree");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(400,400);
setLocation(200,200);
setVisible(true);
DefaultMutableTreeNode root = new DefaultMutableTreeNode("Root");
DefaultMutableTreeNode vegetableNode = new DefaultMutableTreeNode("Vegetables");
vegetableNode.add(new DefaultMutableTreeNode("Capsicum"));
vegetableNode.add(new DefaultMutableTreeNode("Carrot"));
DefaultMutableTreeNode fruitNode = new DefaultMutableTreeNode("Fruits");
fruitNode.add(new DefaultMutableTreeNode("Mango"));
fruitNode.add(new DefaultMutableTreeNode("Apple"));
root.add(vegetableNode);
root.add(fruitNode);
JTree tree = new JTree(root);
TreeTransferHandler treeTransferHandler = new TreeTransferHandler();
tree.setTransferHandler(treeTransferHandler);
Clipboard clipboard = this.getToolkit().getSystemClipboard();
JButton copy = new JButton("Copy");
copy.addActionListener((ActionEvent e) -> {
TransferHandler handler = tree.getTransferHandler();
handler.exportToClipboard(tree, clipboard, TransferHandler.COPY);
});
JButton paste = new JButton("Paste");
paste.addActionListener((ActionEvent e) -> {
Transferable clipData = clipboard.getContents(clipboard);
if (clipData != null) {
if (clipData.isDataFlavorSupported(treeTransferHandler.nodesFlavor)) {
TransferHandler handler = tree.getTransferHandler();
handler.importData(tree, clipData);
}
}
});
JPanel buttonsPanel = new JPanel();
buttonsPanel.add(copy);
buttonsPanel.add(paste);
add(new JScrollPane(tree), BorderLayout.CENTER);
add(buttonsPanel, BorderLayout.SOUTH);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JTreeTransferHandler jTreeTransferHandler = new JTreeTransferHandler();
});
}
}
class TreeTransferHandler extends TransferHandler {
DataFlavor nodesFlavor;
DataFlavor[] flavors = new DataFlavor[1];
DefaultMutableTreeNode[] nodesToRemove;
public TreeTransferHandler() {
try {
String mimeType = DataFlavor.javaJVMLocalObjectMimeType + ";class=\"" +
DefaultMutableTreeNode[].class.getName() + "\"";
nodesFlavor = new DataFlavor(mimeType);
flavors[0] = nodesFlavor;
} catch(ClassNotFoundException e) {
System.out.println("ClassNotFound: " + e.getMessage());
}
}
//TransferHandler
#Override public int getSourceActions(JComponent c) {
return TransferHandler.COPY;
}
//TransferHandler
#Override public boolean canImport(JComponent comp, DataFlavor flavor[]) {
for (int i = 0, n = flavor.length; i < n; i++) {
for (int j = 0, m = flavors.length; j < m; j++) {
if (flavor[i].equals(flavors[j])) {
return true;
}
}
}
return false;
}
//TransferHandler
#Override protected Transferable createTransferable(JComponent c) {
JTree tree = (JTree) c;
TreePath[] paths = tree.getSelectionPaths();
if (paths != null) {
List<DefaultMutableTreeNode> copies = new ArrayList<>();
List<DefaultMutableTreeNode> toRemove = new ArrayList<>();
DefaultMutableTreeNode node =
(DefaultMutableTreeNode) paths[0].getLastPathComponent();
DefaultMutableTreeNode copy = copy(node);
copies.add(copy);
toRemove.add(node);
for (int i = 1; i < paths.length; i++) {
DefaultMutableTreeNode next =
(DefaultMutableTreeNode) paths[i].getLastPathComponent();
// Do not allow higher level nodes to be added to list.
if (next.getLevel() < node.getLevel()) {
break;
} else if (next.getLevel() > 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);
}
//TransferHandler
#Override public boolean importData(TransferHandler.TransferSupport support) {
if (!canImport(support)) {
return false;
}
// Extract transfer data.
DefaultMutableTreeNode[] nodes = null;
try {
Transferable t = support.getTransferable();
nodes = (DefaultMutableTreeNode[]) t.getTransferData(nodesFlavor);
} catch (UnsupportedFlavorException ufe) {
System.out.println("UnsupportedFlavor: " + ufe.getMessage());
} catch (java.io.IOException ioe) {
System.out.println("I/O error: " + ioe.getMessage());
}
// Get drop location info.
JTree.DropLocation dl
= (JTree.DropLocation) support.getDropLocation();
int childIndex = dl.getChildIndex();
TreePath dest = dl.getPath();
DefaultMutableTreeNode parent
= (DefaultMutableTreeNode) dest.getLastPathComponent();
JTree tree = (JTree) support.getComponent();
DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
// Configure for drop mode.
int index = childIndex; // DropMode.INSERT
if (childIndex == -1) { // DropMode.ON
index = parent.getChildCount();
}
// Add data to model.
for (int i = 0; i < nodes.length; i++) {
model.insertNodeInto(nodes[i], parent, index++);
}
return true;
}
//TransferHandler
public boolean importData(JComponent comp, Transferable t) {
// Extract transfer data.
DefaultMutableTreeNode[] nodes;
try {
nodes = (DefaultMutableTreeNode[]) t.getTransferData(nodesFlavor);
if (comp instanceof JTree) {
JTree tree = (JTree)comp;
DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
JTree.DropLocation dl = tree.getDropLocation();
int childIndex = dl.getChildIndex();
TreePath dest = dl.getPath();
DefaultMutableTreeNode parent =
(DefaultMutableTreeNode)dest.getLastPathComponent();
// Configure for drop mode.
int index = childIndex; // DropMode.INSERT
if (childIndex == -1) { // DropMode.ON
index = parent.getChildCount();
}
// Add data to model.
for (DefaultMutableTreeNode node : nodes) {
model.insertNodeInto(node, parent, index++);
}
return true;
}
} catch (UnsupportedFlavorException ufe) {
System.out.println("UnsupportedFlavor: " + ufe.getMessage());
} catch (java.io.IOException ioe) {
System.out.println("I/O error: " + ioe.getMessage());
}
return false;
}
public class NodesTransferable implements Transferable {
DefaultMutableTreeNode[] nodes;
public NodesTransferable(DefaultMutableTreeNode[] nodes) {
this.nodes = nodes;
}
//Transferable
#Override public Object getTransferData(DataFlavor flavor) {
if(!isDataFlavorSupported(flavor)) {
return false;
}
return nodes;
}
//Transferable
#Override public DataFlavor[] getTransferDataFlavors() {
return flavors;
}
//Transferable
#Override public boolean isDataFlavorSupported(DataFlavor flavor) {
return flavor.equals(nodesFlavor);
}
}
}
TransferHandler.TransferSupport#isDrop() method returns false in the case of via Clipboard, so switching it here seems to work fine.
// Get drop location info.
int childIndex;
TreePath dest;
if (support.isDrop()) {
JTree.DropLocation dl = (JTree.DropLocation) support.getDropLocation();
childIndex = dl.getChildIndex();
dest = dl.getPath();
} else {
childIndex = -1;
JTree tree = (JTree) support.getComponent();
dest = tree.getSelectionPath();
}
JTreeTransferHandler2.java
import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.event.ActionEvent;
import java.util.*;
import java.util.List;
import javax.swing.*;
import javax.swing.tree.*;
public class JTreeTransferHandler2 extends JFrame {
public JTreeTransferHandler2() {
initiate();
}
private void initiate() {
setTitle("Copy from JTree");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(400,400);
setLocation(200,200);
setVisible(true);
DefaultMutableTreeNode root = new DefaultMutableTreeNode("Root");
DefaultMutableTreeNode vegetableNode = new DefaultMutableTreeNode("Vegetables");
vegetableNode.add(new DefaultMutableTreeNode("Capsicum"));
vegetableNode.add(new DefaultMutableTreeNode("Carrot"));
DefaultMutableTreeNode fruitNode = new DefaultMutableTreeNode("Fruits");
fruitNode.add(new DefaultMutableTreeNode("Mango"));
fruitNode.add(new DefaultMutableTreeNode("Apple"));
root.add(vegetableNode);
root.add(fruitNode);
JTree tree = new JTree(root);
TreeTransferHandler treeTransferHandler = new TreeTransferHandler();
tree.setTransferHandler(treeTransferHandler);
Clipboard clipboard = this.getToolkit().getSystemClipboard();
JButton copy = new JButton("Copy");
copy.addActionListener((ActionEvent e) -> {
TransferHandler handler = tree.getTransferHandler();
handler.exportToClipboard(tree, clipboard, TransferHandler.COPY);
});
JButton paste = new JButton("Paste");
paste.addActionListener((ActionEvent e) -> {
Transferable clipData = clipboard.getContents(null);
if (clipData != null) {
if (clipData.isDataFlavorSupported(treeTransferHandler.nodesFlavor)) {
TransferHandler handler = tree.getTransferHandler();
handler.importData(tree, clipData);
}
}
});
JPanel buttonsPanel = new JPanel();
buttonsPanel.add(copy);
buttonsPanel.add(paste);
add(new JScrollPane(tree), BorderLayout.CENTER);
add(buttonsPanel, BorderLayout.SOUTH);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JTreeTransferHandler2 jTreeTransferHandler = new JTreeTransferHandler2();
});
}
}
class TreeTransferHandler extends TransferHandler {
DataFlavor nodesFlavor;
DataFlavor[] flavors = new DataFlavor[1];
DefaultMutableTreeNode[] nodesToRemove;
public TreeTransferHandler() {
try {
String mimeType = DataFlavor.javaJVMLocalObjectMimeType + ";class=\"" +
DefaultMutableTreeNode[].class.getName() + "\"";
nodesFlavor = new DataFlavor(mimeType);
flavors[0] = nodesFlavor;
} catch(ClassNotFoundException e) {
System.out.println("ClassNotFound: " + e.getMessage());
}
}
//TransferHandler
#Override public int getSourceActions(JComponent c) {
return TransferHandler.COPY;
}
//TransferHandler
#Override public boolean canImport(JComponent comp, DataFlavor flavor[]) {
for (int i = 0, n = flavor.length; i < n; i++) {
for (int j = 0, m = flavors.length; j < m; j++) {
if (flavor[i].equals(flavors[j])) {
return true;
}
}
}
return false;
}
//TransferHandler
#Override protected Transferable createTransferable(JComponent c) {
JTree tree = (JTree) c;
TreePath[] paths = tree.getSelectionPaths();
if (paths != null) {
List<DefaultMutableTreeNode> copies = new ArrayList<>();
List<DefaultMutableTreeNode> toRemove = new ArrayList<>();
DefaultMutableTreeNode node =
(DefaultMutableTreeNode) paths[0].getLastPathComponent();
DefaultMutableTreeNode copy = copy(node);
copies.add(copy);
toRemove.add(node);
for (int i = 1; i < paths.length; i++) {
DefaultMutableTreeNode next =
(DefaultMutableTreeNode) paths[i].getLastPathComponent();
// Do not allow higher level nodes to be added to list.
if (next.getLevel() < node.getLevel()) {
break;
} else if (next.getLevel() > 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);
}
//TransferHandler
#Override public boolean importData(TransferHandler.TransferSupport support) {
if (!canImport(support)) {
return false;
}
// Extract transfer data.
DefaultMutableTreeNode[] nodes = null;
try {
Transferable t = support.getTransferable();
nodes = (DefaultMutableTreeNode[]) t.getTransferData(nodesFlavor);
} catch (UnsupportedFlavorException ufe) {
System.out.println("UnsupportedFlavor: " + ufe.getMessage());
} catch (java.io.IOException ioe) {
System.out.println("I/O error: " + ioe.getMessage());
}
// Get drop location info.
int childIndex;
TreePath dest;
if (support.isDrop()) {
JTree.DropLocation dl = (JTree.DropLocation) support.getDropLocation();
childIndex = dl.getChildIndex();
dest = dl.getPath();
} else {
childIndex = -1;
JTree tree = (JTree) support.getComponent();
dest = tree.getSelectionPath();
}
DefaultMutableTreeNode parent
= (DefaultMutableTreeNode) dest.getLastPathComponent();
JTree tree = (JTree) support.getComponent();
DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
// Configure for drop mode.
int index = childIndex; // DropMode.INSERT
if (childIndex == -1) { // DropMode.ON
index = parent.getChildCount();
}
// Add data to model.
for (int i = 0; i < nodes.length; i++) {
// ArrayIndexOutOfBoundsException
model.insertNodeInto(nodes[i], parent, index++);
}
return true;
}
//TransferHandler
#Override public boolean importData(JComponent comp, Transferable t) {
return importData(new TransferHandler.TransferSupport(comp, t));
}
public class NodesTransferable implements Transferable {
DefaultMutableTreeNode[] nodes;
public NodesTransferable(DefaultMutableTreeNode[] nodes) {
this.nodes = nodes;
}
//Transferable
#Override public Object getTransferData(DataFlavor flavor) {
if(!isDataFlavorSupported(flavor)) {
return false;
}
return nodes;
}
//Transferable
#Override public DataFlavor[] getTransferDataFlavors() {
return flavors;
}
//Transferable
#Override public boolean isDataFlavorSupported(DataFlavor flavor) {
return flavor.equals(nodesFlavor);
}
}
}
Related
I'm developing a file explorer using Java Swing and I need to set all leaves with the same icon (folder icon). Here is what I have when running without updating Look and Feel:
Note that "System Volume Information" is a folder but has a file icon. To solve it I use this line before showing my JFrame:
UIManager.put("Tree.leafIcon", UIManager.getIcon("FileView.directoryIcon"));.
The problem is that I want to use System Look and Feel: UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());, but the last leaves (folders that do not have any folders inside) are not updating:
Here is my code (removed imports to improve readability):
public class Scratch extends JFrame {
protected JTree m_tree;
protected DefaultTreeModel m_model;
protected JTextField m_display;
protected JScrollPane scrollPane;
protected Action m_action;
protected TreePath m_clickedPath;
public static void main(String argv[]) {
try {
UIManager.put("Tree.leafIcon", UIManager.getIcon("FileView.directoryIcon"));
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
e.printStackTrace();
}
new Scratch();
}
public Scratch() {
super("Test");
setSize(500, 400);
DefaultMutableTreeNode top = new DefaultMutableTreeNode("Computer");
DefaultMutableTreeNode node;
File[] roots = File.listRoots();
for (int k = 0; k < roots.length; k++) {
node = new DefaultMutableTreeNode(new FileNode(roots[k]));
top.add(node);
node.add(new DefaultMutableTreeNode(new Boolean(true)));
}
m_model = new DefaultTreeModel(top);
m_tree = new JTree(m_model);
m_tree.addTreeExpansionListener(new DirExpansionListener());
m_tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
scrollPane = new JScrollPane();
scrollPane.getViewport().add(m_tree);
getContentPane().add(scrollPane, BorderLayout.CENTER);
setVisible(true);
}
class DirExpansionListener extends JLabel implements TreeExpansionListener {
public void treeExpanded(TreeExpansionEvent event) {
final DefaultMutableTreeNode node = getTreeNode(event.getPath());
final FileNode fnode = getFileNode(node);
Thread runner = new Thread() {
public void run() {
if (fnode != null && fnode.expand(node)) {
Runnable runnable = new Runnable() {
public void run() {
m_model.reload(node);
}
};
runnable.run();
}
}
};
runner.start();
}
public void treeCollapsed(TreeExpansionEvent event) {}
}
private FileNode getFileNode(DefaultMutableTreeNode node) {
if (node == null)
return null;
Object obj = node.getUserObject();
if (obj instanceof FileNode)
return (FileNode) obj;
else
return null;
}
private DefaultMutableTreeNode getTreeNode(TreePath path) {
return (DefaultMutableTreeNode) (path.getLastPathComponent());
}
}
class FileNode {
protected File m_file;
public FileNode(File file) {
m_file = file;
}
public File getFile() {
return m_file;
}
public String toString() {
return m_file.getName().length() > 0 ? m_file.getName() : m_file.getPath();
}
public boolean expand(DefaultMutableTreeNode parent) {
DefaultMutableTreeNode flag = (DefaultMutableTreeNode) parent.getFirstChild();
if (flag == null) // No flag
return false;
Object obj = flag.getUserObject();
if (!(obj instanceof Boolean))
return false; // Already expanded
// Texto antes de adicionar os nós
flag.setUserObject("Loading...");
parent.removeAllChildren(); // Remove Flag
File[] files = listFiles();
if (files == null)
return true;
Vector v = new Vector();
for (int k = 0; k < files.length; k++) {
File f = files[k];
if (!f.isDirectory()) {
continue;
}
FileNode newNode = new FileNode(f);
boolean isAdded = false;
for (int i = 0; i < v.size(); i++) {
FileNode nd = (FileNode) v.elementAt(i);
if (newNode.compareTo(nd) < 0) {
v.insertElementAt(newNode, i);
isAdded = true;
break;
}
}
if (!isAdded)
v.addElement(newNode);
}
for (int i = 0; i < v.size(); i++) {
FileNode nd = (FileNode) v.elementAt(i);
DefaultMutableTreeNode node = new DefaultMutableTreeNode(nd);
parent.add(node);
if (nd.hasSubDirs()) {
node.add(new DefaultMutableTreeNode(new Boolean(true)));
}
}
return true;
}
public boolean hasSubDirs() {
File[] files = listFiles();
if (files == null) {
return false;
}
for (int k = 0; k < files.length; k++) {
if (files[k].isDirectory()) {
return true;
}
}
return false;
}
public int compareTo(FileNode toCompare) {
return m_file.getName().compareToIgnoreCase(toCompare.m_file.getName());
}
protected File[] listFiles() {
if (!m_file.isDirectory())
return null;
try {
return m_file.listFiles();
} catch (Exception ex) {
JOptionPane.showMessageDialog(null, "Error reading directory " + m_file.getAbsolutePath(), "Warning", JOptionPane.WARNING_MESSAGE);
return null;
}
}
}
How can I update the folders with System Look and Feel?
Although for your application, a folder should be displayed as a folder icon, the JTree sees an empty folder as a leaf, so it will use the leaf icon always.
To change that you should provide a custom cell renderer, so that folders are shown as folders and files as sheets, or whatever.
There's a example in the Java tutorials.
I have a customized JTree that shows file and folders of a directory, currently it only pick the desktop directory. I could not fine any line to modify it. also fileSystemView only has getters and not setter.
Here is my working code with default 'desktop' parent directory.
public final class MainPanel extends JPanel {
private MainPanel() {
super(new BorderLayout());
final FileSystemView fileSystemView = FileSystemView.getFileSystemView();
DefaultMutableTreeNode root = new DefaultMutableTreeNode();
final DefaultTreeModel treeModel = new DefaultTreeModel(root);
for (File fileSystemRoot: fileSystemView.getRoots()) {
DefaultMutableTreeNode node = new DefaultMutableTreeNode(new CheckBoxNode(fileSystemRoot, Status.DESELECTED));
root.add(node);
for (File file: fileSystemView.getFiles(fileSystemRoot, true)) {
if (file.isDirectory()) {
node.add(new DefaultMutableTreeNode(new CheckBoxNode(file, Status.DESELECTED)));
}
}
}
treeModel.addTreeModelListener(new CheckBoxStatusUpdateListener());
final JTree tree = new JTree(treeModel) {
#Override public void updateUI() {
setCellRenderer(null);
setCellEditor(null);
super.updateUI();
//???#1: JDK 1.6.0 bug??? Nimbus LnF
setCellRenderer(new FileTreeCellRenderer(fileSystemView));
setCellEditor(new CheckBoxNodeEditor(fileSystemView));
}
};
tree.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
tree.setRootVisible(false);
tree.addTreeSelectionListener(new FolderSelectionListener(fileSystemView));
//tree.setCellRenderer(new FileTreeCellRenderer(fileSystemView));
//tree.setCellEditor(new CheckBoxNodeEditor(fileSystemView));
tree.setEditable(true);
tree.expandRow(0);
//tree.setToggleClickCount(1);
setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
add(new JScrollPane(tree));
add(new JButton(new AbstractAction("test") {
#Override public void actionPerformed(ActionEvent ae) {
System.out.println("------------------");
searchTreeForCheckedNode(tree.getPathForRow(0));
// DefaultMutableTreeNode root = (DefaultMutableTreeNode) treeModel.getRoot();
// Enumeration e = root.breadthFirstEnumeration();
// while (e.hasMoreElements()) {
// DefaultMutableTreeNode node = (DefaultMutableTreeNode) e.nextElement();
// CheckBoxNode check = (CheckBoxNode) node.getUserObject();
// if (check != null && check.status == Status.SELECTED) {
// System.out.println(check.file.toString());
// }
// }
}
}), BorderLayout.SOUTH);
setPreferredSize(new Dimension(320, 240));
}
private static void searchTreeForCheckedNode(TreePath path) {
Object o = path.getLastPathComponent();
if (!(o instanceof DefaultMutableTreeNode)) {
return;
}
DefaultMutableTreeNode node = (DefaultMutableTreeNode) o;
o = node.getUserObject();
if (!(o instanceof CheckBoxNode)) {
return;
}
CheckBoxNode check = (CheckBoxNode) o;
if (check.status == Status.SELECTED) {
System.out.println(check.file.toString());
} else if (check.status == Status.INDETERMINATE && !node.isLeaf() && node.getChildCount() >= 0) {
Enumeration e = node.children();
while (e.hasMoreElements()) {
searchTreeForCheckedNode(path.pathByAddingChild(e.nextElement()));
}
}
}
public static void main(String... args) {
EventQueue.invokeLater(new Runnable() {
#Override public void run() {
createAndShowGUI();
}
});
}
public static void createAndShowGUI() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException
| IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("FileSystemTreeWithCheckBox");
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
//frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.getContentPane().add(new MainPanel());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
class TriStateCheckBox extends JCheckBox {
private Icon currentIcon;
#Override public void updateUI() {
currentIcon = getIcon();
setIcon(null);
super.updateUI();
EventQueue.invokeLater(new Runnable() {
#Override public void run() {
if (currentIcon != null) {
setIcon(new IndeterminateIcon());
}
setOpaque(false);
}
});
}
}
class IndeterminateIcon implements Icon {
private static final Color FOREGROUND = new Color(50, 20, 255, 200); //TEST: UIManager.getColor("CheckBox.foreground");
private static final int SIDE_MARGIN = 4;
private static final int HEIGHT = 2;
private final Icon icon = UIManager.getIcon("CheckBox.icon");
#Override public void paintIcon(Component c, Graphics g, int x, int y) {
icon.paintIcon(c, g, x, y);
int w = getIconWidth();
int h = getIconHeight();
Graphics2D g2 = (Graphics2D) g.create();
g2.setPaint(FOREGROUND);
g2.translate(x, y);
g2.fillRect(SIDE_MARGIN, (h - HEIGHT) / 2, w - SIDE_MARGIN - SIDE_MARGIN, HEIGHT);
//g2.translate(-x, -y);
g2.dispose();
}
#Override public int getIconWidth() {
return icon.getIconWidth();
}
#Override public int getIconHeight() {
return icon.getIconHeight();
}
}
enum Status { SELECTED, DESELECTED, INDETERMINATE }
class CheckBoxNode {
public final File file;
public final Status status;
public CheckBoxNode(File file) {
this.file = file;
status = Status.INDETERMINATE;
}
public CheckBoxNode(File file, Status status) {
this.file = file;
this.status = status;
}
#Override public String toString() {
return file.getName();
}
}
class FolderSelectionListener implements TreeSelectionListener {
private final FileSystemView fileSystemView;
public FolderSelectionListener(FileSystemView fileSystemView) {
this.fileSystemView = fileSystemView;
}
#Override public void valueChanged(TreeSelectionEvent e) {
final JTree tree = (JTree) e.getSource();
final DefaultMutableTreeNode node = (DefaultMutableTreeNode) e.getPath().getLastPathComponent();
if (!node.isLeaf()) {
return;
}
CheckBoxNode check = (CheckBoxNode) node.getUserObject();
if (check == null) {
return;
}
final File parent = check.file;
if (!parent.isDirectory()) {
return;
}
final Status parentStatus = check.status == Status.SELECTED ? Status.SELECTED : Status.DESELECTED;
final DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
Task worker = new Task(fileSystemView, parent) {
#Override protected void process(List<File> chunks) {
if (!tree.isDisplayable()) {
System.out.println("process: DISPOSE_ON_CLOSE");
cancel(true);
return;
}
for (File file: chunks) {
model.insertNodeInto(new DefaultMutableTreeNode(new CheckBoxNode(file, parentStatus)), node, node.getChildCount());
//node.add(new DefaultMutableTreeNode(new CheckBoxNode(file, parentStatus)));
}
//model.reload(parent); //= model.nodeStructureChanged(parent);
}
};
worker.execute();
}
}
class Task extends SwingWorker<String, File> {
private final FileSystemView fileSystemView;
private final File parent;
public Task(FileSystemView fileSystemView, File parent) {
super();
this.fileSystemView = fileSystemView;
this.parent = parent;
}
#Override public String doInBackground() {
File[] children = fileSystemView.getFiles(parent, true);
for (File child: children) {
if (child.isDirectory()) {
publish(child);
}
}
return "done";
}
}
class FileTreeCellRenderer extends TriStateCheckBox implements TreeCellRenderer {
private final FileSystemView fileSystemView;
private final JPanel panel = new JPanel(new BorderLayout());
private final DefaultTreeCellRenderer renderer = new DefaultTreeCellRenderer();
public FileTreeCellRenderer(FileSystemView fileSystemView) {
super();
String uiName = getUI().getClass().getName();
if (uiName.contains("Synth") && System.getProperty("java.version").startsWith("1.7.0")) {
System.out.println("XXX: FocusBorder bug?, JDK 1.7.0, Nimbus start LnF");
renderer.setBackgroundSelectionColor(new Color(0, 0, 0, 0));
}
this.fileSystemView = fileSystemView;
panel.setFocusable(false);
panel.setRequestFocusEnabled(false);
panel.setOpaque(false);
panel.add(this, BorderLayout.WEST);
this.setOpaque(false);
}
#Override public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
JLabel l = (JLabel) renderer.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
l.setFont(tree.getFont());
if (value instanceof DefaultMutableTreeNode) {
this.setEnabled(tree.isEnabled());
this.setFont(tree.getFont());
Object userObject = ((DefaultMutableTreeNode) value).getUserObject();
if (userObject instanceof CheckBoxNode) {
CheckBoxNode node = (CheckBoxNode) userObject;
if (node.status == Status.INDETERMINATE) {
setIcon(new IndeterminateIcon());
} else {
setIcon(null);
}
setText("");
File file = (File) node.file;
l.setIcon(fileSystemView.getSystemIcon(file));
l.setText(fileSystemView.getSystemDisplayName(file));
l.setToolTipText(file.getPath());
setSelected(node.status == Status.SELECTED);
}
//panel.add(this, BorderLayout.WEST);
panel.add(l);
return panel;
}
return l;
}
#Override public void updateUI() {
super.updateUI();
if (panel != null) {
//panel.removeAll(); //??? Change to Nimbus LnF, JDK 1.6.0
panel.updateUI();
//panel.add(this, BorderLayout.WEST);
}
setName("Tree.cellRenderer");
//???#1: JDK 1.6.0 bug??? #see 1.7.0 DefaultTreeCellRenderer#updateUI()
//if (System.getProperty("java.version").startsWith("1.6.0")) {
// renderer = new DefaultTreeCellRenderer();
//}
}
}
class CheckBoxNodeEditor extends TriStateCheckBox implements TreeCellEditor {
private final FileSystemView fileSystemView;
private final JPanel panel = new JPanel(new BorderLayout());
private final DefaultTreeCellRenderer renderer = new DefaultTreeCellRenderer();
private File file;
public CheckBoxNodeEditor(FileSystemView fileSystemView) {
super();
this.fileSystemView = fileSystemView;
this.addActionListener(new ActionListener() {
#Override public void actionPerformed(ActionEvent e) {
stopCellEditing();
}
});
panel.setFocusable(false);
panel.setRequestFocusEnabled(false);
panel.setOpaque(false);
panel.add(this, BorderLayout.WEST);
this.setOpaque(false);
}
#Override public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row) {
//JLabel l = (JLabel) renderer.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
JLabel l = (JLabel) renderer.getTreeCellRendererComponent(tree, value, true, expanded, leaf, row, true);
l.setFont(tree.getFont());
setOpaque(false);
if (value instanceof DefaultMutableTreeNode) {
this.setEnabled(tree.isEnabled());
this.setFont(tree.getFont());
Object userObject = ((DefaultMutableTreeNode) value).getUserObject();
if (userObject instanceof CheckBoxNode) {
CheckBoxNode node = (CheckBoxNode) userObject;
if (node.status == Status.INDETERMINATE) {
setIcon(new IndeterminateIcon());
} else {
setIcon(null);
}
file = node.file;
l.setIcon(fileSystemView.getSystemIcon(file));
l.setText(fileSystemView.getSystemDisplayName(file));
setSelected(node.status == Status.SELECTED);
}
//panel.add(this, BorderLayout.WEST);
panel.add(l);
return panel;
}
return l;
}
#Override public Object getCellEditorValue() {
return new CheckBoxNode(file, isSelected() ? Status.SELECTED : Status.DESELECTED);
}
#Override public boolean isCellEditable(EventObject e) {
if (e instanceof MouseEvent && e.getSource() instanceof JTree) {
MouseEvent me = (MouseEvent) e;
JTree tree = (JTree) e.getSource();
TreePath path = tree.getPathForLocation(me.getX(), me.getY());
Rectangle r = tree.getPathBounds(path);
if (r == null) {
return false;
}
Dimension d = getPreferredSize();
r.setSize(new Dimension(d.width, r.height));
if (r.contains(me.getX(), me.getY())) {
if (file == null && System.getProperty("java.version").startsWith("1.7.0")) {
System.out.println("XXX: Java 7, only on first run\n" + getBounds());
setBounds(new Rectangle(0, 0, d.width, r.height));
}
//System.out.println(getBounds());
return true;
}
}
return false;
}
#Override public void updateUI() {
super.updateUI();
setName("Tree.cellEditor");
if (panel != null) {
//panel.removeAll(); //??? Change to Nimbus LnF, JDK 1.6.0
panel.updateUI();
//panel.add(this, BorderLayout.WEST);
}
//???#1: JDK 1.6.0 bug??? #see 1.7.0 DefaultTreeCellRenderer#updateUI()
//if (System.getProperty("java.version").startsWith("1.6.0")) {
// renderer = new DefaultTreeCellRenderer();
//}
}
//Copied from AbstractCellEditor
// protected EventListenerList listenerList = new EventListenerList();
// protected transient ChangeEvent changeEvent;
#Override public boolean shouldSelectCell(EventObject anEvent) {
return true;
}
#Override public boolean stopCellEditing() {
fireEditingStopped();
return true;
}
#Override public void cancelCellEditing() {
fireEditingCanceled();
}
#Override public void addCellEditorListener(CellEditorListener l) {
listenerList.add(CellEditorListener.class, l);
}
#Override public void removeCellEditorListener(CellEditorListener l) {
listenerList.remove(CellEditorListener.class, l);
}
public CellEditorListener[] getCellEditorListeners() {
return listenerList.getListeners(CellEditorListener.class);
}
protected void fireEditingStopped() {
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == CellEditorListener.class) {
// Lazily create the event:
if (changeEvent == null) {
changeEvent = new ChangeEvent(this);
}
((CellEditorListener) listeners[i + 1]).editingStopped(changeEvent);
}
}
}
protected void fireEditingCanceled() {
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == CellEditorListener.class) {
// Lazily create the event:
if (changeEvent == null) {
changeEvent = new ChangeEvent(this);
}
((CellEditorListener) listeners[i + 1]).editingCanceled(changeEvent);
}
}
}
}
class CheckBoxStatusUpdateListener implements TreeModelListener {
private boolean adjusting;
#Override public void treeNodesChanged(TreeModelEvent e) {
if (adjusting) {
return;
}
adjusting = true;
Object[] children = e.getChildren();
DefaultTreeModel model = (DefaultTreeModel) e.getSource();
DefaultMutableTreeNode node;
CheckBoxNode c; // = (CheckBoxNode) node.getUserObject();
if (children != null && children.length == 1) {
node = (DefaultMutableTreeNode) children[0];
c = (CheckBoxNode) node.getUserObject();
TreePath parent = e.getTreePath();
DefaultMutableTreeNode n = (DefaultMutableTreeNode) parent.getLastPathComponent();
while (n != null) {
updateParentUserObject(n);
DefaultMutableTreeNode tmp = (DefaultMutableTreeNode) n.getParent();
if (tmp == null) {
break;
} else {
n = tmp;
}
}
model.nodeChanged(n);
} else {
node = (DefaultMutableTreeNode) model.getRoot();
c = (CheckBoxNode) node.getUserObject();
}
updateAllChildrenUserObject(node, c.status);
model.nodeChanged(node);
adjusting = false;
}
private void updateParentUserObject(DefaultMutableTreeNode parent) {
Object userObject = parent.getUserObject();
if (userObject instanceof CheckBoxNode) {
File file = ((CheckBoxNode) userObject).file;
int selectedCount = 0;
int indeterminateCount = 0;
Enumeration children = parent.children();
while (children.hasMoreElements()) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) children.nextElement();
CheckBoxNode check = (CheckBoxNode) node.getUserObject();
if (check.status == Status.INDETERMINATE) {
indeterminateCount++;
break;
}
if (check.status == Status.SELECTED) {
selectedCount++;
}
}
if (indeterminateCount > 0) {
parent.setUserObject(new CheckBoxNode(file));
} else if (selectedCount == 0) {
parent.setUserObject(new CheckBoxNode(file, Status.DESELECTED));
} else if (selectedCount == parent.getChildCount()) {
parent.setUserObject(new CheckBoxNode(file, Status.SELECTED));
} else {
parent.setUserObject(new CheckBoxNode(file));
}
}
}
private void updateAllChildrenUserObject(DefaultMutableTreeNode root, Status status) {
Enumeration breadth = root.breadthFirstEnumeration();
while (breadth.hasMoreElements()) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) breadth.nextElement();
if (root.equals(node)) {
continue;
}
CheckBoxNode check = (CheckBoxNode) node.getUserObject();
node.setUserObject(new CheckBoxNode(check.file, status));
}
}
#Override public void treeNodesInserted(TreeModelEvent e) { /* not needed */ }
#Override public void treeNodesRemoved(TreeModelEvent e) { /* not needed */ }
#Override public void treeStructureChanged(TreeModelEvent e) { /* not needed */ }
}
Any idea to change its directory ?
Use a File instead of FileSystemView to define the root.
Use File#listFiles to list the files within a specified directory instead of FileSystemView#getFiles
Something like...
File rootPath = new File(".");
DefaultMutableTreeNode root = new DefaultMutableTreeNode();
final DefaultTreeModel treeModel = new DefaultTreeModel(root);
DefaultMutableTreeNode node = new DefaultMutableTreeNode(new CheckBoxNode(rootPath, Status.DESELECTED));
root.add(node);
for (File file : rootPath.listFiles()) {
if (file.isDirectory()) {
node.add(new DefaultMutableTreeNode(new CheckBoxNode(file, Status.DESELECTED)));
}
}
for example.
Take a look at java.io.File for more details
I want to create a tree with given nodes and children. The required behavior is as follows:
"when a node's checkbox is toggled, it's new value (checked/unchecked) should be reflected to all of it's descendants"
Following is the full code, which is not giving expected behavior:
public class CheckBoxTree {
Map<String, DefaultMutableTreeNode> nodes = new HashMap<String, DefaultMutableTreeNode>();
public static void main(final String args[]) {
final CheckBoxTree cbt = new CheckBoxTree();
DefaultMutableTreeNode root = cbt.addNodeWithoutCheckbox(null, "Select divisions");
DefaultMutableTreeNode nodeSTD1 = cbt.addNodeWithCheckbox(root, "STD1", false);
cbt.nodes.put("STD1", nodeSTD1);
DefaultMutableTreeNode nodeDiv1 = cbt.addNodeWithCheckbox(nodeSTD1, "DIV1", false);
cbt.nodes.put("DIV1", nodeDiv1);
DefaultMutableTreeNode nodeDiv2 = cbt.addNodeWithCheckbox(nodeSTD1, "DIV2", false);
cbt.nodes.put("DIV2", nodeDiv2);
root.add(nodeSTD1);
DefaultMutableTreeNode nodeSTD2 = cbt.addNodeWithCheckbox(root, "STD2", false);
cbt.nodes.put("STD2", nodeSTD2);
DefaultMutableTreeNode nodeDiv3 = cbt.addNodeWithCheckbox(nodeSTD2, "DIV3", false);
cbt.nodes.put("DIV3", nodeDiv3);
DefaultMutableTreeNode nodeDiv4 = cbt.addNodeWithCheckbox(nodeSTD2, "DIV4", false);
cbt.nodes.put("DIV4", nodeDiv4);
root.add(nodeSTD2);
final JTree tree = cbt.createCheckBoxTree(root);
// show the tree onscreen
final JFrame frame = new JFrame("CheckBox Tree");
JPanel panel = new JPanel();
panel.setLayout(new BorderLayout());
panel.add(tree, BorderLayout.CENTER);
JButton button = new JButton("Submit");
panel.add(button, BorderLayout.SOUTH);
final JScrollPane scrollPane = new JScrollPane(panel);
frame.getContentPane().add(scrollPane, BorderLayout.CENTER);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300, 150);
frame.setVisible(true);
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
/*TreePath[] selectionPaths = tree.getSelectionModel().getSelectionPaths();
// TreePath[] selectionPaths = tree.getSelectionPaths();
if (selectionPaths != null) {
System.out.println("Selected paths:");
for (TreePath tp : selectionPaths) {
System.out.println(tp);
}
}*/
System.out.println("Selected paths:");
for (DefaultMutableTreeNode n : cbt.nodes.values()) {
Object obj = n.getUserObject();
if (obj instanceof CheckBoxNode) {
if (((CheckBoxNode) obj).isSelected()) {
System.out.println(n.toString());
}
}
}
}
});
}
private JTree createCheckBoxTree(final DefaultMutableTreeNode root) {
final DefaultTreeModel treeModel = new DefaultTreeModel(root);
final JTree tree = new JTree(treeModel);
final CheckBoxNodeRenderer renderer = new CheckBoxNodeRenderer();
tree.setCellRenderer(renderer);
final CheckBoxNodeEditor editor = new CheckBoxNodeEditor(tree);
tree.setCellEditor(editor);
tree.setEditable(true);
// listen for changes in the model (including check box toggles)
treeModel.addTreeModelListener(new TreeModelListener() {
#Override
public void treeNodesChanged(final TreeModelEvent e) {
System.out.println(System.currentTimeMillis() + ": nodes changed");
DefaultMutableTreeNode node =
(DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
if (node == null) {
return;
}
changeSubTreeSelections(node, null);
}
#Override
public void treeNodesInserted(final TreeModelEvent e) {
System.out.println(System.currentTimeMillis() + ": nodes inserted");
}
#Override
public void treeNodesRemoved(final TreeModelEvent e) {
System.out.println(System.currentTimeMillis() + ": nodes removed");
}
#Override
public void treeStructureChanged(final TreeModelEvent e) {
System.out.println(System.currentTimeMillis() + ": structure changed");
}
});
// listen for changes in the selection
tree.addTreeSelectionListener(new TreeSelectionListener() {
#Override
public void valueChanged(TreeSelectionEvent arg0) {
System.out.println(System.currentTimeMillis() + ": value changed");
/*DefaultMutableTreeNode node =
(DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
if (node == null) {
return;
}
changeSubTreeSelections(node, null);*/
}
});
return tree;
}
protected void changeSubTreeSelections(DefaultMutableTreeNode node, Boolean selected) {
/*change all subtree selection if applicable*/
Object obj = node.getUserObject();
Boolean isSelected = selected;
if (obj instanceof CheckBoxNode) {
CheckBoxNode cbn = (CheckBoxNode) obj;
if (isSelected == null) {
isSelected = cbn.isSelected();
} else {
cbn.setSelected(isSelected);
}
} else {
return;
}
int count = node.getChildCount();
for (int i = 0; i < count; i++) {
DefaultMutableTreeNode child = (DefaultMutableTreeNode) node.getChildAt(i);
changeSubTreeSelections(child, isSelected);
}
}
public DefaultMutableTreeNode addNodeWithCheckbox(
final DefaultMutableTreeNode parent, final String text, final boolean checked) {
final CheckBoxNode data = new CheckBoxNode(text, checked);
final DefaultMutableTreeNode node = new DefaultMutableTreeNode(data);
if (parent != null) {
parent.add(node);
}
return node;
}
public DefaultMutableTreeNode addNodeWithoutCheckbox(
final DefaultMutableTreeNode parent, final String text) {
final DefaultMutableTreeNode node = new DefaultMutableTreeNode(text);
if (parent != null) {
parent.add(node);
}
return node;
}
/*private static DefaultMutableTreeNode add(
final DefaultMutableTreeNode parent, final String text, final boolean checked) {
final CheckBoxNode data = new CheckBoxNode(text, checked);
final DefaultMutableTreeNode node = new DefaultMutableTreeNode(data);
parent.add(node);
nodes.put(text, node);
return node;
}*/
}
class CheckBoxNodeRenderer implements TreeCellRenderer {
private JCheckBox checkBoxNodeRenderer = new JCheckBox();
private DefaultTreeCellRenderer nonCheckBoxNodeRenderer = new DefaultTreeCellRenderer();
private Color selectionBorderColor, selectionForegroundColor, selectionBackgroundColor,
textForegroundColor, textBackgroundColor;
protected JCheckBox getLeafCheckBox() {
return checkBoxNodeRenderer;
}
public CheckBoxNodeRenderer() {
Font fontValue;
fontValue = UIManager.getFont("Tree.font");
if (fontValue != null) {
checkBoxNodeRenderer.setFont(fontValue);
}
Boolean booleanValue = (Boolean) UIManager.get("Tree.drawsFocusBorderAroundIcon");
checkBoxNodeRenderer.setFocusPainted((booleanValue != null)
&& (booleanValue.booleanValue()));
selectionBorderColor = UIManager.getColor("Tree.selectionBorderColor");
selectionForegroundColor = UIManager.getColor("Tree.selectionForeground");
selectionBackgroundColor = UIManager.getColor("Tree.selectionBackground");
textForegroundColor = UIManager.getColor("Tree.textForeground");
textBackgroundColor = UIManager.getColor("Tree.textBackground");
}
#Override
public Component getTreeCellRendererComponent(
JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row,
boolean hasFocus) {
Component returnValue;
/*check if it is checkbox object*/
boolean isCheckboxObject = false;
if ((value != null) && (value instanceof DefaultMutableTreeNode)) {
Object userObject = ((DefaultMutableTreeNode) value).getUserObject();
if (userObject instanceof CheckBoxNode) {
isCheckboxObject = true;
CheckBoxNode node = (CheckBoxNode) userObject;
checkBoxNodeRenderer.setText(node.getText());
checkBoxNodeRenderer.setSelected(node.isSelected());
}
} else {
String stringValue =
tree.convertValueToText(value, selected, expanded, leaf, row, false);
checkBoxNodeRenderer.setText(stringValue);
checkBoxNodeRenderer.setSelected(false);
}
if (isCheckboxObject) {
checkBoxNodeRenderer.setEnabled(tree.isEnabled());
if (selected) {
checkBoxNodeRenderer.setForeground(selectionForegroundColor);
checkBoxNodeRenderer.setBackground(selectionBackgroundColor);
} else {
checkBoxNodeRenderer.setForeground(textForegroundColor);
checkBoxNodeRenderer.setBackground(textBackgroundColor);
}
returnValue = checkBoxNodeRenderer;
} else {
returnValue =
nonCheckBoxNodeRenderer.getTreeCellRendererComponent(tree, value, selected,
expanded, leaf, row, hasFocus);
}
return returnValue;
}
}
class CheckBoxNodeEditor extends AbstractCellEditor implements TreeCellEditor {
private static final long serialVersionUID = 1L;
private CheckBoxNodeRenderer renderer = new CheckBoxNodeRenderer();
private ChangeEvent changeEvent = null;
private JTree tree;
public CheckBoxNodeEditor(JTree tree) {
this.tree = tree;
}
#Override
public Object getCellEditorValue() {
JCheckBox checkbox = renderer.getLeafCheckBox();
CheckBoxNode checkBoxNode = new CheckBoxNode(checkbox.getText(), checkbox.isSelected());
return checkBoxNode;
}
#Override
public boolean isCellEditable(EventObject event) {
/*uncomment following code to make all nodes editable*/
// return true;
// make all nodes, which are instance of CheckBoxNode, editable
boolean returnValue = false;
if (event instanceof MouseEvent) {
MouseEvent mouseEvent = (MouseEvent) event;
TreePath path = tree.getPathForLocation(mouseEvent.getX(), mouseEvent.getY());
if (path != null) {
Object node = path.getLastPathComponent();
if ((node != null) && (node instanceof DefaultMutableTreeNode)) {
DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode) node;
Object userObject = treeNode.getUserObject();
returnValue = (userObject instanceof CheckBoxNode);
// returnValue = ((treeNode.isLeaf()) && (userObject
// instanceof CheckBoxNode));
}
}
}
return returnValue;
}
#Override
public Component getTreeCellEditorComponent(
JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row) {
Component editor =
renderer.getTreeCellRendererComponent(tree, value, false, expanded, leaf, row, true);
// editor always selected / focused
ItemListener itemListener = new ItemListener() {
public void itemStateChanged(ItemEvent itemEvent) {
if (stopCellEditing()) {
fireEditingStopped();
}
}
};
if (editor instanceof JCheckBox) {
((JCheckBox) editor).addItemListener(itemListener);
}
return editor;
}
}
class CheckBoxNode {
private String text;
private boolean selected;
public CheckBoxNode(String text, boolean selected) {
this.text = text;
this.selected = selected;
}
public boolean isSelected() {
return selected;
}
public void setSelected(boolean newValue) {
selected = newValue;
}
public String getText() {
return text;
}
public void setText(String newValue) {
text = newValue;
}
/*#Override
public String toString() {
return getClass().getName() + "[" + text + "/" + selected + "]";
}*/
#Override
public String toString() {
return text;
}
}
class NamedVector extends Vector<Object> {
private static final long serialVersionUID = 1L;
private String name;
public NamedVector(String name) {
this.name = name;
}
public NamedVector(String name, Object elements[]) {
this.name = name;
for (int i = 0, n = elements.length; i < n; i++) {
add(elements[i]);
}
}
#Override
public String toString() {
return "[" + name + "]";
}
}
The toggling of any parent nodes' checkbox not reflecting correctly in children nodes.
Please help me to understand where the problem is.
add treeDidChange to repaint the tree.
#Override
public void treeNodesChanged(final TreeModelEvent e) {
System.out.println(System.currentTimeMillis() + ": nodes changed");
DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
if (node == null) {
return;
}
changeSubTreeSelections(node, null);
tree.treeDidChange();
}
What's more, I found that in the function changeSubTreeSelections, this line isSelected = !(cbn.isSelected()); is incorrect. You shouldn't use ! to get an opposite value.
And maybe it's better that you expand the whole descendants' path, if the parent node is selected.
I've managed to create a JList with support of selecting multiple elements and dragging them to reorder the list. But I was wondering how to group some elements together with a handle. Something a lot like this:
Since even figuring out the drag&drop mechanics was so challenging to me, I'm hesitated to add this group feature. Is it even possible with JLists? What topic should I search to be able to do somthing like this?
Use a JTree instead. Using a list is trying to force a square peg (JList) into a round hole (JTree).
See How to Use Trees for details and working examples.
Here is my JTree after learning about JTrees from Andrew Thompson. I found a JTree example from Craig Wood and tweaked it to suit my needs. It is still not 100% complete. It doesn't have any comments or explanations sorry for that, I'm just sharing my source code.
public class TreeDragAndDrop {
final JTree tree = new JTree();
DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
DefaultMutableTreeNode root = (DefaultMutableTreeNode) model.getRoot();
private JScrollPane getContent() {
tree.setDragEnabled(true);
tree.setDropMode(DropMode.ON_OR_INSERT);
tree.getSelectionModel().setSelectionMode(TreeSelectionModel.CONTIGUOUS_TREE_SELECTION);
//expandTree(tree);
tree.setEditable(true);
tree.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "startEditing");
tree.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0), "startEditing");
model.reload();
return new JScrollPane(tree);
}
private void expandTree(JTree tree) {
DefaultMutableTreeNode root = (DefaultMutableTreeNode) tree.getModel().getRoot();
java.util.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(1200, 200);
f.setVisible(true);
}
}
class TreeTransferHandler2 extends TransferHandler {
DataFlavor nodesFlavor;
DataFlavor[] flavors = new DataFlavor[1];
DefaultMutableTreeNode[] nodesToRemove;
private void renameNodes(JTree tree) {
DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
DefaultMutableTreeNode root = (DefaultMutableTreeNode) model.getRoot();
java.util.Enumeration en = root.depthFirstEnumeration();
while (en.hasMoreElements()) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) en.nextElement();
String nodeName = node.toString();
int strLength = node.toString().length();
if (nodeName.charAt(strLength - 1) == ')') {
nodeName = nodeName.substring(0, (nodeName.indexOf("(")) - 1);
}
if (node.getChildCount() > 0) {
node.setUserObject(nodeName + " (" + node.getChildCount() + ")");
}
}
}
public TreeTransferHandler2() {
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 < selRows.length; i++) {
if (selRows[i] == dropRow) {
return false;
}
}
// Do not allow MOVE-action drops if a non-leaf node is
// selected unless all of its children are also selected.
int action = support.getDropAction();
if (action == MOVE) {
return haveCompleteNode(tree);
}
// Do not allow a non-leaf node to be copied to a level
// which is less than its source level.
TreePath dest = dl.getPath();
DefaultMutableTreeNode target = (DefaultMutableTreeNode) dest.getLastPathComponent();
TreePath path = tree.getPathForRow(selRows[0]);
DefaultMutableTreeNode firstNode = (DefaultMutableTreeNode) path.getLastPathComponent();
if (firstNode.getChildCount() > 0 && target.getLevel() < firstNode.getLevel()) {
return false;
}
return true;
}
private boolean haveCompleteNode(JTree tree) {
int[] selRows = tree.getSelectionRows();
TreePath path = tree.getPathForRow(selRows[0]);
DefaultMutableTreeNode first = (DefaultMutableTreeNode) path.getLastPathComponent();
int childCount = first.getChildCount();
// first has children and no children are selected.
if (childCount > 0 && selRows.length == 1) {
return true;
}
// first may have children.
for (int i = 1; i < selRows.length; i++) {
path = tree.getPathForRow(selRows[i]);
DefaultMutableTreeNode next = (DefaultMutableTreeNode) path.getLastPathComponent();
if (first.isNodeChild(next)) {
// Found a child of first.
if (childCount > 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<DefaultMutableTreeNode> copies = new ArrayList<DefaultMutableTreeNode>();
List<DefaultMutableTreeNode> toRemove = new ArrayList<DefaultMutableTreeNode>();
DefaultMutableTreeNode node = (DefaultMutableTreeNode) paths[0].getLastPathComponent();
int childCount = node.getChildCount();
DefaultMutableTreeNode copy = null;
if (node.getChildCount() > 0 && tree.getSelectionRows().length == 1) {
copy = copy(node);
copies.add(copy);
for (int k = 0; k < childCount; k++) {
copy.add((MutableTreeNode) node.getChildAt(0));
}
toRemove.add(node);
} else {
for (int i = 0; i < paths.length; i++) {
DefaultMutableTreeNode next = (DefaultMutableTreeNode) paths[i].getLastPathComponent();
// 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, true);
}
protected void exportDone(JComponent source, Transferable data, int action) {
if ((action & MOVE) == MOVE) {
JTree tree = (JTree) source;
DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
int[] ints = new int[nodesToRemove.length];
DefaultMutableTreeNode parent = (DefaultMutableTreeNode) nodesToRemove[0].getParent();
// Remove nodes saved in nodesToRemove in createTransferable.
for (int i = 0; i < nodesToRemove.length; i++) {
model.removeNodeFromParent(nodesToRemove[i]);
}
}
}
public int getSourceActions(JComponent c) {
return MOVE;
}
public boolean importData(TransferHandler.TransferSupport support) {
if (!canImport(support)) {
return false;
}
// Extract transfer data.
DefaultMutableTreeNode[] nodes = null;
try {
Transferable t = support.getTransferable();
nodes = (DefaultMutableTreeNode[]) t.getTransferData(nodesFlavor);
} catch (UnsupportedFlavorException ufe) {
System.out.println("UnsupportedFlavor: " + ufe.getMessage());
} catch (java.io.IOException ioe) {
System.out.println("I/O error: " + ioe.getMessage());
}
// Get drop location info.
JTree.DropLocation dl = (JTree.DropLocation) support.getDropLocation();
int childIndex = dl.getChildIndex();
TreePath dest = dl.getPath();
DefaultMutableTreeNode parent = (DefaultMutableTreeNode) dest.getLastPathComponent();
DefaultMutableTreeNode superParent = null;
int parentIndex = 0;
if (parent.isLeaf()) {
superParent = (DefaultMutableTreeNode) parent.getParent();
parentIndex = superParent.getIndex(parent);
}
JTree tree = (JTree) support.getComponent();
DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
// Configure for drop mode.
int index = childIndex; // DropMode.INSERT
if (childIndex == -1) { // DropMode.ON
index = 0;
if (parent.getChildCount() == 0) {
DefaultMutableTreeNode newParent = new DefaultMutableTreeNode("New Group", true);
for (int i = 0; i < nodes.length; i++) {
model.insertNodeInto(copy(nodes[i]), newParent, i);
//model.removeNodeFromParent(nodes[i]);
}
model.insertNodeInto(copy(parent), newParent, newParent.getChildCount());
model.removeNodeFromParent(parent);
model.insertNodeInto(newParent, superParent, parentIndex);
model.reload();
} else {
for (int i = 0; i < nodes.length; i++) {
model.insertNodeInto(nodes[i], parent, parent.getChildCount());
}
}
} else {
// Add data to model.
for (int i = 0; i < nodes.length; i++) {
model.insertNodeInto(nodes[i], parent, index++);
}
}
return true;
}
public String toString() {
return getClass().getName();
}
public class NodesTransferable implements Transferable {
DefaultMutableTreeNode[] nodes;
public NodesTransferable(DefaultMutableTreeNode[] nodes) {
this.nodes = nodes;
}
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException {
if (!isDataFlavorSupported(flavor)) {
throw new UnsupportedFlavorException(flavor);
}
return nodes;
}
public DataFlavor[] getTransferDataFlavors() {
return flavors;
}
public boolean isDataFlavorSupported(DataFlavor flavor) {
return nodesFlavor.equals(flavor);
}
}
}
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);
}
}