简体   繁体   中英

How to correctly colour a JTree and all nodes

Let me explain what I need and how far I've come:

I have a JTree with a custom model and some nice icons and everything works as it should, however, the default colours of the tree (white) do not fit in with the GUI that I am trying to design and I'd like to be able to change its background colour.

This has proven to be rather a chore!

I have seen lots of explanations for this around the net, and they seem to revolve around either extending the DefaultTreeCellRenderer or the BasicTreeUI , but there is some suggestion that these are hacks and should be avoided, although there is little explanation of why they are hacks or what should be done instead.

In any case none of the suggested methods seems to work very well because I always end up with one of two scenarios:

The colour is set in the tree but there are nasty gaps between the end of tree labels and the right side of the tree, and between the labels and their icons, and the plus/minus widgets for expanding the tree. In this case it is suggested that full row selection be implemented.

The other scenario has full row selection implemented and the nodes are correctly coloured and full row selection works, but the icons are either missing entirely, along with the plus/minus widgets, or there remains a nasty white border around both.

My own attempts at this have failed and I am currently left with the following code, which colours the tree and the nodes, but still leaves nasty white borders at the end of tree labels and between all icons.

final Color MainBg = new Color(213,220,228);
KTree.setCellRenderer(new DefaultTreeCellRenderer()
{
    @Override
    public Component getTreeCellRendererComponent(JTree pTree, Object pValue, boolean pIsSelected, boolean pIsExpanded, boolean pIsLeaf, int pRow, boolean pHasFocus)
    {
            super.getTreeCellRendererComponent(pTree, pValue, pIsSelected, pIsExpanded, pIsLeaf, pRow, pHasFocus);
            setBackgroundNonSelectionColor(MainBg);
            setBackgroundSelectionColor(MainBg);
            setTextNonSelectionColor(Color.BLACK);
            setTextSelectionColor(Color.BLACK);
            ImageIcon tDoc = createImageIcon("images"  + File.separator + "document.gif","document");
            ImageIcon tOpen = createImageIcon("images"  + File.separator + "book_open.gif","book open");
            ImageIcon tClosed = createImageIcon("images"  + File.separator + "bookclosed.png","book closed");
            setClosedIcon(tClosed);
            setOpenIcon(tOpen);
            setLeafIcon(tDoc);
            putClientProperty("Tree.collapsedIcon", tDoc);
            putClientProperty("tree.expandedIcon", tOpen);
        return (this);
    }
});

My question(s):

1) Naturally I would appreciate some help getting a colour set on the JTree and I am open to suggestion, although if you plan to post a link there is a good chance I've already been there in the last few days, but also ...

2) Id appreciate a definitive explanation of precisely what is involved in the process. Iv read through many tutorials concerning the JTree but none focus for long (if at all) on the question of completely colouring a tree, which is what I am looking for, and there seems to be some discussion about precisely what is required, for example full row selection or not, subclassing or not, etc.

I would also like to understand why certain things are considered 'hacks' and what the alternatives are.


EDIT - A Solution

I am adding this section to show my progress in getting this sorted out and to perhaps give others a little head start on what has been a rather fiddly process. Iv spent days trying to get this to work properly with the System L&F and I have been in circles a few times. No doubt much of this is down to inexperience so if this saves someone else a few hair pulling sessions, all the better!

BTW: I am more than happy to hear critique if I have done something that is regarded as a hack, but please provide a (working) alternative and some clear reasoning behind the label of Hack.

Okay, lets start: I want to have the system L&F set because I'm none too fond of the other options and because I like users to see a program that matches things that they are used to. I set the L&F like this before I do anything else:

try
    {
        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
    }
catch (Exception e) { return; }

This sets the the JTree to the standard for whatever OS you are using. This seems to include setting the background of the tree itself, as well as the background for node selections and so on. You are ready to go if all you want is the standard JTree , but while I want to see system based scrollbars etc, I also want to see colours other than white for the background.

I set the background to a nice blue colour like this (Note that the colour is used again in a moment, hence the final declaration):

final Color MainBg = new Color(213,220,228);
    KTree.setBackground(MainBg);

What I have now is a JTree with a nice blue background which covers only those areas of the tree that do not contain nodes, which remain white according to the settings of the L&F. I now need to set some colour into the nodes themselves and to do this I need to override the getTreeCellRendererComponent of the DefaultTreeCellRenderer with the following code:

final Color SellBg = new Color(232,235,237);
final Color HiliBg = new Color(150,196,246);

KTree.setCellRenderer(new DefaultTreeCellRenderer()
{

    @Override
    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);
        setBackgroundNonSelectionColor(MainBg);
        setBackgroundSelectionColor(SellBg);
        setTextNonSelectionColor(Color.BLACK);
        setTextSelectionColor(Color.BLACK);
        ImageIcon tDoc = createImageIcon("images"  + File.separator + "document.gif","document");
        ImageIcon tOpen = createImageIcon("images"  + File.separator + "book_open.gif","open");
        ImageIcon tClosed = createImageIcon("images"  + File.separator + "bookclosed.png","closed");
        setClosedIcon(tClosed);
        setOpenIcon(tOpen);
        setLeafIcon(tDoc);
        setBorderSelectionColor(HiliBg);
        return this;
    }
});

Here is the createImageIcon from above. This came direct from a tutorial, but note that this is edited before I'm finished with it (So don't copy and paste this code!):

protected ImageIcon createImageIcon(String path, String description)
{
    java.net.URL imgURL = getClass().getResource(path);
    if (imgURL != null)
    {
        return new ImageIcon(imgURL, description);
    }
    else
    {
        System.err.println("Couldn't find file: " + path);
        return null;
    }
}

If you are following the code then at this stage you will see that I have set various colours for the tree nodes, as well as adding some icons to them. All this works very well, however there are now nasty white blocks between the ends of the node text and the edge of the JTree itself, and also between the node text and icon, and the plus/minus widgets of the tree. These white blocks also run through the tree as it expands.

This is pretty much where I came in and the hair pulling began. Thanks to Jacob' suggestion and MadProgrammer' tip (thanks guys) I was on the right track, but this is where things got strange, most likely because I had already been messing about with the JTree to try and get things working. Rolling the code back to a previous version and starting this section again is what helped to get things working.

Quick note: Please, if I'm doing this the wrong way, let me know!

It appeared that to change the white parts of the JTree I was going to need to override the BasicTreeUI , which I did like this:

KTree.setUI(new javax.swing.plaf.basic.BasicTreeUI()
{
    @Override
    public Rectangle getPathBounds(JTree tree, TreePath path)
    {
        if(tree != null && treeState != null)
        {
            return getPathBounds(path, tree.getInsets(), new Rectangle());
        }
        return null;
    }
    private Rectangle getPathBounds(TreePath path, Insets insets, Rectangle bounds)
    {
        bounds = treeState.getBounds(path, bounds);
        if(bounds != null)
        {
            bounds.width = tree.getWidth();
            bounds.y += insets.top;
        }
        return bounds;
    }

});

I must state that this is not my code - I found this on one of the many tutorial sites and it worked as suggested and my JTree now had a correctly painted background, however, the plus/minus widgets had disapeared!

There is most likely an easy way to set these widgets from within the code above, however I was mindful of Jacob's warning about messing with the BasicTreeUI so I placed the following code directly after I set the L&F:

ImageIcon clapsed = createImageIcon("images"  + File.separator + "plus.gif","closed");
ImageIcon clopen = createImageIcon("images"  + File.separator + "minus.gif","open");
UIManager.getLookAndFeelDefaults().put("Tree.collapsedIcon",clapsed);
UIManager.getLookAndFeelDefaults().put("Tree.expandedIcon",clopen);
UIManager.getLookAndFeelDefaults().put("Tree.paintLines", true);
UIManager.getLookAndFeelDefaults().put("Tree.leftChildIndent",7);
UIManager.getLookAndFeelDefaults().put("Tree.lineTypeDashed",true)

This sets the plus/minus widget icons to some of my own and sets the type of painted lines I want between the elements in the tree. Note that at this point I got an error because the non-static createImageIcon from earlier could not be accessed. I had to edit the code like this to allow both calls to this method:

protected static ImageIcon createImageIcon(String path, String description)
{
    Class<?> cl=new Object(){}.getClass().getEnclosingClass();
        java.net.URL imgURL = cl.getResource(path);
        if (imgURL != null)
        {
            return new ImageIcon(imgURL, description);
        }
        else
        {
            System.err.println("Couldn't find file: " + path);
            return null;
        }
}

I now (finally!) have a JTree , with the System L&F set, and with a colour set for all elements of the tree.

There are still unresolved issues:

I do not (yet) fully understand the BasicTreeUI() code - this has given me the colours I require but also seems to have given me full row selection. However I have been unable to get the selection colour to span the whole distance. I intend to play with this later, but I will be cautious thanks to Jacob' warning - If anyone has any hard and fast rules concerning this I'm more than happy to hear them.

I also deliberately set the L&F so that users would see something with which they were familiar, albeit with different colours. However, I have been forced to add my own icons to the plus/minus tree widgets, which is not really an issue, but rather a little niggle. This may well be cleared up at a later time.

My other concern is that when setting values with UIManager I am only able to set certain keys, the others apparently being ignored. MadProgrammer did make the point that using Nimbus could mess these keys up a little, so perhaps using the system L&F has its own quirks.

Anyway, that is the current result of my efforts. I apologise for the length of this post, but I hope that this can help someone else and also that if I've done something in the wrong way, that someone can point out to me the correct way to go about it.

Regards

MVK

If you've already overridden getTreeCellRendererComponent so that the tree items have the color you want, you can use

UIManager.getLookAndFeelDefaults().put("Tree.background", new ColorUIResource(aColor);

to change the color of "unoccupied" space in the tree. Note that this will affect all future trees. The component UI classes use this property and many like it when drawing their components. To find out which keys exist you can do something like this:

for (Entry<Object, Object> entry : UIManager.getLookAndFeelDefaults().entrySet()) {
    System.out.println(entry.getKey() + " : " + entry.getValue());
}

As for your other questions, I'd never consider overriding getTreeCellRendererComponent to be a hack, but carelessly extending BasicTreeUI may cause unexpected behavior on some look and feels.

If all you're looking for is a way to have both System L&F and colored background + text in a JTree this is all the code you should need:

        try {
        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        UIManager.getLookAndFeelDefaults().put("Tree.background", new ColorUIResource(Util.BACKGROUND));
        UIManager.getLookAndFeelDefaults().put("Tree.textBackground", new ColorUIResource(Util.BACKGROUND));
    } catch (ClassNotFoundException | InstantiationException
            | IllegalAccessException | UnsupportedLookAndFeelException e) {
        e.printStackTrace();
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM