2

I've got a JTree with icons on some of the nodes within the tree. They appear and work fine but when I select a node with a icon, the renderer does not render the entire node selected but appears to have an offset applied to it, as if it thinks the icon is still to the left of the node as below:

Example of the rendering fault

The code for the renderer (which extends DefaultTreeCellRenderer) is below:

public ProfileTreeRenderer() {
    super.setLeafIcon(null);
    super.setClosedIcon(null);
    super.setOpenIcon(null);
}

@Override
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
    Component c = super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
    if (c instanceof JLabel) {
        JLabel label = (JLabel) c;
        label.setHorizontalTextPosition(SwingConstants.LEADING);
    }

    if(sel && !hasFocus) {
        setBackgroundSelectionColor(UIManager.getColor("Panel.background"));
        setTextSelectionColor(UIManager.getColor("Panel.foreground"));
    } else {
        setTextSelectionColor(UIManager.getColor("Tree.selectionForeground"));
        setBackgroundSelectionColor(UIManager.getColor("Tree.selectionBackground"));
    } 

    if (value instanceof ProfileNode) {
        ProfileNode node = (ProfileNode) value;
        if (node.isUsed() && !sel) {
            c.setForeground(Color.GRAY);
        }

        if (node.getIcon() != null) {
            setIcon(node.getIcon());
        }
    }
}

I cannot see why the renderer would apply this offset, so can anyone offer a way to get the node fully selected with the icon? The SSCCE code for the tree itself is below.

public class Example extends JDialog {
    public Example() {
        JTree tree = new JTree(createModel());
        tree.setCellRenderer(new ProfileTreeRenderer());

       setLayout(new BorderLayout());
       add(tree, BorderLayout.CENTER);
    }

    private TreeModel createModel() {
        ProfileNode root = new ProfileNode("Profiles");

        ProfileNode userA = new ProfileNode("Example User A");
        ProfileNode userB = new ProfileNode("Example User B");

        // You'll need to subsitute your own 16x16 icons here
        userA.setIcon(ImageSet.USER_ICON);
        userB.setIcon(ImageSet.USER_ICON);

        root.add(userA);
        root.add(userB);

        return new DefaultTreeModel(root);
    }

    public static void main(String[] args) {
       EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                new Example().setVisible(true);
            }
        });
    }
}

The ProfileNode class:

public class ProfileNode extends DefaultMutableTreeNode {

   @Getter private String labelDisplay;
   @Getter @Setter private ImageIcon icon;

   @Getter @Setter private boolean isUsed = false;

   public ProfileNode(String labelDisplay) {
       this.labelDisplay = labelDisplay;
   }

   @Override
   public String toString() {
      return labelDisplay;
   }
}

Thanks in advance.

Ocracoke
  • 1,718
  • 3
  • 24
  • 38

1 Answers1

2

The problem is that the DefaultTreeCellRenderer uses its icon property exclusively for the open/leaf/close icons: it assumes that - if the icon != null - it's at the start of the component (even if it isn't) and adjusts the selection accordingly. You need to re-adjust ... or use SwingX renderers :-)

Something like:

JXTree tree = new JXTree();
tree.expandAll();
IconValue iv = new IconValue() {

    Icon icon = XTestUtils.loadDefaultIcon("green-orb.png");
    @Override
    public Icon getIcon(Object value) {
        return value.toString().contains("s") ? icon : null;
    }

};
StringValue converter = new MappedValue(StringValues.TO_STRING, iv);
WrappingProvider provider = new WrappingProvider(IconValues.NONE, converter);
// hacking around missing api ...
LabelProvider wrappee = (LabelProvider) provider.getWrappee();
wrappee.getRendererComponent(null).setHorizontalTextPosition(JLabel.LEADING);
TreeCellRenderer r = new DefaultTreeRenderer(provider);
tree.setCellRenderer(r);
kleopatra
  • 51,061
  • 28
  • 99
  • 211
  • I couldn't make this answer work in the example or in the real code. It did, however, prompt me to find the reason for the behaviour in DefaultTreeCellRenderer and have since developed a more generic renderer to fix the problem I was having. I will still accept this answer as I can imagine that with a bit more work, I could have made it work in my code. – Ocracoke Oct 16 '13 at 09:41
  • hmm ... should work as-is (you are using SwingX, if I remember correctly?) – kleopatra Oct 16 '13 at 09:43
  • Yeah, still didn't come off though. I could be using a old version of SwingX but I doubt that could be it. It did't work correctly when there was no icon and didn't use the toString of the node in the tree. – Ocracoke Oct 16 '13 at 09:56
  • hmm ... should work since ages. What exactly is not working? Can you show a SSCCE that demonstrates the issue with this code? – kleopatra Oct 16 '13 at 10:00
  • If you put your answer into the Example class, replacing the line tree.setCellRenderer(new ProfileTreeRenderer()); then you see what I mean. The getIcon method is often passed in a null as well. – Ocracoke Oct 16 '13 at 10:18
  • didn't - but looked at your node implementation (which is a bit weird, subclassing DefaultMutableNode and not having a user object): By default, the wrappingProvider "unwraps" a DefaultMutableTreeNode and passes the userObject to the converters. Disable that unwrapping will pass-over the node itself. – kleopatra Oct 16 '13 at 10:27