Search code examples
javaswingrenderingjtreejcheckbox

JTree rendering with JCheckBox nodes


I am attempting to modify the standard Swing JTree to intermingle nodes with and without checkboxes. This is an example:

alt text

When I attempt to check/uncheck one of the checkboxes (the 'User 01' node in this example), the tree loses nodes:

alt text

I my code is an adaptation of this example: http://forums.sun.com/thread.jspa?threadID=5321084&start=13.

Instead of embedding a JCheckBox in a DefaultMutableTreeNode like this:

new DefaultMutableTreeNode(new CheckBoxNode("Accessibility", true));

I thought it made more sense to create a model node that derived from the DefaultMutableTreeNode, which I call JTreeNode. This class automatically sets the DefaultMutableTreeNode's UserObject to a JCheckBox. The class' ShowCheckBox property is used by the TreeCellRenderer to determine if the JCheckBox or DefaultTreeCellRenderer is used. The JTreeNode is used like this:

    JTreeNode user01 = new JTreeNode("User 01");
    user01.setShowCheckBox(true);
    user01.setSelected(true);

I believe the problem is with the class that implements the TreeCellEditor, specifically in the getCellEditorValue() or getTreeCellEditorComponent() methods. I suspect the issue has something to with the getCellEditorValue() returning a derivative of the DefaultMutableTreeNode, rather than a simpler model instance.

public Object getCellEditorValue() {

    JCheckBox checkBox = renderer.getCheckBoxRenderer();

    JTreeNode node = new JTreeNode(checkBox.getText());
    node.setShowCheckBox(true);
    node.setSelected(checkBox.isSelected());
    return node;

}

public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row) {

    Component editor = renderer.getTreeCellRendererComponent(tree, value, true, 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;

}

Here is the TreeCellRender's getTreeCellRendererComponent() method:

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

    Component component;

    //if object being passed for rendering is a JTreeNode that should show a JCheckBox, attempt to render it so
    if (((JTreeNode) value).getShowCheckBox()) {

        String stringValue = tree.convertValueToText(value, selected, expanded, leaf, row, false);

        //set default JCheckBox rendering
        checkBoxRenderer.setText(stringValue);
        checkBoxRenderer.setSelected(false);    //not checked
        checkBoxRenderer.setEnabled(tree.isEnabled());

        if (selected) {
            //removed colorization
            //checkBoxRenderer.setForeground(selectionForeground);
            //checkBoxRenderer.setBackground(selectionBackground);
        }
        else {
            checkBoxRenderer.setForeground(textForeground);
            checkBoxRenderer.setBackground(textBackground);
        }

        //DefaultMutableTreeNode
        if ((value != null) && (value instanceof JTreeNode)) {

            //userObject should be a JTreeNode instance
            //DefaultMutableTreeNode
            //Object userObject = ((JTreeNode) value).getUserObject();

            //if it is
            //if (userObject instanceof JTreeNode) {
                //cast as a JTreeNode
                //JTreeNode node = (JTreeNode) userObject;
                JTreeNode node = (JTreeNode) value;

                //set JCheckBox settings to match JTreeNode's settings
                checkBoxRenderer.setText(node.getText());
                checkBoxRenderer.setSelected(node.isSelected());

            //}

        }

        component = checkBoxRenderer;

    }

    //if not, render the default
    else {

        component = defaultRenderer.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);

    }

    return component;

}

Any thoughts are greatly appreciated.


Solution

  • I was able to solve the problem.

    alt text

    I created a model class (TreeNodeModel) to hold the relevant node data: key, value, hasCheckBox, isSelected:

    public class TreeNodeModel {
    
        int key;
        String value;
        boolean isSelected=false;
        boolean hasCheckBox=false;
    
        public TreeNodeModel() {
        }
        public TreeNodeModel(int key, String value) {
            this.key=key;
            this.value = value;
        }
        public TreeNodeModel(int key, String value, boolean hasCheckBox) {
            this.key=key;
            this.value = value;
            this.hasCheckBox = hasCheckBox;
        }
        public TreeNodeModel(int key, String value, boolean hasCheckBox, boolean isSelected) {
            this.key=key;
            this.value = value;
            this.hasCheckBox=hasCheckBox;
            this.isSelected = isSelected;
        }
    
        public boolean isSelected() {
            return this.isSelected;
        }
        public void setSelected(boolean newValue) {
            this.isSelected = newValue;
        }
    
        public boolean hasCheckBox() {
            return this.hasCheckBox;
        }
        public void setCheckBox(boolean newValue) {
            this.hasCheckBox=newValue;
        }
    
        public int getKey() {
            return this.key;
        }
        public void setKey(int newValue) {
            this.key = newValue;
        }
    
        public String getValue() {
            return this.value;
        }
        public void setValue(String newValue) {
            this.value = newValue;
        }
    
        @Override
        public String toString() {
            return getClass().getName() + "[" + this.value + "/" + this.isSelected + "]";
    //        return this.text;
        }
    
    }
    

    I created an implementation of the TreeCellEditor interface:

    public class TreeNodeEditor  extends AbstractCellEditor implements TreeCellEditor {
    
        private JTree tree;
        private TreeNodeModel editedModel = null;
    
        TreeNodeRenderer renderer = new TreeNodeRenderer();
    
        public TreeNodeEditor(JTree tree) {
            this.tree=tree;
        }
    
        @Override
        public boolean isCellEditable(EventObject event) {
    
            boolean editable=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 editedNode = (DefaultMutableTreeNode) node;
                        TreeNodeModel model = (TreeNodeModel) editedNode.getUserObject();
                        editable = model.hasCheckBox;
    
                    }   //if (node)
                }   //if (path)
            }   //if (MouseEvent)
    
            return editable;
    
        }
    
        public Object getCellEditorValue() {
    
            JCheckBox checkbox = renderer.getCheckBoxRenderer();
    
            TreeNodeModel model = new TreeNodeModel(editedModel.getKey(), checkbox.getText(), true, checkbox.isSelected());
            return model;
    
        }
    
        public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row) {
    
            if (((DefaultMutableTreeNode) value).getUserObject() instanceof TreeNodeModel) {
                editedModel = (TreeNodeModel) ((DefaultMutableTreeNode) value).getUserObject();
            }
    
            Component editor = renderer.getTreeCellRendererComponent(tree, value, true, 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;
        }
    
    }
    

    The key was capturing the model in the getTreeCellEditorComponent() method and using its Key in the getCellEditorValue() method.

    Building the tree was easy:

    DefaultMutableTreeNode server = new DefaultMutableTreeNode(new TreeNodeModel(0,"Server 01", false));
    
    DefaultMutableTreeNode userFolder = new DefaultMutableTreeNode(new TreeNodeModel(1, "User Folders", false));
    server.add(userFolder);
    
    DefaultMutableTreeNode user01 =  new DefaultMutableTreeNode(new TreeNodeModel(2, "User 01", true, true));
    userFolder.add(user01);
    
    DefaultMutableTreeNode clientA = new DefaultMutableTreeNode(new TreeNodeModel(3, "Client A", true, true));
    user01.add(clientA);
    
    DefaultMutableTreeNode clientB = new DefaultMutableTreeNode(new TreeNodeModel(4, "Client B", true, true));
    user01.add(clientB);
    
    DefaultMutableTreeNode publicFolder = new DefaultMutableTreeNode(new TreeNodeModel(5, "Public Folders", false));
    server.add(publicFolder);
    
    DefaultMutableTreeNode clientC = new DefaultMutableTreeNode(new TreeNodeModel(6, "Client C", true));
    publicFolder.add(clientC);
            Tree_Nodes.setCellRenderer(new TreeNodeRenderer());
            Tree_Nodes.setCellEditor(new TreeNodeEditor(Tree_Nodes));
    Tree_Nodes.setModel(new DefaultTreeModel(server);
    

    Finally, determining which nodes where checked was a matter of examining the model's node collection (via a button):

    private Map<Integer, String> checked = new HashMap<Integer, String>();
    
    private void Button_CheckedActionPerformed(java.awt.event.ActionEvent evt) {
    
        DefaultTableModel tableModel = ((DefaultTableModel) Table_Nodes.getModel());
        tableModel.getDataVector().removeAllElements();
        tableModel.fireTableDataChanged();
    
        checked.clear();
    
        DefaultTreeModel treeModel = (DefaultTreeModel) Tree_Nodes.getModel();
        DefaultMutableTreeNode root = (DefaultMutableTreeNode) treeModel.getRoot();
    
        getChildNodes(root);
    
        for (Iterator it=checked.entrySet().iterator(); it.hasNext(); ) {
            Map.Entry entry = (Map.Entry)it.next();
            tableModel.addRow(new Object[] {entry.getKey(), entry.getValue()});
        }
    
        Button_Checked.requestFocus();
    
    
    }
    
    private void getChildNodes(DefaultMutableTreeNode parentNode) {
    
        for (Enumeration e=parentNode.children(); e.hasMoreElements();) {
    
            DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) e.nextElement();
            TreeNodeModel model = (TreeNodeModel) childNode.getUserObject();
    
            if (model.hasCheckBox && model.isSelected()) {
                checked.put(model.getKey(), model.getValue());
            }
    
            //recurse
            if (childNode.getChildCount()>0) getChildNodes(childNode);
    
        }
    
    }