Swing JTree 复选框行为问题

问题描述 投票:0回答:1

在使用 JTree 中的复选框时,我的代码面临这个问题。根节点在第一次迭代中被视为“命名向量”而不是“CheckBoxNode”,因此,它不会迭代其子复选框。

在第二次迭代中,根节点变成“CheckBoxNode”,导致所有子复选框都被选中,但它们应该已经被选中,而应该被取消选中:

第二次迭代和第二次单击:

结果:

JanelaPrincipal.java:

package br.josueborges.principal;

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

public class JanelaPrincipal {

    private JFrame frame;
    private JTree tree;

    public JanelaPrincipal() {
        initialize();
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(() -> {
            try {
                JanelaPrincipal window = new JanelaPrincipal();
                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>> pacotesEArquivos = 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());
            }
        }
        pacotesEArquivos.put("Root", nodes);

        tree = new CheckBoxNodeTreeSample().retornaJTree(pacotesEArquivos);
        JScrollPane scrollPane = new JScrollPane(tree);
        frame.getContentPane().add(scrollPane, BorderLayout.CENTER);
    }
}

CheckBoxNode.java:

package br.josueborges.principal;

import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseEvent;
import java.util.EventObject;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import java.util.*;

import javax.swing.AbstractCellEditor;
import javax.swing.JCheckBox;
import javax.swing.JTree;
import javax.swing.UIManager;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeCellEditor;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreePath;

public class CheckBoxNodeTreeSample {

    public CheckBoxNodeTreeSample() {

    }

    public JTree retornaJTree(Map<String, List<String>> pacotesEArquivos) {

        ArrayList<Vector<Object>> vetores = new ArrayList<Vector<Object>>();

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

            System.out.println("Pacote: " + pacote);
            List<String> nomesArquivos = entry.getValue();

            CheckBoxNode checkBoxNode[] = new CheckBoxNode[nomesArquivos.size()];

            // String nomeArquivo : nomesArquivos
            for (int i = 0; i < nomesArquivos.size(); i++) {
                checkBoxNode[i] = new CheckBoxNode(nomesArquivos.get(i), false);
            }

            Vector<Object> vetorDoJTree = new NamedVector(pacote, checkBoxNode);

            vetores.add(vetorDoJTree);
        }

        CheckBoxNodeRenderer renderer = new CheckBoxNodeRenderer();

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

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

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

        JTree tree = new JTree(rootVector);

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

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

        return tree;
    }
}

class CheckBoxNodeRenderer implements TreeCellRenderer {

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

    protected JCheckBox getLeafRenderer() {
        return leafRenderer;
    }

    public CheckBoxNodeRenderer() {
        Font fontValue;
        fontValue = UIManager.getFont("Tree.font");
        if (fontValue != null) {
            leafRenderer.setFont(fontValue);
        }
        Boolean booleanValue = (Boolean) UIManager.get("Tree.drawsFocusBorderAroundIcon");
        System.out.println("booleanValue: " + booleanValue);
        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 CheckBoxNode) {
                CheckBoxNode node = (CheckBoxNode) userObject;
                leafRenderer.setText(node.getText());
                leafRenderer.setSelected(node.isSelected());
            }
        }
        returnValue = leafRenderer;
        
        return returnValue;
    }
}

class CheckBoxNodeEditor extends AbstractCellEditor implements TreeCellEditor {

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

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

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

    @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 = true;
                }
            }
        }
        return returnValue;
    }

    @Override
    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() {
            @Override
            public void itemStateChanged(ItemEvent itemEvent) {
                if (stopCellEditing()) {
                    fireEditingStopped();
                }
            }
        };
        if (editor instanceof JCheckBox) {
            JCheckBox checkBox = (JCheckBox) editor;
            checkBox.removeItemListener(itemListener);
            checkBox.addItemListener(itemListener);

            // Ao clicar no checkbox, atualizar o estado de todos os checkboxes filhos e pais
            DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
            Object userObject = node.getUserObject();
            if (userObject instanceof CheckBoxNode) {
                
                CheckBoxNode parentNode = (CheckBoxNode) userObject;
                boolean parentSelected = checkBox.isSelected();

                // Percorrer os nós filhos (arquivos) e definir o mesmo estado do checkbox pai
                for (int i = 0; i < node.getChildCount(); i++) {
                    DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) node.getChildAt(i);
                    Object childUserObject = childNode.getUserObject();
                    if (childUserObject instanceof CheckBoxNode) {
                        CheckBoxNode childCheckBoxNode = (CheckBoxNode) childUserObject;
                        childCheckBoxNode.setSelected(parentSelected);
                    }
                }

                // Atualizar o estado dos nós pais
                updateParentNodes(node, parentSelected);

                // Notificar a árvore que houve mudança no modelo
                ((DefaultTreeModel) tree.getModel()).nodeStructureChanged(node);
          }
        }
        return editor;
    }

// Método para atualizar o estado dos nós pais
    private void updateParentNodes(DefaultMutableTreeNode node, boolean selected) {
        DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) node.getParent();
        if (parentNode != null) {
            Object parentUserObject = parentNode.getUserObject();
            if (parentUserObject instanceof CheckBoxNode) {
                CheckBoxNode parentCheckBoxNode = (CheckBoxNode) parentUserObject;

                // Verificar se todos os filhos estão selecionados e atualizar o estado do nó
                // pai
                boolean allChildrenSelected = true;
                for (int i = 0; i < parentNode.getChildCount(); i++) {
                    DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) parentNode.getChildAt(i);
                    Object childUserObject = childNode.getUserObject();
                    if (childUserObject instanceof CheckBoxNode) {
                        CheckBoxNode childCheckBoxNode = (CheckBoxNode) childUserObject;
                        if (!childCheckBoxNode.isSelected()) {
                            allChildrenSelected = false;
                            break;
                        }
                    }
                }

                parentCheckBoxNode.setSelected(allChildrenSelected);

                // Continuar atualizando os nós pais recursivamente
                updateParentNodes(parentNode, selected);
            }
        }
    }
}

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

class NamedVector extends Vector<Object> {

    private static final long serialVersionUID = 1L;
    private String name;

    public NamedVector(String name) {
        super(); 
        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 + "]";
    }
}
java swing awt jtree treecellrenderer
1个回答
1
投票

我不会尝试修复您的代码,它朝着您似乎想要实现的目标的错误方向发展,相反,我修改了此示例以添加对在父节点状态更改时更新子节点的支持。

import java.awt.Component;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractCellEditor;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.UIManager;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeCellEditor;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;

public class Main {

    public static void main(String[] args) {
        new Main();
    }

    public Main() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JTree tree = createTree();
                tree.setToggleClickCount(0);
                tree.setCellRenderer(new StateRenderer());
                tree.setCellEditor(new StateEditor());
                tree.setEditable(true);

                JFrame f = new JFrame();
                f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                f.add(new JScrollPane(tree));

                f.pack();
                f.setLocationRelativeTo(null);
                f.setVisible(true);
            }
        });
    }

    private JTree createTree() {
        int children = 4;
        int grandChildren = 2;
        DefaultMutableTreeNode root = new DefaultMutableTreeNode(new State("Films", false));
        DefaultMutableTreeNode node;

        String[] cat = {"Sci-Fi", "Fantasy", "Action", "Comedy"};
        String[][] films = {
            {"Star Wars", "Star Trek"},
            {"Lord of the Rings", "Conan"},
            {"Terminator", "Transformers"},
            {"Cheaper by the Doze", "Father of the Bride"}
        };
        for (int j = 0; j < children; j++) {
            node = new DefaultMutableTreeNode(new State(cat[j], false));
            root.add(node);
            for (int k = 0; k < grandChildren; k++) {
                node.add(new DefaultMutableTreeNode(new State(films[j][k], false)));
            }
        }
        TestModel model = new TestModel(root);
        return new JTree(model);
    }

    public class State {

        private String text;
        private boolean selected;

        public State(String text, boolean selected) {
            this.text = text;
            this.selected = selected;
        }

        public String getText() {
            return text;
        }

        public boolean isSelected() {
            return selected;
        }

        public void setSelected(boolean selected) {
            this.selected = selected;
        }

    }

    public class StateEditor extends AbstractCellEditor implements TreeCellEditor {

        //JPanel panel;
        private JCheckBox checkBox;

        private State editorValue;

        public StateEditor() {
            checkBox = new JCheckBox();
            checkBox.setOpaque(false);
            // This seems to be important :/
            checkBox.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    stopCellEditing();
                }
            });
        }

        @Override
        public Object getCellEditorValue() {
            editorValue.setSelected(checkBox.isSelected());
            return editorValue;
        }

        @Override
        public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row) {
            if (value instanceof DefaultMutableTreeNode) {
                DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
                State state = (State) node.getUserObject();
                editorValue = state;
                checkBox.setText(state.getText());
                checkBox.setSelected(state.isSelected());
            } else {
                checkBox.setText("??");
                checkBox.setSelected(false);
            }

            return checkBox;

        }

    }

    public class StateRenderer implements TreeCellRenderer {

        private JCheckBox checkBox;

        public StateRenderer() {
            checkBox = new JCheckBox();
            checkBox.setOpaque(false);
        }

        @Override
        public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
            if (value instanceof DefaultMutableTreeNode) {
                DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
                State state = (State) node.getUserObject();
                checkBox.setText(state.getText());
                checkBox.setSelected(state.isSelected());
            } else {
                checkBox.setText("??");
                checkBox.setSelected(false);
            }

            if (selected) {
                checkBox.setBackground(UIManager.getColor("Tree.selectionBackground"));
                checkBox.setForeground(UIManager.getColor("Tree.selectionForeground"));
            } else {
                checkBox.setForeground(tree.getForeground());
                checkBox.setBackground(null);
            }

            checkBox.setOpaque(selected);

            return checkBox;
        }
    }

    public class TestModel extends DefaultTreeModel {
        public TestModel(TreeNode root) {
            super(root);
        }

        @Override
        public void valueForPathChanged(TreePath path, Object newValue) {
            // This is important, as it fires the nodeChanged event
            super.valueForPathChanged(path, newValue);
            Object lastPathComponent = path.getLastPathComponent();
            if (!(lastPathComponent instanceof DefaultMutableTreeNode)) {
                return;
            }
            DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) lastPathComponent;
            if (parentNode.isLeaf()) {
                return;
            }

            State state = stateFrom(parentNode);
            StateChangeModel model = new StateChangeModel();
            updateState(parentNode, state, model);

            fireTreeNodesChanged(this, model.getPaths(), model.getIndicies(), model.getChildren());
        }

        protected State stateFrom(TreeNode node) {
            if (!(node instanceof DefaultMutableTreeNode)) {
                return null;
            }
            DefaultMutableTreeNode defaultNode = (DefaultMutableTreeNode) node;
            Object userObject = defaultNode.getUserObject();
            if (!(userObject instanceof State)) {
                // Here, if you're clever, you could check the
                // state of all the nodes of this node's parent
                // and if they're all selected, mark the parent
                // nodes as selected as well
                return null;
            }
            return (State) userObject;
        }

        protected void updateState(DefaultMutableTreeNode parentNode, State state, StateChangeModel model) {
            for (int index = 0; index < parentNode.getChildCount(); index++) {
                TreeNode childNode = parentNode.getChildAt(index);
                if (!(childNode instanceof DefaultMutableTreeNode)) {
                    continue;
                }
                DefaultMutableTreeNode defaultChildNode = (DefaultMutableTreeNode) childNode;
                Object childObject = defaultChildNode.getUserObject();
                if (!(childObject instanceof State)) {
                    return;
                }
                State childState = (State) childObject;
                childState.setSelected(state.isSelected());

                model.add(new TreePath(getPathToRoot(childNode)), index, childNode);
                // Recursive update...
                updateState(defaultChildNode, state, model);
            }
        }

        protected class StateChangeModel {
            List<TreePath> paths = new ArrayList<>(8);
            List<Integer> indicies = new ArrayList<>(8);
            List<Object> children = new ArrayList<>(8);            

            public void add(TreePath path, int index, TreeNode child) {
                paths.add(path);
                indicies.add(index);
                children.add(child);
            }

            public TreePath[] getPaths() {
                return paths.toArray(new TreePath[paths.size()]);
            }

            public int[] getIndicies() {
                return indicies.stream().mapToInt(i -> i).toArray();
            }

            public Object[] getChildren() {
                return children.toArray(new Object[children.size()]);
            }
        }
    }
}

现在,重要的是要注意几件事......

  1. 当状态改变时,在
    stopCellEditing
    上调用
    TreeCellEditor
    非常重要,否则这将不起作用。我不知道为什么,但这就是它需要的工作方式。
  2. 我使用了自定义
    DefaultTreeModel
    并重写了
    valueForPathChanged
    方法。这非常重要,因为这是模型收到编辑器更改节点状态的通知的方式。否则你将需要花费大量时间在
    TreeModelListener
    上并花费大量时间编写大量脏代码。
  3. 您当前的方向/实现对于您似乎正在尝试做的事情来说存在根本缺陷,我建议花一些时间研究上面的示例并尝试将您的代码移至其中。
© www.soinside.com 2019 - 2024. All rights reserved.