I am wanting to create a custom JTree where it's nodes are JProgressBar's I have this working just like I want it
However you'll notice that it's missing it's open/closed icon, which I also want to be a custom +/- icon instead of the standard expandable folder. Not entirely sure what I'm doing wrong (Just developing the concept at the moment before prettifying the code)
package com.testingarea;
import java.awt.Component;
import java.awt.Graphics;
import java.util.HashMap;
import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.UIManager;
import javax.swing.plaf.IconUIResource;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeCellRenderer;
public class TestTrees {
public static void main(String args[]) {
JFrame frame = new JFrame("Tree");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
ProgressTreeCellRenderer renderer = new ProgressTreeCellRenderer();
JProgressBar progressBar = new JProgressBar(0, 100);
progressBar.setValue(50);
progressBar.setStringPainted(true);
DefaultMutableTreeNode top =
new DefaultMutableTreeNode(progressBar);
renderer.getNodeMap().put(top, new ProgressTreeCellRenderer2(progressBar));
createNodes(top, renderer);
DefaultTreeModel model = new DefaultTreeModel(top);
JTree tree = new JTree(model);
tree.setCellRenderer(renderer);
JScrollPane pane = new JScrollPane(tree);
tree.clearSelection();
frame.getContentPane().add(pane);
frame.setSize(250, 250);
frame.setVisible(true);
}
private static void createNodes(DefaultMutableTreeNode top, ProgressTreeCellRenderer renderer) {
JProgressBar progressBar1 = new JProgressBar(0, 100);
progressBar1.setValue(25);
progressBar1.setStringPainted(true);
DefaultMutableTreeNode one =
new DefaultMutableTreeNode(progressBar1);
top.add(one);
renderer.getNodeMap().put(one, new ProgressTreeCellRenderer2(progressBar1));
JProgressBar progressBar2 = new JProgressBar(0, 100);
progressBar2.setValue(25);
progressBar2.setStringPainted(true);
DefaultMutableTreeNode two =
new DefaultMutableTreeNode(progressBar2);
top.add(two);
renderer.getNodeMap().put(two, new ProgressTreeCellRenderer2(progressBar2));
}
}
@SuppressWarnings("serial")
class ProgressTreeCellRenderer extends DefaultTreeCellRenderer {
private HashMap<DefaultMutableTreeNode, ProgressTreeCellRenderer2> nodeMap = new HashMap<DefaultMutableTreeNode, ProgressTreeCellRenderer2>();
@Override
public Component getTreeCellRendererComponent(JTree tree, final Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
if(nodeMap.containsKey(value)) {
//I was expecting this to work, alas it does not?
setClosedIcon(new IconUIResource(new NodeIcon('+')));
setOpenIcon(new IconUIResource(new NodeIcon('-')));
return nodeMap.get(value).getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
}
return super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
}
public HashMap<DefaultMutableTreeNode, ProgressTreeCellRenderer2> getNodeMap() {
return nodeMap;
}
}
class ProgressTreeCellRenderer2 implements TreeCellRenderer {
private JProgressBar _progressBar;
private DefaultTreeCellRenderer _defaultRenderer;
public ProgressTreeCellRenderer2(JProgressBar progressBar) {
_progressBar = progressBar;
_defaultRenderer = new DefaultTreeCellRenderer();
}
@Override
public Component getTreeCellRendererComponent(JTree tree, final Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
Component render = _defaultRenderer.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
final JPanel panel = new JPanel();
BoxLayout layout = new BoxLayout(panel, BoxLayout.X_AXIS);
panel.setLayout(layout);
panel.add(_progressBar);
render = panel;
return render;
}
}
class NodeIcon implements Icon {
private static final int SIZE = 9;
private char type;
public NodeIcon(char type) {
this.type = type;
}
public void paintIcon(Component c, Graphics g, int x, int y) {
g.setColor(UIManager.getColor("Tree.background"));
g.fillRect(x, y, SIZE - 1, SIZE - 1);
g.setColor(UIManager.getColor("Tree.hash").darker());
g.drawRect(x, y, SIZE - 1, SIZE - 1);
g.setColor(UIManager.getColor("Tree.foreground"));
g.drawLine(x + 2, y + SIZE / 2, x + SIZE - 3, y + SIZE / 2);
if (type == '+') {
g.drawLine(x + SIZE / 2, y + 2, x + SIZE / 2, y + SIZE - 3);
}
}
public int getIconWidth() {
return SIZE;
}
public int getIconHeight() {
return SIZE;
}
}
In case of
ProgressTreeCellRenderer
callingsetClosedIcon
orsetOpenIcon
has no effect as you are returning another renderer that is retrieved from anodeMap
. You'd have to add the icon in the implementation ofProgressTreeCellRenderer2
. For example, try this to add an icon to the panel inProgressTreeCellRenderer2
:However, note that your current implementation is very expensive. Not only you have a new renderer per node, but also, you reallocate the panel instance inside
getTreeCellRendererComponent()
. Try to use a single renderer for all nodes with preallocated controls where you only update properties based on the arguments ofgetTreeCellRendererComponent
. See How to Use Trees for renderer details and examples of a tree display customization.Take a look at this answer (by @mKorbel) that illustrates a single progress bar renderer in a table.