Java Swing: нужно хорошее качество, разработанное JTree с флажками

Я искал реализацию JTree, которая содержит флажки и которая:

  • при выборе одного узла автоматически выбираются все его преемники в дереве

  • когда вы отменяете выбор одного узла, все его преемники в дереве автоматически отменяются

  • когда родительский узел уже выбран, и выбор был удален из одного из его преемников, цвет узла будет изменен на сделайте интуитивно понятным, что, хотя этот родительский узел выбран, не все его преемники выбраны (например, при выборе компонентов для установки в общих установщиках)

  • щелчок по узлу приводит к (нет необходимости удерживать клавишу "Ctrl"!):

    • Если узел уже выбран, он становится невыбранным со всеми его преемниками
    • Если узел не выбран, он становится выбранным, со всеми его преемники!--6-->

Я искал в сети что-то простое, но не мог найти что-то настолько простое, как мне хотелось.

кто-нибудь знает хорошую реализацию такого дерева?

3 ответов


отвечая себе:

я решила поделиться своим кодом со всеми.

вот скриншот результата:

Screenshot

детали реализации:

  • создан новый класс, который расширяет JTree

  • заменил "TreeCellRenderer" на новый класс, который я создал, который показывает флажок и метку. Выбор флажка изменяется вместо фона и границы метки.

  • полностью прекратил механизм выбора. Заменил "модель выбора" на "DefaultTreeSelectionModel", переопределенный inline, который имеет пустую реализацию

    • создан новый тип события для проверки чекбоксов

    • созданы специальные структуры данных, которые помогают быстро указывают состояние каждого узла

наслаждайтесь!!

вот использование пример:

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

вот исходный код самого класса:

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<TreePath, CheckedNode> nodesCheckingState;
    HashSet<TreePath> checkedPaths = new HashSet<TreePath>();

    // 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<TreePath, CheckedNode>();
        checkedPaths = new HashSet<TreePath>();
        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);
        }
    }

}

у меня была аналогичная потребность, но решение, размещенное здесь, было не совсем правильным (эффективным) для меня. Мне нужно было лениво построить модель check-box-tree, так как моя полная модель дерева может быть огромной (со многими тысячами узлов). Кроме того, сохранение набора всех checkedPaths было ценой, которая казалась чрезмерной и ненужной для моего набора данных и использования : все, что мне было нужно, это карта узлов, которые были фактически выбраны и отменены пользователем, поскольку это минималистская информация, из которой подчиненный узлы могут быть выведены.

поэтому я "улучшил" решение выше, чтобы быть ленивым в построении древовидной модели (только добавьте к модели, когда узел фактически расширен пользователем) и в отображении его (только установите состояние подчиненных узлов, когда пользователь фактически проверяет/отменяет проверку родительского узла).

Я также добавил к интерфейсу JCheckBoxTree, чтобы предоставить treeModel и expand-listener извне. Чтобы использовать это, вам нужно предоставить альтернативную реализацию части кода ниже комментарий "Ваш домен конкретный материал идет здесь", основанный на вашем собственном домене. TreeExpansionListener выполняет ленивую вставку узлов, используя метод расширения объекта домена.

package contrib.backup.checkboxtree;

import javax.swing.*;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.tree.*;
import java.awt.BorderLayout;
import java.awt.Component;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Random;

public class MyDomainCheckBoxTree extends JFrame {

    HashSet<TreePath> includedPaths = new HashSet<>();
    HashSet<TreePath> excludedPaths = new HashSet<>();
    TreeModel treeModel;

    public MyDomainCheckBoxTree(boolean testDefault) {
        super();
        setSize(500, 500);
        this.getContentPane().setLayout(new BorderLayout());

        final JCheckBoxTree cbt;
        if( testDefault ) {
            treeModel = null;
            cbt = new JCheckBoxTree();
        }
        else {
            treeModel = buildModel();
            LazyCheckBoxCellRenderer treeCellRenderer = new LazyCheckBoxCellRenderer();
            cbt = new JCheckBoxTree(treeModel, null, treeCellRenderer);
            treeCellRenderer.setCheckBoxTree(cbt);
            cbt.addTreeExpansionListener(new NodeExpansionListener());
        }

        JScrollPane s = new JScrollPane();
        s.getViewport().add(cbt);
        getContentPane().add(s, BorderLayout.CENTER);

        //this.getContentPane().add(cbt);

        cbt.addCheckChangeEventListener(new JCheckBoxTree.CheckChangeEventListener() {
            public void checkStateChanged(JCheckBoxTree.CheckChangeEvent event) {
                updatePaths(cbt, event);
                // For Debugging (correctness and laziness)
                System.out.println("\n========== Current State ========");
                System.out.println("+ + + Included Paths: ");
                printPaths(includedPaths);
                System.out.println("- - - Excluded Paths: ");
                printPaths(excludedPaths);
                System.out.println("Size of node-checkState cache = " + cbt.nodesCheckingState.size());
                //
            }
        });
        this.setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    // (As prelude)Purges any of clickedPath's children from the 2 path-sets.
    // Then adds/removes clickedPath from the 2 path-sets if appropriate.
    protected void updatePaths(JCheckBoxTree cbt,
                               JCheckBoxTree.CheckChangeEvent event){
        boolean  parentAlreadyIncluded = false;
        boolean  parentAlreadyExcluded = false;
        TreePath clickedPath = (TreePath) event.getSource();
        HashSet<TreePath>  toBeRemoved = new HashSet<>();

        //When a node is included/excluded, its children are implied as included/excluded.
        // Note: The direct-parent check is needed to avoid problem if immediate-parent is excluded
        // but grand-father/higher-ancestor is included
        for( TreePath exp : excludedPaths){
            if( clickedPath.isDescendant(exp) ) // exp is descended from clickedPath
                toBeRemoved.add(exp);
            if( isParent(exp, clickedPath)) // clickedPath is child of exp
                parentAlreadyExcluded = true;
        }
        excludedPaths.removeAll(toBeRemoved);
        toBeRemoved.clear();

        for( TreePath inp : includedPaths) {
            if(clickedPath.isDescendant(inp)) // inp is descended from clickedPath
                toBeRemoved.add(inp);
            if( isParent(inp, clickedPath)) // clickedPath is child of inp
                parentAlreadyIncluded = true;
        }
        includedPaths.removeAll(toBeRemoved);
        toBeRemoved.clear();

        // Now add/remove clickedPath from the path-sets as appropriate
        if( cbt.getCheckMode(clickedPath) ){ //selected => to be included
            if(!parentAlreadyIncluded)
                includedPaths.add(clickedPath);
            excludedPaths.remove(clickedPath);
        }else {    //deselected => to be excluded
            if( !parentAlreadyExcluded )
                excludedPaths.add(clickedPath);
            includedPaths.remove(clickedPath);
        }
    }

    // returns true if aPath is immediate parent of bPath; both must be non-null
    protected boolean isParent(TreePath aPath, TreePath bPath){
        return aPath.equals(bPath.getParentPath());
    }

    protected void printPaths(HashSet<TreePath> pathSet){
        TreePath[] paths = pathSet.toArray(new TreePath[pathSet.size()]);
        for (TreePath tp : paths) {
            for (Object pathPart : tp.getPath()) {
                System.out.print(pathPart + ",");
            }
            System.out.println();
        }
    }

    private class LazyCheckBoxCellRenderer extends JPanel implements TreeCellRenderer {
        JCheckBoxTree cbt;
        JCheckBox checkBox;

        public LazyCheckBoxCellRenderer() {
            super();
            this.setLayout(new BorderLayout());
            checkBox = new JCheckBox();
            add(checkBox, BorderLayout.CENTER);
            setOpaque(false);
        }

        public void setCheckBoxTree(JCheckBoxTree someCbt) { cbt = someCbt;}

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

            checkBox.setText(obj.toString());

            if (obj instanceof Boolean)
                checkBox.setText("Retrieving data...");
            else
            {
                TreePath tp = new TreePath(node.getPath());
                JCheckBoxTree.CheckedNode cn = null;
                if( cbt != null )
                    cn = cbt.getCheckedNode(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 static void main(String args[]) {
        boolean test = false;
        if( args.length > 0 && args[0].equalsIgnoreCase("test") )
            test = true;
        MyDomainCheckBoxTree m = new MyDomainCheckBoxTree(test);
        m.setVisible(true);
    }

    // Make sure expansion is threaded and updating the tree model
    // only occurs within the event dispatching thread.
    class NodeExpansionListener implements TreeExpansionListener
    {
        public void treeExpanded(TreeExpansionEvent event) {
            final DefaultMutableTreeNode node = JCheckBoxTree.getTreeNode(event.getPath());
            Object obj = node.getUserObject();

            //Expand by adding any children nodes
            Thread runner = new Thread() {
                public void run() {
                    if (obj != null && ((MyDomainObject)obj).expand(node)) {
                        Runnable runnable = new Runnable() {
                            public void run() {
                                ((DefaultTreeModel)treeModel).reload(node);
                            }
                        };
                        SwingUtilities.invokeLater(runnable);
                    }
                }
            };
            runner.start();
        }

        public void treeCollapsed(TreeExpansionEvent event) {}
    }

    //====================== Your Domain specific stuff goes here

    protected TreeModel buildModel() {
        DefaultMutableTreeNode topNode = new DefaultMutableTreeNode("Root");
        DefaultMutableTreeNode node;
        String[] categories = {"Product","Place","Critter"};
        for (String cat : categories) {
            MyDomainObject d = new MyDomainObject(cat);
            d.hasChildren = true;
            node = new DefaultMutableTreeNode(d);

            topNode.add(node);
            node.add( new DefaultMutableTreeNode(true));
        }

        return new DefaultTreeModel(topNode);
    }

    //sample impl of a domain-object; should have expand method
    class MyDomainObject {
        protected Object data;
        protected boolean  hasChildren;

        public MyDomainObject(Object obj) {
            data = obj;
            hasChildren = new Random().nextBoolean();
        }

        // Expand the tree at parent node and add nodes.
        public boolean expand(DefaultMutableTreeNode parent) {
            DefaultMutableTreeNode flagNode = (DefaultMutableTreeNode) parent.getFirstChild();
            if (flagNode == null)    // No flag
                return false;
            Object obj = flagNode.getUserObject();
            if (!(obj instanceof Boolean))
                return false;      // Already expanded

            parent.removeAllChildren();  // Remove FlagNode

            Object[] children = getChildren();
            if (children == null)
                return true;

            // Create a sorted list of domain-objects
            ArrayList sortedChildDomainObjects = new ArrayList();

            for (Object child : children) {
                MyDomainObject newNode = new MyDomainObject(child);
                //System.out.println("Size of arraylist=" + sortedChildDomainObjects.size());
                boolean isAdded = false;
                for (int i = 0; i < sortedChildDomainObjects.size(); i++) {
                    MyDomainObject nd = (MyDomainObject) sortedChildDomainObjects.get(i);
                    if (newNode.compareTo(nd) < 0) {
                        sortedChildDomainObjects.add(i, newNode);
                        isAdded = true;
                        break;
                    }
                }
                if (!isAdded)
                    sortedChildDomainObjects.add(newNode);
            }

            // Add children nodes under parent in the tree
            for (Object aChild : sortedChildDomainObjects) {
                MyDomainObject nd = (MyDomainObject) aChild;
                DefaultMutableTreeNode node = new DefaultMutableTreeNode(nd);
                parent.add(node);

                if (nd.hasChildren)
                    node.add(new DefaultMutableTreeNode(true));
            }

            return true;
        }

        private int compareTo(MyDomainObject toCompare) {
            assert toCompare.data != null;
            return data.toString().compareToIgnoreCase(toCompare.data.toString());
        }

        //should be Domain specific; dummy impl provided
        private Object[]  getChildren(){
            if( data == null || (!hasChildren))
                return null;

            Random   rand = new Random();

            Object[] children = new Object[rand.nextInt(20)];
            for( int i=0; i < children.length; i++){
                children[i] = data.toString() + "-" + rand.nextInt(1024); ;
            }
            return children;
        }

        public String toString() {
            return data != null ? data.toString() : "(EMPTY)";
        }
    }


}

моя измененная / ленивая версия JCheckBoxTree выглядит следующим образом:

package contrib.backup.checkboxtree;

import javax.swing.JCheckBox;
import javax.swing.JPanel;
import javax.swing.JTree;
import javax.swing.event.EventListenerList;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeWillExpandListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeSelectionModel;
import javax.swing.tree.ExpandVetoException;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
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;

public class JCheckBoxTree extends JTree {

    JCheckBoxTree selfPointer = this;

    // Data structure to quickly indicate the state of each node
    // It totally replaces the "selection" mechanism of the JTree
    protected class CheckedNode {
        boolean isSelected;
        boolean hasChildren;
        boolean allChildrenSelected;

        public CheckedNode(boolean isSelected_, boolean hasChildren_, boolean allChildrenSelected_) {
            isSelected = isSelected_;
            hasChildren = hasChildren_;
            allChildrenSelected = allChildrenSelected_;
        }
    }
    //CHANGED Data struct to hold nodes lazily (as they are expanded).REMOVED other data-struct
    HashMap<TreePath, CheckedNode> nodesCheckingState;

    // Defining a new event type for the checking mechanism and preparing event-handling mechanism
    protected EventListenerList listenerList = new EventListenerList();

    public class CheckChangeEvent extends EventObject {
        public CheckChangeEvent(Object source) {
            super(source);
        }
    }

    public interface CheckChangeEventListener extends EventListener {
        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();
    }

    // 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 = getCheckedNode(path);
        return cn.isSelected && cn.hasChildren && !cn.allChildrenSelected;
    }

    private void resetCheckingState() {
        nodesCheckingState = new HashMap<>();
        DefaultMutableTreeNode node = (DefaultMutableTreeNode)getModel().getRoot();
        if (node == null) {
            return;
        }
        addSubtreeToCheckingStateTracking(node);
    }

    // Builds up data structure for the checking mechanism. CHANGED to be lazy (do if expanded)
    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);
        if( isExpanded(tp) ) {
            for (int i = 0; i < node.getChildCount(); i++) {
                DefaultMutableTreeNode treeNode = getTreeNode(tp.pathByAddingChild(node.getChildAt(i)));
                addSubtreeToCheckingStateTracking(treeNode);
            }
        }
    }


    // 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 {
        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 = getCheckedNode(tp);
            if (cn == null) {
                return this;
            }
            checkBox.setSelected(cn.isSelected);
            checkBox.setText(obj.toString());
            checkBox.setOpaque(cn.isSelected && cn.hasChildren && ! cn.allChildrenSelected);
            return this;
        }
    }

    // CHANGED to simply delegate to others
    public JCheckBoxTree(){
        this(JTree.getDefaultTreeModel(), null, null);
    }

    // added NEW, arg can be passed null.
    public JCheckBoxTree(TreeModel treeModel){
        this(treeModel, null, null);
    }

    // CHANGED; with added params, any or all of which may be null
    public JCheckBoxTree(TreeModel treeModel,
                         TreeWillExpandListener tweListener,
                         TreeCellRenderer treeCellRenderer) {
        super(treeModel);

        // Disabling toggling by double-click
        this.setToggleClickCount(0);
        // Overriding cell renderer by new one defined above OR provided one
        if( treeCellRenderer == null )
            treeCellRenderer = new CheckBoxCellRenderer();
        //cellRenderer = treeCellRenderer;
        this.setCellRenderer(treeCellRenderer);

        // Overriding selection model by an empty one
        DefaultTreeSelectionModel dtsm = new DefaultTreeSelectionModel() {
            // 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 = ! getCheckMode(tp);
                checkSubTree(tp, checkMode, false); // func CHANGED for laziness

                updatePredecessorsWithCheckMode(tp);
                // Firing the check change event
                //fireCheckChangeEvent(new CheckChangeEvent(new Object())); //REPLACED by next-line
                fireCheckChangeEvent(new CheckChangeEvent(tp));
                // 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) {
            }
        });

        // added NEW for lazy action
        // Do the checkbox update just before the tree expands
        if( tweListener == null )
            tweListener =
                    new TreeWillExpandListener() {
                        @Override
                        public void treeWillExpand(TreeExpansionEvent event) throws ExpandVetoException {
                            TreePath expandingNodePath = event.getPath();
                            boolean checkMode = getCheckMode(expandingNodePath);
                            checkSubTree(expandingNodePath, checkMode, true);
                        }

                        @Override
                        public void treeWillCollapse(TreeExpansionEvent event) throws ExpandVetoException {
                        }
                    };

        this.addTreeWillExpandListener(tweListener);
        this.setSelectionModel(dtsm);
    }


    // added NEW
    public boolean getCheckMode( TreePath nodePath ){
        CheckedNode checkedNode = getCheckedNode(nodePath);
        return checkedNode.isSelected;
    }

    // added NEW.
    // Fetches checked-node if available or lazily add it with
    // checkMode inherited from 'nearest' ancestor.
    CheckedNode getCheckedNode(TreePath nodePath){
        CheckedNode checkedNode = nodesCheckingState.get(nodePath);
        if( checkedNode == null ){
            DefaultMutableTreeNode node = getTreeNode(nodePath);
            boolean ancestorCheckedMode = getAncestorCheckMode(nodePath);
            checkedNode = new CheckedNode(ancestorCheckedMode, node.getChildCount() > 0, ancestorCheckedMode);
            nodesCheckingState.put(nodePath, checkedNode);
        }
        return checkedNode;
    }

    // added NEW
    // Returns the checkedMode of the nearest ancestor that can be found, else false
    protected boolean getAncestorCheckMode(TreePath  nodePath){
        TreePath parentPath = nodePath.getParentPath();
        if( parentPath == null ) {// nodePath is root so has null parent
            return false;
        }
        else {
            CheckedNode checkedNode = nodesCheckingState.get(parentPath);
            if( checkedNode == null )
                return getAncestorCheckMode(parentPath);
            else
                return checkedNode.isSelected;
        }
    }

    // When a node is checked/unchecked, updating the states of the predecessors
    protected void updatePredecessorsWithCheckMode(TreePath tp) {
        TreePath parentPath = tp.getParentPath();
        // If it is the root, stop the recursive calls and return
        if (parentPath == null) {
            return;
        }
        CheckedNode parentCheckedNode = getCheckedNode(parentPath);
        DefaultMutableTreeNode parentNode = getTreeNode(parentPath);
        parentCheckedNode.allChildrenSelected = true;
        parentCheckedNode.isSelected = false;
        for (int i = 0 ; i < parentNode.getChildCount() ; i++) {
            TreePath childPath = parentPath.pathByAddingChild(parentNode.getChildAt(i));
            CheckedNode childCheckedNode = getCheckedNode(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;
            }
        }
        // Go to upper predecessor
        updatePredecessorsWithCheckMode(parentPath);
    }

    // Recursively checks/unchecks a subtree. NEW: modified to perform lazily.
    // NEW arg goOneLevelDown will be false when checkbox is clicked, true when expanding a node.
    protected void checkSubTree(TreePath tp, boolean check, boolean goOneLevelDown) {
        CheckedNode cn =  getCheckedNode(tp);
        cn.isSelected = check;
        DefaultMutableTreeNode node = getTreeNode(tp);
        if( isExpanded(tp) || goOneLevelDown ){
            for (int i = 0 ; i < node.getChildCount() ; i++) {
                checkSubTree(tp.pathByAddingChild(node.getChildAt(i)), check, false);
            }
        }
        cn.allChildrenSelected = check;
    }

    public static DefaultMutableTreeNode getTreeNode(TreePath path)
    {
        return (DefaultMutableTreeNode)(path.getLastPathComponent());
    }
}

может быть решением без структуры карты.

MouseAdapter ml = new MouseAdapter() {
        public void mousePressed(MouseEvent e) {
            if ( e.getClickCount() == 1 )
            {
                TreePath selPath = m_xTree.getPathForLocation(e.getX(), e.getY());
                if(selPath==null) return;
                else if (selPath!=null && selPath.getPathCount()>=1)
                {
                    DataItem xDIClicked = (DataItem)( selPath.getPathComponent(selPath.getPathCount()-1) );
                    xDIClicked.setIsSelectedByCheckbox( !xDIClicked.getIsSelectedByCheckbox() );
                    m_xTree.repaint();
                }
            }
        }
    };
    m_xTree.addMouseListener(ml);

таким образом, xDIClicked, возвращаемый getPathComponent (), является элементом данных модели JTree. И каждый DataItem содержит проверенное свойство.