Java Swing: Need a good quality developed JTree with checkboxes

前端 未结 3 1868
面向向阳花
面向向阳花 2020-12-03 13:33

I was looking for a a JTree implementation, that contains checkboxes and which:

  • When you select one node, all its successors in the tree are automatically s

3条回答
  •  星月不相逢
    2020-12-03 14:37

    Answering myself:

    I decided to share my code with everyone.

    Here's a screenshot of the result:

    Screenshot

    The implementation details:

    • Created a new class that extends JTree

    • Replaced the 'TreeCellRenderer' by a new class I created, that shows a checkbox and a label. The checkbox selection is changed instead of the label background and border.

    • Totally terminated the selection mechanism. Replaced the 'Selection Model' by a 'DefaultTreeSelectionModel' overridden inline, that has empty implementation

      • Created new event type for checking of the checkboxes

      • Created special data structures that help to indicate fast the state of each node

    Enjoy!!

    Here's a usage example:

    public class Main extends JFrame {
    
        private static final long serialVersionUID = 4648172894076113183L;
    
        public Main() {
            super();
            setSize(500, 500);
            this.getContentPane().setLayout(new BorderLayout());
            final JCheckBoxTree cbt = new JCheckBoxTree();
            this.getContentPane().add(cbt);
            cbt.addCheckChangeEventListener(new JCheckBoxTree.CheckChangeEventListener() {
                public void checkStateChanged(JCheckBoxTree.CheckChangeEvent event) {
                    System.out.println("event");
                    TreePath[] paths = cbt.getCheckedPaths();
                    for (TreePath tp : paths) {
                        for (Object pathPart : tp.getPath()) {
                            System.out.print(pathPart + ",");
                        }                   
                        System.out.println();
                    }
                }           
            });         
            this.setDefaultCloseOperation(EXIT_ON_CLOSE);
        }
    
        public static void main(String args[]) {
            Main m = new Main();
            m.setVisible(true);
        }
    }
    

    Here is the source code of the class itself:

    import java.awt.BorderLayout;
    import java.awt.Component;
    import java.awt.event.MouseEvent;
    import java.awt.event.MouseListener;
    import java.util.EventListener;
    import java.util.EventObject;
    import java.util.HashMap;
    import java.util.HashSet;
    
    import javax.swing.JCheckBox;
    import javax.swing.JPanel;
    import javax.swing.JTree;
    import javax.swing.event.EventListenerList;
    import javax.swing.tree.DefaultMutableTreeNode;
    import javax.swing.tree.DefaultTreeSelectionModel;
    import javax.swing.tree.TreeCellRenderer;
    import javax.swing.tree.TreeModel;
    import javax.swing.tree.TreeNode;
    import javax.swing.tree.TreePath;
    
    public class JCheckBoxTree extends JTree {
    
        private static final long serialVersionUID = -4194122328392241790L;
    
        JCheckBoxTree selfPointer = this;
    
    
    
        // Defining data structure that will enable to fast check-indicate the state of each node
        // It totally replaces the "selection" mechanism of the JTree
        private class CheckedNode {
            boolean isSelected;
            boolean hasChildren;
            boolean allChildrenSelected;
    
            public CheckedNode(boolean isSelected_, boolean hasChildren_, boolean allChildrenSelected_) {
                isSelected = isSelected_;
                hasChildren = hasChildren_;
                allChildrenSelected = allChildrenSelected_;
            }
        }
        HashMap nodesCheckingState;
        HashSet checkedPaths = new HashSet();
    
        // Defining a new event type for the checking mechanism and preparing event-handling mechanism
        protected EventListenerList listenerList = new EventListenerList();
    
        public class CheckChangeEvent extends EventObject {     
            private static final long serialVersionUID = -8100230309044193368L;
    
            public CheckChangeEvent(Object source) {
                super(source);          
            }       
        }   
    
        public interface CheckChangeEventListener extends EventListener {
            public void checkStateChanged(CheckChangeEvent event);
        }
    
        public void addCheckChangeEventListener(CheckChangeEventListener listener) {
            listenerList.add(CheckChangeEventListener.class, listener);
        }
        public void removeCheckChangeEventListener(CheckChangeEventListener listener) {
            listenerList.remove(CheckChangeEventListener.class, listener);
        }
    
        void fireCheckChangeEvent(CheckChangeEvent evt) {
            Object[] listeners = listenerList.getListenerList();
            for (int i = 0; i < listeners.length; i++) {
                if (listeners[i] == CheckChangeEventListener.class) {
                    ((CheckChangeEventListener) listeners[i + 1]).checkStateChanged(evt);
                }
            }
        }
    
        // Override
        public void setModel(TreeModel newModel) {
            super.setModel(newModel);
            resetCheckingState();
        }
    
        // New method that returns only the checked paths (totally ignores original "selection" mechanism)
        public TreePath[] getCheckedPaths() {
            return checkedPaths.toArray(new TreePath[checkedPaths.size()]);
        }
    
        // Returns true in case that the node is selected, has children but not all of them are selected
        public boolean isSelectedPartially(TreePath path) {
            CheckedNode cn = nodesCheckingState.get(path);
            return cn.isSelected && cn.hasChildren && !cn.allChildrenSelected;
        }
    
        private void resetCheckingState() { 
            nodesCheckingState = new HashMap();
            checkedPaths = new HashSet();
            DefaultMutableTreeNode node = (DefaultMutableTreeNode)getModel().getRoot();
            if (node == null) {
                return;
            }
            addSubtreeToCheckingStateTracking(node);
        }
    
        // Creating data structure of the current model for the checking mechanism
        private void addSubtreeToCheckingStateTracking(DefaultMutableTreeNode node) {
            TreeNode[] path = node.getPath();   
            TreePath tp = new TreePath(path);
            CheckedNode cn = new CheckedNode(false, node.getChildCount() > 0, false);
            nodesCheckingState.put(tp, cn);
            for (int i = 0 ; i < node.getChildCount() ; i++) {              
                addSubtreeToCheckingStateTracking((DefaultMutableTreeNode) tp.pathByAddingChild(node.getChildAt(i)).getLastPathComponent());
            }
        }
    
        // Overriding cell renderer by a class that ignores the original "selection" mechanism
        // It decides how to show the nodes due to the checking-mechanism
        private class CheckBoxCellRenderer extends JPanel implements TreeCellRenderer {     
            private static final long serialVersionUID = -7341833835878991719L;     
            JCheckBox checkBox;     
            public CheckBoxCellRenderer() {
                super();
                this.setLayout(new BorderLayout());
                checkBox = new JCheckBox();
                add(checkBox, BorderLayout.CENTER);
                setOpaque(false);
            }
    
            @Override
            public Component getTreeCellRendererComponent(JTree tree, Object value,
                    boolean selected, boolean expanded, boolean leaf, int row,
                    boolean hasFocus) {
                DefaultMutableTreeNode node = (DefaultMutableTreeNode)value;
                Object obj = node.getUserObject();          
                TreePath tp = new TreePath(node.getPath());
                CheckedNode cn = nodesCheckingState.get(tp);
                if (cn == null) {
                    return this;
                }
                checkBox.setSelected(cn.isSelected);
                checkBox.setText(obj.toString());
                checkBox.setOpaque(cn.isSelected && cn.hasChildren && ! cn.allChildrenSelected);
                return this;
            }       
        }
    
        public JCheckBoxTree() {
            super();
            // Disabling toggling by double-click
            this.setToggleClickCount(0);
            // Overriding cell renderer by new one defined above
            CheckBoxCellRenderer cellRenderer = new CheckBoxCellRenderer();
            this.setCellRenderer(cellRenderer);
    
            // Overriding selection model by an empty one
            DefaultTreeSelectionModel dtsm = new DefaultTreeSelectionModel() {      
                private static final long serialVersionUID = -8190634240451667286L;
                // Totally disabling the selection mechanism
                public void setSelectionPath(TreePath path) {
                }           
                public void addSelectionPath(TreePath path) {                       
                }           
                public void removeSelectionPath(TreePath path) {
                }
                public void setSelectionPaths(TreePath[] pPaths) {
                }
            };
            // Calling checking mechanism on mouse click
            this.addMouseListener(new MouseListener() {
                public void mouseClicked(MouseEvent arg0) {
                    TreePath tp = selfPointer.getPathForLocation(arg0.getX(), arg0.getY());
                    if (tp == null) {
                        return;
                    }
                    boolean checkMode = ! nodesCheckingState.get(tp).isSelected;
                    checkSubTree(tp, checkMode);
                    updatePredecessorsWithCheckMode(tp, checkMode);
                    // Firing the check change event
                    fireCheckChangeEvent(new CheckChangeEvent(new Object()));
                    // Repainting tree after the data structures were updated
                    selfPointer.repaint();                          
                }           
                public void mouseEntered(MouseEvent arg0) {         
                }           
                public void mouseExited(MouseEvent arg0) {              
                }
                public void mousePressed(MouseEvent arg0) {             
                }
                public void mouseReleased(MouseEvent arg0) {
                }           
            });
            this.setSelectionModel(dtsm);
        }
    
        // When a node is checked/unchecked, updating the states of the predecessors
        protected void updatePredecessorsWithCheckMode(TreePath tp, boolean check) {
            TreePath parentPath = tp.getParentPath();
            // If it is the root, stop the recursive calls and return
            if (parentPath == null) {
                return;
            }       
            CheckedNode parentCheckedNode = nodesCheckingState.get(parentPath);
            DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) parentPath.getLastPathComponent();     
            parentCheckedNode.allChildrenSelected = true;
            parentCheckedNode.isSelected = false;
            for (int i = 0 ; i < parentNode.getChildCount() ; i++) {                
                TreePath childPath = parentPath.pathByAddingChild(parentNode.getChildAt(i));
                CheckedNode childCheckedNode = nodesCheckingState.get(childPath);           
                // It is enough that even one subtree is not fully selected
                // to determine that the parent is not fully selected
                if (! childCheckedNode.allChildrenSelected) {
                    parentCheckedNode.allChildrenSelected = false;      
                }
                // If at least one child is selected, selecting also the parent
                if (childCheckedNode.isSelected) {
                    parentCheckedNode.isSelected = true;
                }
            }
            if (parentCheckedNode.isSelected) {
                checkedPaths.add(parentPath);
            } else {
                checkedPaths.remove(parentPath);
            }
            // Go to upper predecessor
            updatePredecessorsWithCheckMode(parentPath, check);
        }
    
        // Recursively checks/unchecks a subtree
        protected void checkSubTree(TreePath tp, boolean check) {
            CheckedNode cn = nodesCheckingState.get(tp);
            cn.isSelected = check;
            DefaultMutableTreeNode node = (DefaultMutableTreeNode) tp.getLastPathComponent();
            for (int i = 0 ; i < node.getChildCount() ; i++) {              
                checkSubTree(tp.pathByAddingChild(node.getChildAt(i)), check);
            }
            cn.allChildrenSelected = check;
            if (check) {
                checkedPaths.add(tp);
            } else {
                checkedPaths.remove(tp);
            }
        }
    
    }
    

提交回复
热议问题