JamesB
JamesB

Reputation: 529

JTree Node as Checkbox is unclickable

I'm currently facing a challenge with my JTree implementation, and I'm seeking to find a solution. The JTree displays a hierarchical structure with nodes and checkboxes, and I want to make the root node selectable as a Checkbox. Checkboxes in the nodes are selectable.

Tried this implementation, and it shows the root as a checkbox but it remains unclickable. I've tried adjusting the renderer and editor classes, but so far, without success. Tried to write a Minimum Reproducible Version, but it is still quite lengthy due to the TreeCellRenderer implementation:

enter image description here

MainFrame.java:

package test;

import javax.swing.*;
import java.awt.*;
import java.util.*;
import java.util.List;
import javax.swing.tree.*;

public class MinimalReproducibleExample{

    private JFrame frame;
    private JTree tree;

    public MainFrame() {
        initialize();
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(() -> {
            try {
                MainFrame window = new MainFrame();
                window.frame.setVisible(true);
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }

    private void initialize() {
        frame = new JFrame();
        frame.setBounds(100, 100, 400, 400);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().setLayout(new BorderLayout());

        DefaultMutableTreeNode root = new DefaultMutableTreeNode("Root");

        DefaultMutableTreeNode folder1 = new DefaultMutableTreeNode("Folder 1");
        DefaultMutableTreeNode folder2 = new DefaultMutableTreeNode("Folder 2");

        DefaultMutableTreeNode file1 = new DefaultMutableTreeNode("File 1");
        DefaultMutableTreeNode file2 = new DefaultMutableTreeNode("File 2");
        DefaultMutableTreeNode file3 = new DefaultMutableTreeNode("File 3");

        root.add(folder1);
        root.add(folder2);

        folder1.add(file1);
        folder2.add(file2);
        folder2.add(file3);

        Map<String, List<String>> packagesAndFiles = new HashMap<>();
        List<String> nodes = new ArrayList<>();
        Enumeration<?> enumeration = root.breadthFirstEnumeration();
        while (enumeration.hasMoreElements()) {
            DefaultMutableTreeNode node = (DefaultMutableTreeNode) enumeration.nextElement();
            if (node.isLeaf()) {
                nodes.add(node.toString());
            }
        }
        packagesAndFiles.put("Root", nodes);

        tree = new CustomCheckBoxTreeSample().createCustomCheckBoxTree(packagesAndFiles);
        JScrollPane scrollPane = new JScrollPane(tree);
        frame.getContentPane().add(scrollPane, BorderLayout.CENTER);
    }
}

CustomCheckBoxNodeEditor.java:

package test;

import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreeCellEditor;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreePath;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.util.EventObject;
import java.util.List;
import java.util.Map;
import java.util.Vector;

public class CustomCheckBoxTreeSample {

    public CustomCheckBoxTreeSample() {

    }

    public JTree createCustomCheckBoxTree(Map<String, List<String>> packagesAndFiles) {

        List<Vector<Object>> vectors = new Vector<>();

        for (Map.Entry<String, List<String>> entry : packagesAndFiles.entrySet()) {
            String packageName = entry.getKey();
            packageName = packageName.substring(1);

            System.out.println("Package: " + packageName);
            List<String> fileNames = entry.getValue();

            CustomCheckBoxNode checkBoxNodes[] = new CustomCheckBoxNode[fileNames.size()];

            for (int i = 0; i < fileNames.size(); i++) {
                checkBoxNodes[i] = new CustomCheckBoxNode(fileNames.get(i), true);
            }

            Vector<Object> packageVector = new NamedVector(packageName, checkBoxNodes);

            vectors.add(packageVector);
        }

        CustomCheckBoxNodeRenderer renderer = new CustomCheckBoxNodeRenderer();

        Object rootNodes[] = new Object[packagesAndFiles.size()];

        for (int i = 0; i < vectors.size(); i++) {
            rootNodes[i] = vectors.get(i);
        }

        Vector<Object> rootVector = new NamedVector("Root", rootNodes);

        JTree tree = new JTree(rootVector);

        tree.setVisibleRowCount(50);
        tree.setRootVisible(false);
        tree.setShowsRootHandles(true);

        tree.setCellRenderer(renderer);
        tree.setCellEditor(new CustomCheckBoxNodeEditor(tree));
        tree.setEditable(true);

        return tree;
    }
}

class CustomCheckBoxNodeRenderer implements TreeCellRenderer {

    private JCheckBox leafRenderer = new JCheckBox();
    private Color selectionForeground, selectionBackground, textForeground, textBackground;

    protected JCheckBox getLeafRenderer() {
        return leafRenderer;
    }

    public CustomCheckBoxNodeRenderer() {
        Font fontValue = UIManager.getFont("Tree.font");
        if (fontValue != null) {
            leafRenderer.setFont(fontValue);
        }
        Boolean booleanValue = (Boolean) UIManager.get("Tree.drawsFocusBorderAroundIcon");
        leafRenderer.setFocusPainted((booleanValue != null) && (booleanValue.booleanValue()));
        UIManager.getColor("Tree.selectionBorderColor");
        selectionForeground = UIManager.getColor("Tree.selectionForeground");
        selectionBackground = UIManager.getColor("Tree.selectionBackground");
        textForeground = UIManager.getColor("Tree.textForeground");
        textBackground = UIManager.getColor("Tree.textBackground");
    }

    @Override
    public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded,
                                                  boolean leaf, int row, boolean hasFocus) {

        Component returnValue;
        String stringValue = tree.convertValueToText(value, selected, expanded, leaf, row, false);
        leafRenderer.setText(stringValue);
        leafRenderer.setSelected(false);
        leafRenderer.setEnabled(tree.isEnabled());
        if (selected) {
            leafRenderer.setForeground(selectionForeground);
            leafRenderer.setBackground(selectionBackground);
        } else {
            leafRenderer.setForeground(textForeground);
            leafRenderer.setBackground(textBackground);
        }
        if ((value != null) && (value instanceof DefaultMutableTreeNode)) {
            Object userObject = ((DefaultMutableTreeNode) value).getUserObject();
            if (userObject instanceof CustomCheckBoxNode) {
                CustomCheckBoxNode node = (CustomCheckBoxNode) userObject;
                leafRenderer.setText(node.getText());
                leafRenderer.setSelected(node.isSelected());
            }
        }
        returnValue = leafRenderer;

        return returnValue;
    }
}

class CustomCheckBoxNodeEditor extends AbstractCellEditor implements TreeCellEditor {

    private static final long serialVersionUID = 1L;
    private CustomCheckBoxNodeRenderer renderer = new CustomCheckBoxNodeRenderer();
    private JTree tree;

    public CustomCheckBoxNodeEditor(JTree tree) {
        this.tree = tree;
    }

    @Override
    public Object getCellEditorValue() {
        JCheckBox checkbox = renderer.getLeafRenderer();
        CustomCheckBoxNode customCheckBoxNode = new CustomCheckBoxNode(checkbox.getText(), checkbox.isSelected());
        return customCheckBoxNode;
    }

    @Override
    public boolean isCellEditable(EventObject event) {
        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 = ((treeNode.isLeaf()) && (userObject instanceof CustomCheckBoxNode));
                }
            }
        }
        return returnValue;
    }

    @Override
    public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row) {
        return renderer.getTreeCellRendererComponent(tree, value, true, expanded, leaf, row, true);
    }
}

class CustomCheckBoxNode {

    private String text;
    private boolean selected;

    public CustomCheckBoxNode(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 + "]";
    }
}

class NamedVector extends Vector<Object> {

    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 + "]";
    }
}

Upvotes: 0

Views: 59

Answers (0)

Related Questions