Java Swing:需要一个高质量的JTree和复选框

我正在寻找一个包含复选框的JTree实现,其中包含:

  • 选择一个节点时,将自动选择树中的所有后续节点

  • 取消选择一个节点时,将自动取消选择树中的所有后续节点

  • 如果已选择父节点,并且已从其后续节点中删除选择,则将更改节点颜色,以使其直观,即虽然选择了此父节点,但并未选择其所有后续节点(如选择时一样)要在通用安装程序中安装的组件)

  • 单击节点即可(无需按住’Ctrl’键!):

    • 如果已选择该节点,则将取消选中该节点及其所有后继节点
    • 如果未选择该节点,则会选择该节点及其所有后继节点

我在网上寻找简单的东西,却找不到我想要的简单东西。

有谁知道这种树的良好实现?

回答自己:

我决定与大家分享我的代码。

这是结果的屏幕截图:

截图

实施细节:

  • 创建了一个扩展JTree的新类

  • 用我创建的新类替换了’TreeCellRenderer’,它显示了一个复选框和一个标签。 将更改复选框选择,而不是标签背景和边框。

  • 完全终止了选择机制。 用’DefaultTreeSelectionModel’覆盖内联替换’Selection Model’,它具有空实现

    • 创建新事件类型以检查复选框

    • 创建了特殊的数据结构,有助于快速指示每个节点的状态

请享用!!

这是一个用法示例:

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

我有类似的需求,但这里发布的解决方案对我来说不太合适(高效)。 我需要懒惰地构建check-box-tree的模型,因为我的完整树模型可能很庞大(有数千个节点)。 此外,保留所有checkedPaths的集合对我的数据集和使用来说似乎是过多且不必要的成本:我所需要的只是用户实际选择和取消选择的节点映射,因为这是极简主义信息。可以推断出从属节点。

所以我“增强”上面的解决方案在构建树模型时懒惰(仅在用户实际扩展节点时添加到模型中)并显示它(当用户实际检查/取消时仅设置从属节点的状态 – 检查父节点)。

我还添加了JCheckBoxTree的接口,从外部提供treeModel和expand-listener。 要使用此function,您需要根据您自己的域提供“您的域特定内容到此处”注释下方代码部分的替代实现。 TreeExpansionListener使用domain-object的expand方法执行延迟的节点插入。

 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 includedPaths = new HashSet<>(); HashSet 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 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 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 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); 

因此,getPathComponent()返回的xDIClicked是JTree Model的数据项。 每个DataItem都包含checked属性。