ejemplo java swing checkbox jtree jcheckbox

ejemplo - Java Swing: Necesita un JTree desarrollado de buena calidad con casillas de verificación



java swing pdf (3)

Puede ser solución sin estructura de mapa.

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

Así que xDIClicked devuelto por getPathComponent () es el elemento de datos de JTree Model. Y cada DataItem contiene propiedad marcada.

Estaba buscando una implementación de JTree, que contiene casillas de verificación y que:

  • Cuando selecciona un nodo, todos sus sucesores en el árbol se seleccionan automáticamente

  • Cuando deselecciona un nodo, todos sus sucesores en el árbol se deseleccionan automáticamente

  • Cuando ya se seleccionó un nodo principal y se eliminó la selección de uno de sus sucesores, se cambiará el color del nodo, para que sea intuitivo que aunque este nodo principal esté seleccionado, no se seleccionen todos sus sucesores (como cuando selecciona componentes para instalar en instaladores comunes)

  • Un clic en un nodo conduce a (¡No es necesario mantener presionada la tecla ''Ctrl''!):

    • Si el nodo ya está seleccionado, se deselecciona, con todos sus sucesores.
    • Si el nodo no está seleccionado, se selecciona, con todos sus sucesores

Busqué en la red algo simple, pero no pude encontrar algo tan simple como quería.

¿Alguien sabe una buena implementación de tal árbol?


Respondiendo a mi mismo:

Decidí compartir mi código con todos.

Aquí hay una captura de pantalla del resultado:

Los detalles de la implementación:

  • Crea una nueva clase que extiende JTree.

  • Reemplazé el ''TreeCellRenderer'' por una nueva clase que creé, que muestra una casilla de verificación y una etiqueta. La selección de la casilla de verificación se cambia en lugar del fondo y el borde de la etiqueta.

  • Terminó totalmente el mecanismo de selección. Se reemplazó el ''Modelo de selección'' por un ''DefaultTreeSelectionModel'' reemplazado en línea, que tiene una implementación vacía

    • Creado nuevo tipo de evento para la comprobación de las casillas de verificación.

    • Se crearon estructuras de datos especiales que ayudan a indicar rápidamente el estado de cada nodo.

¡¡Disfrutar!!

Aquí hay un ejemplo de uso:

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

Aquí está el código fuente de la clase en sí:

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


Tenía una necesidad similar pero la solución publicada aquí no era del todo correcta (eficiente) para mí. Necesitaba que el modelo de check-box-tree se construyera perezosamente, ya que mi modelo de árbol completo podría ser enorme (con muchos miles de nodos). Además, mantener el conjunto de todas las rutas de verificación era un costo que parecía excesivo e innecesario para mi conjunto de datos y uso: todo lo que necesitaba era el mapa de nodos que el usuario realmente había seleccionado y deseleccionado, ya que esa es la información minimalista a partir de la cual Los nodos subordinados podrían inferirse.

Así que "mejoré" la solución anterior para que sea perezoso al crear el modelo de árbol (solo agregar al modelo cuando el nodo realmente expande un nodo) y al mostrarlo (solo establecer el estado de los nodos subordinados cuando un usuario realmente verifica / desactiva) - comprueba un nodo padre).

También agregué a la interfaz de JCheckBoxTree para proporcionar treeModel y expand-listener desde afuera. Para usar esto, debe proporcionar una implementación alternativa de la parte del código que se encuentra debajo del comentario "Las cosas específicas de su dominio van aquí", según su propio dominio. TreeExpansionListener realiza la inserción perezosa de nodos, utilizando el método de expansión del objeto de dominio.

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

Mi versión modificada / perezosa de JCheckBoxTree es la siguiente:

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