2
  1. I know how to set custom leaf icons in JTree
  2. I know how to set custom closed/open icons for all group nodes

But I can not set custom open/closed icons based on the group node names, for example of node could be called Emails (so it is nice to have an envelop icon) or one group may be called tasks and so on.

I tried to do this by overriding the getTreeCellRendererComponent method of class DefaultTreeCellRenderer

But changing the icon for the current node will affect for the next node only!

How to set custom open/closed icons for individual groups?

Please take a look at my code:

Employee.java

package com.ehsunbehravesh.swing;

import java.util.Random;

public class Employee {

  public String name;
  public int id;
  public boolean isBoss;
  public Employee[] employees;

  public Employee(String name, boolean isBoss) {
    this.name = name;
    this.isBoss = isBoss;
    this.id = new Random(System.currentTimeMillis()).nextInt(Integer.MAX_VALUE);
  }  

  @Override
  public String toString() {
    return this.name;
  }

    static String randomName() {
    String chars = "abcdefghijklmnopqrstuvwxyz";
    StringBuilder builder = new StringBuilder();
    Random r = new Random(System.currentTimeMillis());
    int length = r.nextInt(10) + 1;
    for (int i = 0; i < length; i++) {
      builder.append(chars.charAt(r.nextInt(chars.length())));
    }

    return builder.toString();
  }
}

CustomTreeNode.java

package com.ehsunbehravesh.swing;

import javax.swing.ImageIcon;
import javax.swing.tree.DefaultMutableTreeNode;

public class CustomTreeNode extends DefaultMutableTreeNode {

  /**
   * The icon which is displayed on the JTree object. open, close, leaf icon.
   */
  private ImageIcon icon;

  public CustomTreeNode(ImageIcon icon) {
    this.icon = icon;
  }

  public CustomTreeNode(ImageIcon icon, Object userObject) {
    super(userObject);
    this.icon = icon;
  }

  public CustomTreeNode(ImageIcon icon, Object userObject, boolean allowsChildren) {
    super(userObject, allowsChildren);
    this.icon = icon;
  }

  public ImageIcon getIcon() {
    return icon;
  }

  public void setIcon(ImageIcon icon) {
    this.icon = icon;
  }    
}

CustomeTreeCellRenderer.java

package com.ehsunbehravesh.swing;

import java.awt.Component;
import javax.swing.JTree;
import javax.swing.tree.DefaultTreeCellRenderer;

class CustomeTreeCellRenderer extends DefaultTreeCellRenderer {

  public CustomeTreeCellRenderer() {
  }

  @Override
  public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
    super.getTreeCellRendererComponent(tree, value, leaf, expanded, leaf, row, hasFocus);

    if (!leaf) {
      CustomTreeNode node = (CustomTreeNode) value;
      System.out.println(((Employee) node.getUserObject()).name);

      if (node.getIcon() != null) {
        System.out.println(node.getIcon().toString());
        setClosedIcon(node.getIcon());
        setOpenIcon(node.getIcon());
      } else {
        setClosedIcon(getDefaultClosedIcon());
        setClosedIcon(getDefaultOpenIcon());
        setOpenIcon(getDefaultOpenIcon());
      }
    }

    return this;
  }
}

Test1.java

package com.ehsunbehravesh.swing;

import java.awt.BorderLayout;
import java.awt.Color;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTree;
import javax.swing.tree.DefaultTreeModel;

class TreeSample {
  public static void main(String args[]) {
    JFrame f = new JFrame("JTree Sample");
    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    JPanel pnlMain = new JPanel(new BorderLayout());
    pnlMain.setBackground(Color.white);

    createTree(pnlMain);

    f.setContentPane(pnlMain);

    f.setSize(300, 200);
    f.setVisible(true);
  }

  private static void createTree(JPanel pnlMain) {
    Employee bigBoss = new Employee(Employee.randomName(), true);
    Employee[] level1 = new Employee[5];    
    bigBoss.employees = level1;

    for (int i = 0; i < level1.length; i++) {
      level1[i] = new Employee(Employee.randomName(), true);      
    }


    for (int i = 0; i < level1.length; i++) {
      Employee employee = level1[i];
      if (employee.isBoss) {
        int count = 5;
        employee.employees = new Employee[count];

        for (int j = 0; j < employee.employees.length; j++) {
          employee.employees[j] = new Employee(Employee.randomName(), false);          
        }
      }
    }

    CustomTreeNode root = new CustomTreeNode(new ImageIcon("images/Circle_3.gif"), bigBoss);           
    DefaultTreeModel model = new DefaultTreeModel(root);

    for (Employee employee : bigBoss.employees) {
      CustomTreeNode boss = new CustomTreeNode(new ImageIcon("images/Circle_2.gif"), employee);
      root.add(boss);
      if (employee.isBoss) {                
        for (Employee employee1 : employee.employees) {
          CustomTreeNode emp = new CustomTreeNode(new ImageIcon("images/Circle_1.gif"), employee1);
          boss.add(emp);
        }
      }
    }

    JTree tree = new JTree(model);
    tree.setCellRenderer(new CustomeTreeCellRenderer());            
    pnlMain.add(tree, BorderLayout.CENTER);
  }  
}
Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
ehsun7b
  • 4,796
  • 14
  • 59
  • 98

3 Answers3

10

In your TreeCellRenderer, you can use setOpenIcon() and setClosedIcon() as required in conjunction with the defined parameters and predicates related to your model. Given a tree having the default JTree model, the TreeRenderer below will use the closed and open icons for the sports node:

iamge

private static class TreeRenderer extends DefaultTreeCellRenderer {

    private static final Icon closed =
        (Icon) UIManager.get("InternalFrame.maximizeIcon");
    private static final Icon open =
        (Icon) UIManager.get("InternalFrame.minimizeIcon");

    @Override
    public Component getTreeCellRendererComponent(JTree tree, Object value,
        boolean sel, boolean exp, boolean leaf, int row, boolean hasFocus) {
        DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
        String s = node.getUserObject().toString();
        if ("sports".equals(s)) {
            setOpenIcon(open);
            setClosedIcon(closed);
        } else {
            setOpenIcon(getDefaultOpenIcon());
            setClosedIcon(getDefaultClosedIcon());
        }
        super.getTreeCellRendererComponent(
            tree, value, sel, exp, leaf, row, hasFocus);
        return this;
    }
}

See also this related example.

Community
  • 1
  • 1
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • Don't we need to call `super.getTreeCellRendererComponent` at the very beginning of the overridden version? – ehsun7b Jan 02 '13 at 00:46
  • 1
    No, you may be thinking of invoking the [superclass constructor](http://docs.oracle.com/javase/tutorial/java/IandI/super.html), which is automatic for the _no-argument_ constructor of `DefaultTreeCellRenderer`. – trashgod Jan 02 '13 at 02:54
  • 1
    Oh, my mistake was that I called the `super.getTreeCellRendererComponent` before I change the icons so it was effective for the next nodes only. Thanks – ehsun7b Jan 03 '13 at 04:28
  • I implemented your method however Java keeps throwing `Exception in thread "AWT-EventQueue-0" java.lang.ClassCastException: TreeRenderer cannot be cast to javax.swing.AbstractButton`. The exception is internal to Java, i.e. the stack trace never once mentions a line of my code where the issue resides, what is going on? – Troyseph Oct 21 '14 at 14:02
  • The bug seemed entirely due to using the open & closed icons on Windows, using my own locally saved images loaded as Icons works just fine... – Troyseph Oct 21 '14 at 14:27
  • @SebastianTroy: Indeed, the L&F implementation is free to do this; you can also construct an `Icon`, for [example](http://stackoverflow.com/a/3072979/230513). – trashgod Oct 21 '14 at 15:00
5

Having run you code, it would appear that the images you are trying to load are "meant" to be embedded within you application (that is, they don't reside some where on the disk out side of the application context).

So instead of doing this...

CustomTreeNode root = new CustomTreeNode(new ImageIcon("images/Circle_3.gif"), bigBoss); 

Try doing something like this...

CustomTreeNode root = new CustomTreeNode(new ImageIcon(ImageIO.read(getClass().getResource("/images/Circle_3.gif"))), bigBoss); 

Instead. This will cause Java to look within it's class path (including any JAR resources) to find the images.

When I run you code without this fix, nothing worked, when I updated it to use this feature, it worked fine.

NB: ImageIO#read throws an IOException so look out for it

Updated

After much head scratching...I changed the cell renderer to look like this...

class CustomeTreeCellRenderer extends DefaultTreeCellRenderer {

    public CustomeTreeCellRenderer() {
    }

    @Override
    public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {

//            if (!leaf) {
        CustomTreeNode node = (CustomTreeNode) value;

        if (node.getIcon() != null) {
            System.out.println(node + " - " + node.getIcon());
            setClosedIcon(node.getIcon());
            setOpenIcon(node.getIcon());
            setLeafIcon(node.getIcon());
        } else {
            System.out.println(node + " - default");
            setClosedIcon(getDefaultClosedIcon());
            setLeafIcon(getDefaultLeafIcon());
            setOpenIcon(getDefaultOpenIcon());
        }
//            }

        super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);

        return this;
    }
}

It it cleared it all up...

enter image description here

Calling setXxxIcon doesn't effect the current renderer, but the future renderer. That is. If you call setOpenIcon AFTER you've already called super.getTreeCellRendererComponent, it will not effect the current renderer, but it will effect the next call to super.getTreeCellRendererComponent as the set method is simply setting the value of class variable.

Additional

Trashgod has made a valuable comment about relying on the implementation and the way it works now.

Instead of calling DefaultTreeCellRenderer#setXxxIcon within the getTreeCellRendererComponent method, you should actually simply call DefaultTreeCellRenderer#setIcon, using the required icon based on the parameters passed to it.

This means you can call super.getTreeCellRendererComponent first and then override the behavior of the icons after it.

You could also grab a reference to Object value and override the DefaultTreeCellRenderer#getXxxIcon methods and based on the value, change the return values of these methods. I personally, wouldn't courage this as it changes the documented behavior of the renderer

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • +1 for having the motivation to run code consisting of 4 classes. – Andrew Thompson Dec 31 '12 at 07:33
  • 1
    @AndrewThompson at least it ran :P – MadProgrammer Dec 31 '12 at 07:35
  • Actually images should be in one folder called images in the root of project (at production level means out of the jar file), anyway it is not the problem, of course all of us know how to load the image into ImageIcon, please focus on main question instead. – ehsun7b Dec 31 '12 at 09:04
  • @ehsun7b: resources are a problem in an [sscce](http://sscce.org/); using a recycled `UIManager` icon is a handy alternative. – trashgod Dec 31 '12 at 19:09
  • @ehsun7b You'd be very surprised - but since you seemed unwilling to supply those details, the only choice we have is to guess – MadProgrammer Dec 31 '12 at 21:06
  • @MadProgrammer: I'd adopted a similar approach after reading the source, but I'm wary of relying on an implementation detail. The API for `DefaultTreeCellRenderer` is a bit laconic about "its self-use of overridable methods." – trashgod Jan 01 '13 at 05:51
  • @trashgod I have to admit, I had thought that the `setXxxIcon` methods would do just that, apply the icons, but given the fact that it's a renderer, I guess one shouldn't be surpised – MadProgrammer Jan 01 '13 at 07:17
  • @MadProgrammer: I'm probably being too fastidious; it _does_ say, "the icon is set based on the `leaf` and `expanded` parameters." Thanks for feedback. – trashgod Jan 01 '13 at 07:34
  • @trashgod Actually, I just had the thought, rather then using the `setXxxMethod`s in the renderer itself, we should actually be calling set icon, based on the parameters past to the method – MadProgrammer Jan 01 '13 at 07:44
  • 1
    @MadProgrammer: Right; I just hard coded a (non-leaf) node for special treatment, but you'd probably want a `Map` to have multiple different open/closed pairs in the same tree. – trashgod Jan 01 '13 at 12:04
  • for reference, `DefaultTreeCellRenderer` will change the node's selection background and foreground color ,when you click any node the node not show gray highlight , this issue you can use `this.selected = selected; this.hasFocus = hasFocus;` to fix it . Hope this helps others. – Alter Hu Oct 17 '16 at 06:33
0

If we need the Component (so we need to call super renderer at first step) it is important to set all tree icons (open, close, leaf) in order to get it working correctly.

public java.awt.Component getTreeCellRendererComponent(javax.swing.JTree tree, Object value, boolean selected, boolean expanded, boolean isLeaf, int row, boolean focused) {
    Component c = super.getTreeCellRendererComponent(tree, value, selected, expanded, isLeaf, row, focused);

    DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
    MyTreeNodeWrapper treeNodeWrapper = (MyTreeNodeWrapper) node.getUserObject();
    Icon icon = treeNodeWrapper .getIcon();

    setOpenIcon(icon);
    setClosedIcon(icon);
    setLeafIcon(icon);

    if (!tree.isEnabled()) {
        if (isLeaf) {
            setDisabledIcon(getLeafIcon());
        } else if (expanded) {
            setDisabledIcon(getOpenIcon());
        } else {
            setDisabledIcon(getClosedIcon());
        }
    }
    else {
        if (isLeaf) {
            setIcon(getLeafIcon());
        } else if (expanded) {
            setIcon(getOpenIcon());
        } else {
            setIcon(getClosedIcon());
        }
    }
    
         
return c;

        
}

if not, as stated above we only need to set the 3 icon types:

public java.awt.Component getTreeCellRendererComponent(javax.swing.JTree tree, Object value, boolean selected, boolean expanded, boolean isLeaf, int row, boolean focused) {

    DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
    MyTreeNodeWrapper treeNodeWrapper = (MyTreeNodeWrapper ) node.getUserObject();
    Icon icon = treeNodeWrapper .getIcon();

    setOpenIcon(icon);
    setClosedIcon(icon);
    setLeafIcon(icon);

    
Component c = super.getTreeCellRendererComponent(tree, value, selected, expanded, isLeaf, row, focused);
         
return c;

        
}
Adrian Faur
  • 61
  • 1
  • 2