Reputation: 11920
Folks,
I am trying to create a gradient JTree control. The following code mostly works except that the background for the tree cell is not transparent. I would appreciate it if someone call tell me what is it that I am not doing right.
Thank you in advance for your help.
Regards,
Peter
package TestPackage;
import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import java.awt.*;
public class Test {
public Test() {
JFrame frame = new JFrame();
JPanel framePanel = new JPanel();
framePanel.setLayout(new BorderLayout());
frame.setContentPane(framePanel);
DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode("Item");
DefaultMutableTreeNode childNode = new DefaultMutableTreeNode("Child");
rootNode.add(childNode);
GradientTree tree = new GradientTree(rootNode);
// JTree tree = new JTree(rootNode);
// tree.setBackground(Color.blue);
tree.setCellRenderer(new MyRenderer());
JScrollPane scroll = new JScrollPane(tree);
scroll.setOpaque(false);
framePanel.add(scroll, BorderLayout.CENTER);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new Test();
}
});
}
@SuppressWarnings("serial")
public static class GradientTree extends JTree {
public GradientTree(DefaultMutableTreeNode node) {
super(node);
}
@Override
protected void paintComponent(Graphics g) {
int h = getHeight();
int w = getWidth();
GradientPaint gradientPaint = new GradientPaint(0, 0, Color.LIGHT_GRAY, 0, h, Color.WHITE);
Graphics2D g2D = (Graphics2D) g;
g2D.setPaint(gradientPaint);
g2D.fillRect(0, 0, w, h);
this.setOpaque(false);
super.paintComponent(g);
this.setOpaque(true);
}
}
@SuppressWarnings({"serial" })
private class MyRenderer extends DefaultTreeCellRenderer {
public MyRenderer() {
this.setOpaque(false);
this.setForeground(Color.RED);
}
public Component getTreeCellRendererComponent(
JTree tree,
Object value,
boolean sel,
boolean expanded,
boolean leaf,
int row,
boolean hasFocus) {
super.getTreeCellRendererComponent(
tree, value, sel,
expanded, leaf, row,
hasFocus);
return this;
}
}
}
Upvotes: 1
Views: 3881
Reputation: 15692
I really hesitate to advance this hypothesis in the presence of these Swing experts... but could it be that more recent JDKs have actually rectified this problem?
I have code like this in my app and it seems to work fine... the JTree's background shines through perfectly... NB Jython, but should be understandable:
def getTreeCellRendererComponent( self, tree, value, selected, expanded, leaf, row, has_focus ):
super_comp = self.super__getTreeCellRendererComponent( tree, value, selected, expanded, leaf, row, has_focus )
super_comp.opaque = not selected
...
Java version is 1.7.0_079
Upvotes: 0
Reputation: 1
How about extending the DefaultTreeCellRenderer like this:
public class MyRenderer extends DefaultTreeCellRenderer {
public Component getTreeCellRendererComponent(JTree tree, Object value,
boolean isSelected, boolean expanded, boolean leaf, int row,
boolean hasFocus) {
JComponent c = (JComponent) super.getTreeCellRendererComponent(tree, value, isSelected, expanded, leaf, row, hasFocus);
c.setOpaque(true);
return c;
}
}
Setting c.setOpaque(true); seems to solve it.
Upvotes: 0
Reputation: 51536
Answer
(expanding on @Mad's answer, the longish analysis of the underlying problems is at the end):
If you want the global property to be effective in a defaultTreeCellRenderer set manually to the tree, that renderer has to call updateUI again , f.i.
UIManager.put("Tree.rendererFillBackground", false);
...
TreeCellRenderer r = new DefaultTreeCellRenderer() {
{
updateUI();
}
};
tree.setCellRenderer(r);
If you do not want to change the global setting and have the transparent renderers only some tree instances - the options are
Tricksing code:
TreeCellRenderer r = new DefaultTreeCellRenderer() {
{
updateUI();
}
@Override
public void updateUI() {
Object old = UIManager.get("Tree.rendererFillBackground");
try {
UIManager.put("Tree.rendererFillBackground", false);
super.updateUI();
} finally {
UIManager.put("Tree.rendererFillBackground", old);
}
}
};
Analysis
starting from my comment:
Weirdly, the mere act of setting CellRenderer (vs. letting the ui install its favourits) makes the flag ineffective
This puzzle is resolved:
DefaultTreeCellRenderer has the intention to set its fillBackground field from the setting in the UIManager - but fails doing so on instantiation. The reason is a - all too common error ;-) - in actually doing so in super's instantiation, due to calling a overridden method in super's constructor:
// this is implemented in DefaultTreeCellRenderer
// but called in JLabel constructor
public void updateUI() {
....
// we are in JLabel, that is fillBackground not yet known
fillBackground = DefaultLookup.getBoolean(this, ui, "Tree.rendererFillBackground", true);
...
}
then later in the instantiation process, the field value is hardcoded:
private boolean fillBackground = true;
The net result is (assuming that we force access to the field, f.i. via reflection), the following passes always, irrespective of the setting in the UIManager.
DefaultTreeCellRenderer renderer = new DefaultTreeRenderer();
assertTrue(renderer.fillBackground);
With that the unusual thingy is: why does the setting in the UIManager has an effect when letting the ui install its default? Here the reason is that the renderers updateUI is called twice: once on instantiation and once in the tree's updateUI:
public void updateUI() {
setUI((TreeUI)UIManager.getUI(this));
// JW: at this point the renderer has its fillbackground hard-coded to true
SwingUtilities.updateRendererOrEditorUI(getCellRenderer());
// JW: now it's updateUI has been called again, and correctly set to the
// UIManager's value
SwingUtilities.updateRendererOrEditorUI(getCellEditor());
}
BTW: this instantiation mess seems to be introduced in jdk7 ... most probably (didn't check, though) the default settings of the renderer colors not working as well.
Upvotes: 2
Reputation: 347332
This is a real pain. The DefaultTreeCellRenderer
will ignore the opaque
value and fill it's contents anyway. However, there is a flag you can try. I've done it in the past, but don't have time to test it...
Try UIManager.put("Tree.rendererFillBackground", false)
. Try and do this before anything is renderer, but after any look and feel settings have been applied.
UPDATED
It is very important to set this property BEFORE you create any trees
Without | With...
public class TestTreeRenderer {
public static void main(String[] args) {
new TestTreeRenderer();
}
public TestTreeRenderer() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TreePane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TreePane extends JPanel {
private JTree tree;
public TreePane() {
// THIS IS VERY IMPORTANT
// You must set this BEFORE creating ANY trees!!
UIManager.put("Tree.rendererFillBackground", false);
setLayout(new BorderLayout());
tree = new JTree();
tree.setBackground(Color.BLUE);
System.out.println("Loading files...");
File root = new File("/etc");
DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode(root.getName());
for (File file : root.listFiles()) {
rootNode.add(new DefaultMutableTreeNode(file.getName()));
}
System.out.println("Loading model");
DefaultTreeModel model = new DefaultTreeModel(rootNode);
tree.setModel(model);
add(new JScrollPane(tree));
}
}
}
Upvotes: 5