¿Cómo agregar checkbox al nodo JTree para administrar la multiselección?

Quiero construir JTree que tenga nodos que contengan checkbox + icono + datos y algoritmo de selección de árbol.

Este es un ejemplo completo que demuestra cómo agregar checkbox al nodo Jtree. Usé JTree con nodos basados ​​en el contenido del sistema de archivos.

También AddCheckBoxToTree.CheckTreeManager clase AddCheckBoxToTree.CheckTreeManager para administrar las opciones de selección o semi selección.

Utilizar

 public AddCheckBoxToTree.CheckTreeManager getCheckTreeManager() { return checkTreeManager; } 

método para jugar con la ruta del árbol de selección.

por ejemplo:

 // clear all selected path in order TreePath[] paths=getCheckTreeManager().getSelectionModel().getSelectionPaths(); if(paths != null){ for(TreePath tp : paths){ getCheckTreeManager().getSelectionModel().removeSelectionPath(tp); } } 

aquí pegué todo el código que hace eso:

 package com.demo.tree.checkbox; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Graphics; import java.awt.event.MouseEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.event.WindowListener; import java.io.File; import java.util.Vector; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JScrollPane; import javax.swing.JTree; import javax.swing.SwingUtilities; import javax.swing.ToolTipManager; import javax.swing.UIManager; import javax.swing.event.TreeExpansionEvent; import javax.swing.event.TreeExpansionListener; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreeCellRenderer; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; public class FileTreeViewer extends JFrame { private static final long serialVersionUID = 1L; public static final ImageIcon ICON_COMPUTER = new ImageIcon(""); public static final ImageIcon ICON_DISK = new ImageIcon("defaults1.png"); public static final ImageIcon ICON_FOLDER = new ImageIcon("fol_orig.png"); public static final ImageIcon ICON_EXPANDEDFOLDER = new ImageIcon("folder_open.png"); protected JTree m_tree; protected DefaultTreeModel m_model; AddCheckBoxToTree AddCh = new AddCheckBoxToTree(); private AddCheckBoxToTree.CheckTreeManager checkTreeManager; protected TreePath m_clickedPath; public FileTreeViewer() { super("Demo tree check box"); setSize(400, 300); DefaultMutableTreeNode top = new DefaultMutableTreeNode( new IconData(ICON_COMPUTER, null, "Computer")); DefaultMutableTreeNode node; File[] roots = File.listRoots(); for (int k=0; k 0 ? m_file.getName() : m_file.getPath(); } public boolean expand(DefaultMutableTreeNode parent){ DefaultMutableTreeNode flag = (DefaultMutableTreeNode)parent.getFirstChild(); if (flag==null) // No flag return false; Object obj = flag.getUserObject(); if (!(obj instanceof Boolean)) return false; // Already expanded parent.removeAllChildren(); // Remove Flag File[] files = listFiles(); if (files == null) return true; Vector v = new Vector(); for (int k=0; k 

Hasta ahora todo estaba bien y claro. Ahora voy a pegar el controlador de selección, aquí vamos:

Aquí creamos un elemento de checkbox para agregar después al nodo de árbol

 package com.demo.tree.checkbox; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import javax.swing.AbstractAction; import javax.swing.ActionMap; import javax.swing.ButtonGroup; import javax.swing.ButtonModel; import javax.swing.Icon; import javax.swing.JCheckBox; import javax.swing.SwingUtilities; import javax.swing.event.ChangeListener; import javax.swing.plaf.ActionMapUIResource; public class TristateCheckBox extends JCheckBox { static final long serialVersionUID =0; /** This is a type-safe enumerated type */ public static class State { private State() {} } public final State NOT_SELECTED = new State(); public final State SELECTED = new State(); public final static State DONT_CARE = new State(); private final TristateDecorator model; public TristateCheckBox(String text, Icon icon, State initial){ super(text, icon); // Add a listener for when the mouse is pressed super.addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent e) { grabFocus(); model.nextState(); } }); // Reset the keyboard action map ActionMap map = new ActionMapUIResource(); map.put("pressed", new AbstractAction() { private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent e) { grabFocus(); model.nextState(); } }); map.put("released", null); SwingUtilities.replaceUIActionMap(this, map); // set the model to the adapted model model = new TristateDecorator(getModel()); setModel(model); setState(initial); } // Constractor types: public TristateCheckBox(String text, State initial) { this(text, null, initial); } public TristateCheckBox(String text) { this(text, DONT_CARE); } public TristateCheckBox() { this(null); } /** No one may add mouse listeners, not even Swing! */ public void addMouseListener(MouseListener l) { } /** * Set the new state to either SELECTED, NOT_SELECTED or * DONT_CARE. If state == null, it is treated as DONT_CARE. */ public void setState(State state) { model.setState(state); } /** Return the current state, which is determined by the * selection status of the model. */ public State getState() { return model.getState(); } public void setSelected(boolean b) { if (b) { setState(SELECTED); } else { setState(NOT_SELECTED); } } /** * Exactly which Design Pattern is this? Is it an Adapter, * a Proxy or a Decorator? In this case, my vote lies with the * Decorator, because we are extending functionality and * "decorating" the original model with a more powerful model. */ private class TristateDecorator implements ButtonModel { private final ButtonModel other; private TristateDecorator(ButtonModel other) { this.other = other; } private void setState(State state) { if (state == NOT_SELECTED) { other.setArmed(false); setPressed(false); setSelected(false); } else if (state == SELECTED) { other.setArmed(false); setPressed(false); setSelected(true); } else { // either "null" or DONT_CARE other.setArmed(true); setPressed(true); setSelected(false); } } /** * The current state is embedded in the selection / armed * state of the model. * * We return the SELECTED state when the checkbox is selected * but not armed, DONT_CARE state when the checkbox is * selected and armed (grey) and NOT_SELECTED when the * checkbox is deselected. */ private State getState() { if (isSelected() && !isArmed()) { // normal black tick return SELECTED; } else if (isSelected() && isArmed()) { // don't care grey tick return DONT_CARE; } else { // normal deselected return NOT_SELECTED; } } /** We rotate between NOT_SELECTED, SELECTED and DONT_CARE.*/ private void nextState() { State current = getState(); if (current == NOT_SELECTED) { setState(SELECTED); } else if (current == SELECTED) { setState(DONT_CARE); } else if (current == DONT_CARE) { setState(NOT_SELECTED); } } /** Filter: No one may change the armed status except us. */ public void setArmed(boolean b) { } /** We disable focusing on the component when it is not * enabled. */ public void setEnabled(boolean b) { setFocusable(b); other.setEnabled(b); } /** All these methods simply delegate to the "other" model * that is being decorated. */ public boolean isArmed() {return other.isArmed(); } public boolean isSelected() {return other.isSelected(); } public boolean isEnabled() {return other.isEnabled(); } public boolean isPressed() {return other.isPressed(); } public boolean isRollover() {return other.isRollover(); } public int getMnemonic() {return other.getMnemonic(); } public String getActionCommand() {return other.getActionCommand();} public Object[]getSelectedObjects() {return other.getSelectedObjects();} public void setSelected(boolean b) {other.setSelected(b);} public void setPressed(boolean b) {other.setPressed(b);} public void setRollover(boolean b) {other.setRollover(b);} public void setMnemonic(int key) {other.setMnemonic(key);} public void setActionCommand(String s) {other.setActionCommand(s);} public void setGroup(ButtonGroup group) {other.setGroup(group);} public void addActionListener(ActionListener l) {other.addActionListener(l);} public void removeActionListener(ActionListener l) {other.removeActionListener(l);} public void addItemListener(ItemListener l) {other.addItemListener(l);} public void removeItemListener(ItemListener l) {other.removeItemListener(l);} public void addChangeListener(ChangeListener l) {other.addChangeListener(l);} public void removeChangeListener(ChangeListener l) {other.removeChangeListener(l);} } } 

Después de agregar CheckTreeManager:

 package com.demo.tree.checkbox; import java.awt.BorderLayout; import java.awt.Component; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.Stack; import javax.swing.JCheckBox; import javax.swing.JPanel; import javax.swing.JTree; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.tree.DefaultTreeSelectionModel; import javax.swing.tree.TreeCellRenderer; import javax.swing.tree.TreeModel; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; public class AddCheckBoxToTree { public class CheckTreeSelectionModel extends DefaultTreeSelectionModel{ static final long serialVersionUID =0; private TreeModel model; public CheckTreeSelectionModel(TreeModel model){ this.model = model; setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION); } // tests whether there is any unselected node in the subtree of given path (DONT_CARE) public boolean isPartiallySelected(TreePath path){ if(isPathSelected(path, true)){ return false; } TreePath[] selectionPaths = getSelectionPaths(); if(selectionPaths==null){ return false; } for(int j = 0; j toBeRemoved = new ArrayList(); for(int j = 0; j stack = new Stack(); TreePath parent = path.getParentPath(); Boolean isParameters = false; Boolean isDescription = false; while(parent!=null && !isPathSelected(parent)){ stack.push(parent); parent = parent.getParentPath(); } if(parent!=null) stack.push(parent); else{ super.removeSelectionPaths(new TreePath[]{path}); return; } while(!stack.isEmpty()){ TreePath temp = (TreePath)stack.pop(); TreePath peekPath = stack.isEmpty() ? path : (TreePath)stack.peek(); Object node = temp.getLastPathComponent(); Object peekNode = peekPath.getLastPathComponent(); int childCount = model.getChildCount(node); for(int i = 0; i>>>>> selected " ); } else { checkBox.setState(checkBox.NOT_SELECTED); //System.out.println("not selected "); } if(selectionModel.isPartiallySelected(path)) { checkBox.setState(checkBox.DONT_CARE); } } removeAll(); add(checkBox, BorderLayout.WEST); add(renderer, BorderLayout.CENTER); return this; } public TreeCellRenderer getDelegate() { return delegate; } public void setDelegate(TreeCellRenderer delegate) { this.delegate = delegate; } } public class CheckTreeManager extends MouseAdapter implements TreeSelectionListener{ CheckTreeSelectionModel selectionModel; private JTree tree = new JTree(); int hotspot = new JCheckBox().getPreferredSize().width; public CheckTreeManager(JTree tree, CheckTreeSelectionModel checkTreeSelectionModel){ this.tree = tree; if(checkTreeSelectionModel != null) { //selectionModel = new CheckTreeSelectionModel(tree.getModel()); selectionModel = checkTreeSelectionModel; } else { selectionModel = new CheckTreeSelectionModel(tree.getModel()); //System.out.println(selectionModel.getSelectionPath()); } tree.setCellRenderer(new CheckTreeCellRenderer(tree.getCellRenderer(), selectionModel)); tree.addMouseListener(this); selectionModel.addTreeSelectionListener(this); } public void mouseClicked(MouseEvent me){ //System.out.println("start..."); TreePath path = tree.getPathForLocation(me.getX(), me.getY()); //System.out.println(Arrays.asList(path)); if(path==null) { //System.out.println("path==null"); return; } if(me.getX()/1.2>tree.getPathBounds(path).x+hotspot) { //System.out.println("me.getX()/1.2>tree.getPathBounds(path).x+hotspot"); return; } boolean selected = selectionModel.isPathSelected(path, true); selectionModel.removeTreeSelectionListener(this); try{ if(selected) { //System.out.println("selected"); selectionModel.removeSelectionPath(path); } else { //System.out.println("not selected"); selectionModel.addSelectionPath(path); } } finally { //System.out.println("finally"); selectionModel.addTreeSelectionListener(this); tree.treeDidChange(); } } public CheckTreeSelectionModel getSelectionModel(){ return selectionModel; } public void setSelectionModel(CheckTreeSelectionModel selectionModel) { this.selectionModel = selectionModel; } public void valueChanged(TreeSelectionEvent e){ tree.treeDidChange(); } } } 

El método más impotente es:

 boolean com.demo.tree.checkbox.AddCheckBoxToTree.CheckTreeSelectionModel.isPartiallySelected 

que comprueba si hay un nodo no seleccionado en el subárbol de la ruta determinada (DONT_CARE).

Y este es el resultado de lo que hicimos: Y este es el resultado de lo que hicimos:


Esta respuesta se basa en la publicación Weblog de Santhosh Kumar con correcciones menores.