复选框树未按预期工作

时间:2014-03-09 11:00:20

标签: java swing checkbox treeview

我想创建一个包含给定节点和子节点的树。所需的行为如下:

“当切换节点的复选框时,它的新值(选中/取消选中)应该反映到所有的后代”

以下是完整代码,它没有给出预期的行为:

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

切换任何父节点的复选框在子节点中无法正确反映。

请帮助我了解问题所在。

1 个答案:

答案 0 :(得分:1)

添加treeDidChange以重绘树。

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

更重要的是,我发现在函数changeSubTreeSelections中,此行isSelected = !(cbn.isSelected());不正确。您不应该使用!来获得相反的值。

如果选择父节点,也许最好扩展整个后代的路径。