Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to add checkbox to JTree node to manage multiselection?

Tags:

java

swing

jtree

I want to build JTree which has nodes that contain check box + icon + data and tree selection algorithm.

like image 473
Maxim Shoustin Avatar asked Oct 13 '12 07:10

Maxim Shoustin


1 Answers

This is full example that demonstrate how to add checkbox to Jtree node. I used JTree with nodes based on File system content.

I usesd also AddCheckBoxToTree.CheckTreeManager class to manage selection or semi selection options.

Use

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

method to play with selection tree path.

for example:

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

here I pasted all code that do that:

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<roots.length; k++)
    {
        node = new DefaultMutableTreeNode(new IconData(ICON_DISK, null, new FileNode(roots[k])));
        top.add(node);
        node.add(new DefaultMutableTreeNode( new Boolean(true) ));
    }

    m_model = new DefaultTreeModel(top);

    m_tree = new JTree(m_model){
        public String getToolTipText(MouseEvent ev) 
        {
            if(ev == null)
                return null;
            TreePath path = m_tree.getPathForLocation(ev.getX(), 
                    ev.getY());
            if (path != null)
            {
                FileNode fnode = getFileNode(getTreeNode(path));
                if (fnode==null)
                    return null;
                File f = fnode.getFile();
                return (f==null ? null : f.getPath());
            }
            return null;
        }
    };

    ToolTipManager.sharedInstance().registerComponent(m_tree);

    m_tree.putClientProperty("JTree.lineStyle", "Angled");

    TreeCellRenderer renderer = new IconCellRenderer();
    m_tree.setCellRenderer(renderer);

    m_tree.addTreeExpansionListener(new  DirExpansionListener());

    m_tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); 
    m_tree.setShowsRootHandles(true); 
    m_tree.setEditable(false);


    checkTreeManager = AddCh.new CheckTreeManager(m_tree, null);



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


    WindowListener wndCloser = new WindowAdapter(){
        public void windowClosing(WindowEvent e){
            System.exit(0);
        }
    };

    addWindowListener(wndCloser);

    setVisible(true);
}

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

FileNode getFileNode(DefaultMutableTreeNode node)
{
    if (node == null)
        return null;
    Object obj = node.getUserObject();
    if (obj instanceof IconData)
        obj = ((IconData)obj).getObject();
    if (obj instanceof FileNode)
        return (FileNode)obj;
    else
        return null;
}

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

// Make sure expansion is threaded and updating the tree model
// only occurs within the event dispatching thread.
class DirExpansionListener implements TreeExpansionListener
{
    public void treeExpanded(TreeExpansionEvent event)
    {
        final DefaultMutableTreeNode node = getTreeNode(
                event.getPath());
        final FileNode fnode = getFileNode(node);

        Thread runner = new Thread() 
        {
            public void run() 
            {
                if (fnode != null && fnode.expand(node)) 
                {
                    Runnable runnable = new Runnable() 
                    {
                        public void run() 
                        {
                            m_model.reload(node);
                        }
                    };
                    SwingUtilities.invokeLater(runnable);
                }
            }
        };
        runner.start();
    }

    public void treeCollapsed(TreeExpansionEvent event) {}
}



public static void main(String argv[]) 
{
    try {
        UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
    } catch (Exception evt) {}
    new FileTreeViewer();
}
}

class IconCellRenderer extends JLabel implements TreeCellRenderer{
protected Color m_textSelectionColor;
protected Color m_textNonSelectionColor;
protected Color m_bkSelectionColor;
protected Color m_bkNonSelectionColor;
protected Color m_borderSelectionColor;

protected boolean m_selected;

public IconCellRenderer()
{
    super();
    m_textSelectionColor = UIManager.getColor(
            "Tree.selectionForeground");
    m_textNonSelectionColor = UIManager.getColor(
            "Tree.textForeground");
    m_bkSelectionColor = UIManager.getColor(
            "Tree.selectionBackground");
    m_bkNonSelectionColor = UIManager.getColor(
            "Tree.textBackground");
    m_borderSelectionColor = UIManager.getColor(
            "Tree.selectionBorderColor");
    setOpaque(false);
}

public Component getTreeCellRendererComponent(JTree tree, 
        Object value, boolean sel, boolean expanded, boolean leaf, 
        int row, boolean hasFocus) 

{
    DefaultMutableTreeNode node = 
            (DefaultMutableTreeNode)value;
    Object obj = node.getUserObject();
    setText(obj.toString());

    if (obj instanceof Boolean)
        setText("Retrieving data...");

    if (obj instanceof IconData)
    {
        IconData idata = (IconData)obj;
        if (expanded)
            setIcon(idata.getExpandedIcon());
        else
            setIcon(idata.getIcon());
    }
    else
        setIcon(null);

    setFont(tree.getFont());
    setForeground(sel ? m_textSelectionColor : 
        m_textNonSelectionColor);
    setBackground(sel ? m_bkSelectionColor : 
        m_bkNonSelectionColor);
    m_selected = sel;
    return this;
}

public void paintComponent(Graphics g) 
{
    Color bColor = getBackground();
    Icon icon = getIcon();

    g.setColor(bColor);
    int offset = 0;
    if(icon != null && getText() != null) 
        offset = (icon.getIconWidth() + getIconTextGap());
    g.fillRect(offset, 0, getWidth() - 1 - offset,
            getHeight() - 1);

    if (m_selected) 
    {
        g.setColor(m_borderSelectionColor);
        g.drawRect(offset, 0, getWidth()-1-offset, getHeight()-1);
    }

    super.paintComponent(g);
}
}

class IconData {
protected Icon   m_icon;
protected Icon   m_expandedIcon;
protected Object m_data;

public IconData(Icon icon, Object data)
{
    m_icon = icon;
    m_expandedIcon = null;
    m_data = data;
}

public IconData(Icon icon, Icon expandedIcon, Object data)
{
    m_icon = icon;
    m_expandedIcon = expandedIcon;
    m_data = data;
}

public Icon getIcon() 
{ 
    return m_icon;
}

public Icon getExpandedIcon() 
{ 
    return m_expandedIcon!=null ? m_expandedIcon : m_icon;
}

public Object getObject() 
{ 
    return m_data;
}

public String toString() 
{ 
    return m_data.toString();
}
}

class FileNode {
protected File m_file;

public FileNode(File file)
{
    m_file = file;
}

public File getFile() 
{ 
    return m_file;
}

public String toString() 
{ 
    return m_file.getName().length() > 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<FileNode> v = new Vector<FileNode>();

    for (int k=0; k<files.length; k++){
        File f = files[k];
        if (!(f.isDirectory()))
            continue;

        FileNode newNode = new FileNode(f);

        boolean isAdded = false;
        for (int i=0; i<v.size(); i++)
        {
            FileNode nd = (FileNode)v.elementAt(i);
            if (newNode.compareTo(nd) < 0)
            {
                v.insertElementAt(newNode, i);
                isAdded = true;
                break;
            }
        }
        if (!isAdded)
            v.addElement(newNode);
    }

    for (int i=0; i<v.size(); i++){
        FileNode nd = (FileNode)v.elementAt(i);
        IconData idata = new IconData(FileTreeViewer.ICON_FOLDER, FileTreeViewer.ICON_EXPANDEDFOLDER, nd);
        DefaultMutableTreeNode node = new 
                DefaultMutableTreeNode(idata);
        parent.add(node);

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

    return true;
}

public boolean hasSubDirs(){
    File[] files = listFiles();
    if (files == null)
        return false;
    for (int k=0; k<files.length; k++)
    {
        if (files[k].isDirectory())
            return true;
    }
    return false;
}

public int compareTo(FileNode toCompare){ 
    return  m_file.getName().compareToIgnoreCase(
            toCompare.m_file.getName() ); 
}

protected File[] listFiles(){
    if (!m_file.isDirectory())
        return null;
    try
    {
        return m_file.listFiles();
    }
    catch (Exception ex)
    {
        JOptionPane.showMessageDialog(null, "Error reading directory "+m_file.getAbsolutePath(),"Warning", JOptionPane.WARNING_MESSAGE);
        return null;
    }
}
}

Till now all was good and clear. Now i'm going to paste selection Controller, here we go:

Here we create checkbox element to add after to tree node

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

}
}

After we add 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<selectionPaths.length; j++){ 
            if(isDescendant(selectionPaths[j], path)){
                return true;
            }                    
        } 

        return false; 
    } 

    // tells whether given path is selected. 
    // if dig is true, then a path is assumed to be selected, if 
    // one of its ancestor is selected. 
    public boolean isPathSelected(TreePath path, boolean dig){                      
        if(!dig){
            return super.isPathSelected(path); 
        }

        while(path!=null && !super.isPathSelected(path)){
            path = path.getParentPath(); 
        }

        return path!=null; 
    } 

    // is path1 descendant of path2 
    private boolean isDescendant(TreePath path1, TreePath path2){ 
        Object obj1[] = path1.getPath(); 
        Object obj2[] = path2.getPath(); 
        for(int i = 0; i<obj2.length; i++){ 
            if(obj1[i]!=obj2[i]) 
                return false; 
        } 
        return true; 
    } 

    public void setSelectionPaths(TreePath[] pPaths){ 
        throw new UnsupportedOperationException("not implemented yet!!!"); 
    } 

    public void addSelectionPaths(TreePath[] paths){ 

        // unselect all descendants of paths[] 
        for(int i = 0; i<paths.length; i++)
        { 
            TreePath path = paths[i]; 

            TreePath[] selectionPaths = getSelectionPaths(); 

            if(selectionPaths==null){ 
                break; 
            }

            ArrayList<TreePath> toBeRemoved = new ArrayList<TreePath>(); 

            for(int j = 0; j<selectionPaths.length; j++)
            { 
                if(isDescendant(selectionPaths[j], path))
                {
                    toBeRemoved.add(selectionPaths[j]); 
                }
            } 
            super.removeSelectionPaths((TreePath[])toBeRemoved.toArray(new TreePath[0])); 
        } 

        // if all siblings are selected then unselect them and select parent recursively 
        // otherwize just select that path. 
        for(int i = 0; i<paths.length; i++){ 
            TreePath path = paths[i]; 

            TreePath temp = null; 

            while(areSiblingsSelected(path)){ 
                temp = path; 

                if(path.getParentPath()==null)
                { 
                    break; 
                }

                path = path.getParentPath(); 
            } 

            if(temp!=null){ 
                if(temp.getParentPath()!=null)
                { 
                    addSelectionPath(temp.getParentPath());
                }
                else
                { 
                    if(!isSelectionEmpty())
                    { 
                        removeSelectionPaths(getSelectionPaths()); 
                    }

                    super.addSelectionPaths(new TreePath[]{temp}); 
                }                   
            }
            else 
            {   
                super.addSelectionPaths(new TreePath[]{ path}); 
            }
        } 
    } 

    // tells whether all siblings of given path are selected. 
    private boolean areSiblingsSelected(TreePath path){ 
        TreePath parent = path.getParentPath(); 

        if(parent==null){ 
            return true; 
        }

        Object node = path.getLastPathComponent(); 

        Object parentNode = parent.getLastPathComponent(); 

        int childCount = model.getChildCount(parentNode); 

        Boolean isParameters = false;
        Boolean isDescription = false;

        for(int i = 0; i<childCount; i++){ 

            Object childNode = model.getChild(parentNode, i); 



            if(childNode==node){ 
                continue;
            }

// If last Path component equals to "parameters" or "description" - select second child too. 
            if(childCount == 2)
            {
                if(childNode.toString().equals("parameters") && model.isLeaf(childNode))
                {
                    isParameters = true;
                }
                if(childNode.toString().equals("description") && model.isLeaf(childNode))
                {
                    isDescription = true;
                }
            }


            if(!isPathSelected(parent.pathByAddingChild(childNode)) && !isParameters && !isDescription){ 
                return false; 
            }
        }

        return true; 
    } 

    public void removeSelectionPaths(TreePath[] paths){ 
        for(int i = 0; i<paths.length; i++){ 
            TreePath path = paths[i]; 
            if(path.getPathCount()==1) 
                super.removeSelectionPaths(new TreePath[]{ path}); 
            else 
                toggleRemoveSelection(path); 
        } 
    } 

    /** if any ancestor node of given path is selected then unselect it 
     *  and selection all its descendants except given path and descendants. 
     * otherwise just unselect the given path */
    private void toggleRemoveSelection(TreePath path){ 

        Stack<TreePath> stack = new Stack<TreePath>(); 
        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<childCount; i++){ 
                Object childNode = model.getChild(node, i); 

                if(childNode.toString().equals("parameters") && model.isLeaf(childNode))
                {
                    isParameters = true;
                }
                if(childNode.toString().equals("description")  && model.isLeaf(childNode))
                {
                    isDescription = true;
                }


                if(childNode!=peekNode)
                {
                    if(!isParameters && !isDescription)
                    super.addSelectionPaths(new TreePath[]{temp.pathByAddingChild(childNode)}); 
                }
            } 
        } 

        super.removeSelectionPaths(new TreePath[]{parent}); 
    }

    public TreeModel getModel() {
        return model;
    } 
}


public class CheckTreeCellRenderer extends JPanel implements TreeCellRenderer { 

    static final  long serialVersionUID =0;

    CheckTreeSelectionModel selectionModel; 
    private TreeCellRenderer delegate; 
    TristateCheckBox checkBox = new TristateCheckBox(); 





    public CheckTreeCellRenderer(TreeCellRenderer delegate, CheckTreeSelectionModel selectionModel){ 
        this.delegate = delegate; 
        this.selectionModel = selectionModel; 

        setLayout(new BorderLayout()); 
        setOpaque(false); 
        checkBox.setOpaque(false);      

    } 


    public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus){ 
        Component renderer = delegate.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus); 


        TreePath path = tree.getPathForRow(row);            


        if(path!=null)
        {                   
            if(selectionModel.isPathSelected(path, true))
            {                   
                checkBox.setState(checkBox.SELECTED);   
                //System.out.println(">>>>>>     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(); 
    }       
}    
}

The most impotent method is:

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

that checks whether there is any unselected node in the subtree of given path (DONT_CARE).

And this is the result of what we did: And this is the result of what we did:


This answer is based on Santhosh Kumar's Weblog post with minor fixes.

like image 132
Maxim Shoustin Avatar answered Sep 19 '22 16:09

Maxim Shoustin