Search code examples
javaswingeditorrendererjtogglebutton

Saving selection in JToggleButton


For a project I have been working on, I have had to create a JTree with a JToggleButton on each row, well two on each row actually. So to make this work, as you will see in the MCVE, I wrote a custom DefaultTreeCellRenderer and a custom AbstractCellEditor.

However, there is a problem with editing whether the JToggleBox is selected or not. Initially, when you select the first value to change, it works fine. And if you stay on that row of the JTree and don't click anywhere else but on the JToggleButton it works fine. If you click elsewhere the values you set are lost.

How can the set values of the JToggleButton be kept when somewhere else is clicked?

MCVE

import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseEvent;
import java.util.EventObject;

import javax.swing.AbstractCellEditor;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JToggleButton;
import javax.swing.JTree;
import javax.swing.event.ChangeEvent;
import javax.swing.plaf.metal.MetalToggleButtonUI;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreeCellEditor;
import javax.swing.tree.TreePath;

@SuppressWarnings("serial")
public class DirectoryExplorer extends JFrame {
    private DirectoryExplorer() {
        super("Directory Explorer");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLayout(new GridLayout(1, 1));
        createPanel();
        setSize(800,600);
        setVisible(true);
    }

    private void createPanel() {
        DefaultMutableTreeNode colors = new DefaultMutableTreeNode("Colours");
        colors.add(new DefaultMutableTreeNode("Red"));
        colors.add(new DefaultMutableTreeNode("Green"));
        colors.add(new DefaultMutableTreeNode("Blue"));

        DefaultMutableTreeNode falvors = new DefaultMutableTreeNode("Flavours");
        falvors.add(new DefaultMutableTreeNode("Toffee"));
        falvors.add(new DefaultMutableTreeNode("Fudge"));
        falvors.add(new DefaultMutableTreeNode("Chocolate"));

        DefaultMutableTreeNode root = new DefaultMutableTreeNode("root");
        root.add(colors);
        root.add(falvors);
        root.add(new DefaultMutableTreeNode("Leafy"));

        JPanel panel = new JPanel(new GridLayout(1, 1));
        JTree tree = new JTree(root);

        FileNameRenderer fileRender = new FileNameRenderer();
        tree.setCellRenderer(fileRender);

        tree.setCellEditor(new CheckBoxNodeEditor(tree));
        tree.setEditable(true);

        tree.setShowsRootHandles(true);

        panel.add(new JScrollPane(tree));
        getContentPane().add(panel);
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(() -> new DirectoryExplorer());
    }

    private class FileNameRenderer extends DefaultTreeCellRenderer {
        @Override
        public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
            JComponent c = (JComponent) super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);

            return new Holder((JLabel) c);
        }
    }

    private class CheckBoxNodeEditor extends AbstractCellEditor implements TreeCellEditor {
        FileNameRenderer renderer = new FileNameRenderer();
        ChangeEvent changeEvent = null;
        JTree t;
        TreePath path;

        public CheckBoxNodeEditor(JTree tree) {
            t = tree;
        }

        public Object getCellEditorValue() {
            Holder checkBoxNode = new Holder(new JLabel((((DefaultMutableTreeNode) path.getLastPathComponent()).getUserObject().toString())));
            return checkBoxNode;
        }

        public boolean isCellEditable(EventObject event) {
            if(event instanceof MouseEvent) {
                MouseEvent mouseEvent = (MouseEvent) event;
                path = t.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();
                        return (userObject instanceof String);
                    }
                }
            }
            return false;
        }

        public Component getTreeCellEditorComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row) {
            Component editor = renderer.getTreeCellRendererComponent(tree, value, true, expanded, leaf, row, true);
            ItemListener itemListener = new ItemListener() {
                public void itemStateChanged(ItemEvent itemEvent) {
                    if (stopCellEditing())
                        fireEditingStopped();
                }
            };

            if (editor instanceof JToggleButton) {
                ((JToggleButton) editor).addItemListener(itemListener);
            }

            return editor;
        }
    }

    private class Holder extends JPanel {
        public Holder(Component c) {
            setLayout(new GridBagLayout());
            setBackground(Color.BLACK);
            setOpaque(false);
            addComponents(c);
        }

        private void addComponents(Component c) {
            GridBagConstraints gBC = new GridBagConstraints();

            gBC.insets = new Insets(0, 0, 0, 5);
            add(c, gBC);

            gBC.insets = new Insets(0, 0, 0, 0);
            add(new DefaultChkBx(), gBC);
            add(new FavouriteChkBx(), gBC);
        }
    }

    private class DefaultChkBx extends JToggleButton {
        public DefaultChkBx() {
            setUI(new MetalToggleButtonUI() {
                @Override
                protected Color getSelectColor() {
                    return new Color(242, 0, 255);
                }
            });
            setBorder(null);
            setForeground(Color.GRAY);
            setText("Default");
            setFocusPainted(false);
        }
    }

    private class FavouriteChkBx extends JToggleButton {
        public FavouriteChkBx() {
            setUI(new MetalToggleButtonUI() {
                @Override
                protected Color getSelectColor() {
                    return Color.RED;
                }
            });
            setBorder(null);
            setForeground(Color.GRAY);
            setText("Favourite");
            setFocusPainted(false);
        }
    }
}

Solution

  • It seems to me that your problem is with your "model": You're using plain vanilla DefaultMutableTreeNode objects for your nodes, a class that is unable to save the most important data -- the state of your buttons.

    Instead consider

    1. using a class that extends from DefaultMutableTreeNode, one that has two booleans, values that will be used by the renderers to tell the buttons what state to display.
    2. Or use your DefaultMutableTreeNode as a "wrapper" by passing into your DefaultMutableTreeNode an object that holds text as well as two boolean fields, fields that the JToggleButtons use to display their state.