4

I would like to override the background color of headers in JTables when using the Nimbus L&F. I'm effectively "theming" the Nimbus L&F, i.e. making small adjustments to it.

Whatever I try it doesn't seem to have effect.

Here's an SSCCS :

public class MyTest {

    public static void main(String[] args) {
        new MyTest();
    }

    public MyTest() {
        try {
            UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel");
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
            Logger.getLogger(MyTest.class.getName()).log(Level.SEVERE, null, ex);
        }

        UIManager.put("TableHeader.background", Color.RED);

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame("Test");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        public TestPane() {
            DefaultTableModel model = new DefaultTableModel(
                    new Object[][]{
                        {"hhvt ", "er sdf", "sfdg"},
                        {"hyshg ", "dh sdf", "jer"}
                    },
                    new Object[]{"Col A", "Col B", "Col C"}
            );
            JTable table = new JTable(model);

            setLayout(new BorderLayout());
            add(new JScrollPane(table));
        }

    }

}

Here's the result:

enter image description here

I'm well aware that Nimbus is a Synth L&F so it uses Painters for just about anything. I bet I could override some Painter in UIManager but I don't want to redo a Painter from scratch. The Painters in Nimbus are quite advanced, they use gradients and what have you. I would like to take advantage of that. It's just the color I would like to change.

mKorbel
  • 109,525
  • 20
  • 134
  • 319
peterh
  • 18,404
  • 12
  • 87
  • 115
  • 1
    I'm think that a few times here incl. detailed description about, hint ---> is still about XxxRenderer not about keys in UIManager (why bothering with keys that aren't accesible, or setting hasn't any effect without awfull effort) – mKorbel Nov 10 '14 at 10:04
  • for Nimbus L&F is better to use standard renderers/highlighter concept designated for Metal L&F – mKorbel Nov 10 '14 at 10:06
  • @mKorbel. I'm afraid I do not understand any of your comments. Anyway, an update: From looking at the source it seems the table header backgrounds in Nimbus are hardcoded to pick up the following colors : `nimbusBorder`, `nimbusBlueGrey`, `nimbusFocus` and `nimbusBase`. It then creates gradients out of these. Different colors for different states, etc. But these are the colors used in total. Everything is derived off these. Problem is that I don't want to change those as such, I only want to influence table headers. – peterh Nov 10 '14 at 10:17
  • `I only want to influence table headers.` hint ---> is still about XxxRenderer – mKorbel Nov 10 '14 at 12:22
  • @mKorbel. I'm afraid you lost me. Are you saying this can't be done without setting specific renderers on each `JTable` ? I was hoping to be able to do this by "skinning", i.e. without changing any source code, only change the L&F class or set global UIManager properties or the like. – peterh Nov 10 '14 at 12:51
  • 1
    `Are you saying this can't be done without setting specific renderers`- [again why bothering e.g. example, half way, for production code required own xml schema, injection for Nimbus (search for aephyr - http://aephyr.googlecode.com/svn/trunk/)](http://stackoverflow.com/a/5884987/714968), `Are you saying this can't be done without setting specific renderers` - instead of to use 1. standard TableCellRenderer for that (then you can to override all Key & Mouse Events and corrrectly), 2. a few L&F that are based on Nimbus has solved access to all keys (including JTabbedPane:-) – mKorbel Nov 10 '14 at 13:05
  • possible solution ---> download Substance L&F, only code source, there was one example (there are a few code examples) that allows / offering to switch between all good L&Fs in current JFrame – mKorbel Nov 10 '14 at 13:14
  • I would like to use Nimbus, not Substance. Substance predates Nimbus with many years and doesn't have the fancy look that Nimbus has, IMHO. – peterh Nov 10 '14 at 13:56
  • two mistakes, 1. Substance has better and intesive buggy history (with comparing in former Sun was Nimbus quite ignored) and has a few excelent themes, NImbus has a few new bugs in Java7 (JLabel, JButton and most of keys aren't refresehed from nimbus base) 2. Sunstance is alive, has Java7 version, 3. reason for linking tha Substance is fact that in one JFrames window is possible to test and to compare all good custom L&Fs (required to download before) 4. again to search for custom L&Fs based on Nimbus, keys are accesible, settable – mKorbel Nov 10 '14 at 14:08

1 Answers1

2

Here's a possible - but quite ugly - solution.

Nimbus relies heavily on Painters. The reason why Nimbus looks good is because it uses gradients, shadows and what not. That's the job of the Painter. We really, really don't want to do our own Painters. The Nimbus Painters are quite complex and produce beautiful results. So we want to leverage them. Not do them ourselves!

Nimbus has a lot of auto-generated source code. All source code is generated off the skin.laf XML file (which is in the JDK source) but the XML file is not used at runtime. Most of the auto-generated source files are in fact type-specific Painters. For example there's a painter class for TableHeaderRendererPainter (a painter responsible for painting table headers) and so on. The problem is that all auto-generated source code is package-private.

Painters are set when an instance of NimbusLookAndFeel is initialized. They don't change after this.

From skin.laf file we can see what colors are used for what. In our case we can see it's really the nimbusBlueGrey color that governs the background color of the table headers. We can't just change the value of nimbusBlueGrey as that would affect everything in Nimbus that uses this color. So we need to come up with something else. And this is where it gets ugly.

In the specific case we're interested in table headers as they look by default (i.e. when the mouse is not over them, the table is not disabled, the column header is not pressed, etc). So this is what we'll concentrate on below. But the technique would be the same for any other type of special decoration that somebody would want to do.

The technique is to first start up a temporary instance of the NimbusLookAndFeel. We do this only so we can 'steal' one of the Painters it has generated. We than safe keep this Painter and then start the NimbusLookAndFeel for real. Now we can replace our specific Painter so that we swap in the one we saved previously.

public class MyTest {

    public static void main(String[] args) throws UnsupportedLookAndFeelException {
        new MyTest();
    }

    public MyTest() throws UnsupportedLookAndFeelException {

        // Start dummy instance of L&F
        NimbusLookAndFeel nimbusTmp = new NimbusLookAndFeel();
        Object nimbusBlueGreyOrg = UIManager.get("nimbusBlueGrey");  // original value
        UIManager.put("nimbusBlueGrey", Color.RED);   // the color we want
        try {
            UIManager.setLookAndFeel(nimbusTmp);
        } catch (UnsupportedLookAndFeelException ex) {
            Logger.getLogger(MyTest.class.getName()).log(Level.SEVERE, null, ex);
        }
        Object painter = UIManager.get("TableHeader:\"TableHeader.renderer\"[Enabled].backgroundPainter");

        // We've got what we came for. Now unload the dummy.
        UIManager.getLookAndFeel().uninitialize(); // important to avoid UIDefaults change listeners firing
        UIManager.put("nimbusBlueGrey", nimbusBlueGreyOrg);  // revert

        // Load the L&F for real. 
        UIManager.setLookAndFeel(new NimbusLookAndFeel());

        // Swap in the value we saved previously
        UIManager.put("TableHeader:\"TableHeader.renderer\"[Enabled].backgroundPainter", painter);

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame("Test");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);

            }
        });
    }

    public class TestPane extends JPanel {

        public TestPane() {
            DefaultTableModel model = new DefaultTableModel(
                    new Object[][]{
                        {"hhvt ", "er sdf", "sfdg"},
                        {"hyshg ", "dh sdf", "jer"}},
                    new Object[]{"Col A", "Col B", "Col C"}
            );
            JTable table = new JTable(model);    
            setLayout(new BorderLayout());
            add(new JScrollPane(table));
        }

    }

}

Not proud of this, but it works. Anyone with better ideas ?

peterh
  • 18,404
  • 12
  • 87
  • 115