在 JToggleButton 中保存选择

Saving selection in JToggleButton

对于我一直从事的项目,我必须创建一个 JTree,每行有一个 JToggleButton,实际上每行两个。因此,为了使这项工作正常进行,正如您将在 MCVE 中看到的那样,我编写了一个自定义 DefaultTreeCellRenderer 和一个自定义 AbstractCellEditor.

但是,无论 JToggleBox 是否被 select 编辑,都存在问题。最初,当您 select 更改第一个值时,它工作正常。如果您停留在 JTree 的那一行并且不点击除 JToggleButton 以外的其他任何地方,它就可以正常工作。如果您单击别处,您设置的值将丢失。

JToggleButton的设置值如何在点击其他地方时保留?

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);
        }
    }
}

在我看来,您的问题出在 "model" 上:您正在为节点使用普通的 DefaultMutableTreeNode 对象,class 无法保存最重要的数据 --你的按钮的状态。

而是考虑

  1. 使用从 DefaultMutableTreeNode 扩展的 class,它有两个布尔值,渲染器将使用这些值来告诉按钮显示什么状态。
  2. 或者将 DefaultMutableTreeNode 用作 "wrapper",方法是将一个包含文本的对象以及两个布尔字段(JToggleButton 用来显示其状态的字段)传入 DefaultMutableTreeNode。