Nimbus and alternate row colors
Asked Answered
F

2

5

I don't understand how alternate row coloring works in Nimbus. It seems just crazy!!! I would like to clear things up here.

For the demonstration, let's say that we want a JTable that alternate Red and Pink rows (and I don't care which color is the first one).

Without redefining custom cellRenderers that perform their own "modulo 2" thing, and without overriding any method from JTable, I want to list the mandatory steps between starting one's application and getting a JTable with custom alternate row colors using Nimbus properties only.

Here are the steps I expected to follow:

  1. Install the Nimbus PLAF
  2. Customize the "Table.background" nimbus property
  3. Customize the "Table.alternateRowColor" nimbus property
  4. Create a JTable with simple data/header
  5. Wrap the jTable in a JScrollPane and add it to the JFrame
  6. Show the JFrame

Here the source code:


public class JTableAlternateRowColors implements Runnable {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new JTableAlternateRowColors());
    }

    @Override
    public void run() {
        try {
            UIManager.setLookAndFeel(new NimbusLookAndFeel());
        } catch (UnsupportedLookAndFeelException e) {
            e.printStackTrace();
        }

        UIManager.getDefaults().put("Table.background", Color.RED);
        UIManager.getDefaults().put("Table.alternateRowColor", Color.PINK);

        final JFrame jFrame = new JFrame("Nimbus alternate row coloring");
        jFrame.getContentPane().add(new JScrollPane(new JTable(new String[][] {
                {"one","two","three"},
                {"one","two","three"},
                {"one","two","three"}
        }, new String[]{"col1", "col2", "col3"}
        )));
        jFrame.setSize(400, 300);
        jFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        jFrame.setVisible(true);
    }
}

This is JDK6 code. Can somebody tell me goes wrong here?


As per @kleopatra's comment and the contribution of the whole community here's a/the way to get alternate row coloring using only Nimbus properties

public class JTableAlternateRowColors implements Runnable {

public static void main(String[] args) {
    SwingUtilities.invokeLater(new JTableAlternateRowColors());
}

@Override
public void run() {
    try {
        UIManager.setLookAndFeel(new NimbusLookAndFeel());
    } catch (UnsupportedLookAndFeelException e) {
        e.printStackTrace();
    }

    UIManager.put("Table.background", new ColorUIResource(Color.RED));
    UIManager.put("Table.alternateRowColor", Color.PINK);
    UIManager.getLookAndFeelDefaults().put("Table:\"Table.cellRenderer\".background", new ColorUIResource(Color.RED));

    final JFrame jFrame = new JFrame("Nimbus alternate row coloring");
    final JTable jTable = new JTable(new String[][]{
            {"one", "two", "three"},
            {"one", "two", "three"},
            {"one", "two", "three"}
    }, new String[]{"col1", "col2", "col3"});
    jTable.setFillsViewportHeight(true);
    jFrame.getContentPane().add(new JScrollPane(jTable));
    jFrame.setSize(400, 300);
    jFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    jFrame.setVisible(true);
}

}

Fredi answered 22/10, 2012 at 9:18 Comment(11)
Nothing goes wrong. The code works fine for me, except that with jdk6 first and third rows are pink, while with jdk7 only the second is.Shulman
no there nothing crazy, have to accepting, that all Randerers works without restriction (for job that Renderer is implemented in API), for UIManager have to override proeprs Key(s) in Nimbus Default, sure sometimes Color, somewhere Painter, issue could be only with XxxUIResouces (basically has nothing to do with Questions 1- 8), every keys are properly described..., BTW kind form in the qeustion, especially points 1-8 are great addepts for vWorker.comCapone
@DenisTulskiy Not having rows alternating between Red and Pink is actually wrong. It's either me or Nimbus, but one of us is wrong!Fredi
@Capone My goal is to have a clear tutorial of what should be done to have proper alternate colors with Nimbus. If you know how to change my sample to make it work, please be specific.Fredi
@Pigelvy: oh, didn't notice that background should be red, it was white for me.Shulman
in such cases I usually had to dig down into Nimbus source code to see how it paints stuff. I'd recommend you to take that awesome journey too :)Shulman
@Fredi I was more than specifics, first two points are proper ways, did you bothering with research, everything is on this forum, Renderer works in all cased, sure restictions could methods (1sr sight) implemented on API (nothing wrong, mistakes, nor Bugs to use Renderer only for rendering)Capone
Not having rows alternating between Red and Pink is actually wrong add that sentence to your question, crystal balls are notoriously unreliable on Monday mornings ;-)Photochronograph
@Capone Just so you know: I search over and over, I debugged through Nimbus/Synth (DefaultTableCellRenderer, SynthTableUI, ...) and experimented with ColorUIResource instead of Color. All this without success. That's the reason I'm asking the community. The only right solution to this problem would involve Nimbus properties onlyFredi
@Fredi (whatever about buggy are strong words) there you have to override a basic nimbus colors too, have to accepting that JTable is compound JComponets, Nimbus is coumpond L&F, then you have to override childs too, don't do that, use SeaGlass, create own Color injector, don't do that, JTable.setBackground works, don't do that, use Renderer, use Rendere for reason that is created, no issue, talking about Renderer concepts, have to test if UIManager returns null???, for all JComponents where is Renderer implemented in APICapone
The only color that works with Table.background is pink. Nimbus devs like it fancy :)Shulman
P
12

Looks like the interference of several bugs ...

For changing both default table background and default striping, the expected (not only yours, mine as well) configuration of the UIManager (same for all LAFs which respect the alternateRow property) would be:

UIManager.put("Table.background", Color.RED);
UIManager.put("Table.alternateRowColor", Color.PINK);

Doesn't work, neither for Metal nor for Nimbus

  • in Metal: no striping, table is all red
  • in Nimbus: striping white/pink, that is the table background is ignored

Underlying reason for the first can be found in DefaultTableCellRenderer:

Color background = unselectedBackground != null
                        ? unselectedBackground
                        : table.getBackground();
if (background == null || background instanceof javax.swing.plaf.UIResource) {
    Color alternateColor = DefaultLookup.getColor(this, ui, "Table.alternateRowColor");
    if (alternateColor != null && row % 2 != 0) {
        background = alternateColor;
    }
}

It's logic is crooked: the alternate color is only taken if the table's background is a colorUIResource, a rather weak distinction. Anyway, it leads us to next try:

UIManager.put("Table.background", new ColorUIResource(Color.RED));
UIManager.put("Table.alternateRowColor", Color.PINK);

This looks fine (except the typical issue with a checkbox renderer, but that's yet another bug story ;-) for metal, still no luck for Nimbus.

Next step is look up Nimbus defaults which might be related, and apply (after! setting the LAF):

UIManager.getLookAndFeelDefaults().put("Table:\"Table.cellRenderer\".background", 
    new ColorUIResource(Color.RED));

Edit (as it was asked in the comments)

JXTable tries to side-step the problem entirely - its means for striping is a Highlighter retrieved from the HighlighterFactory. Needs to go dirty with Nimbus by removing the alternateRowColor property from the lookAndFeelDefaults and add it with a new key "UIColorHighlighter.stripingBackground"

Photochronograph answered 22/10, 2012 at 13:29 Comment(8)
+1 congratulations you got it working :). 1 question why does this not work because I tried it at first but no luck as you can see: UIManager.getDefaults().put("Table:\"Table.cellRenderer\".background", Color.RED);Overmatter
@David Kroukamp see my post here (+1 for basic stuff)Capone
And do you get all this hacking for free if you use SwingX and its highlighters ? I never encountered any problems with creating alternating row colors, but I do not use the standard JTableSavage
@Savage I see there only the issue with to override Key in UIManager, described very well in this answerCapone
@Capone it is described very well, but requires a certain amount of debugging to discover for something that should be an almost trivial use-case. Tables with alternating row colors is a pretty basic requirement when you design a tableSavage
@Savage JXTable tries to ignore the setting (and doesn't try too hard to cope with Nimbus, that's just tooo buggy)Photochronograph
I smell like an acceptable solution here! Very good @Photochronograph =D. I tested a little and the 3 properties you talk about can be set after Nimbus is installed (which is actually fine for me because we install Nimbus using the "swing.defaultlaf" system property). I'll update my original post with a complete example using your solution. I don't know if this will introduce some kind of regression down the road, but I think we've made good progress on the subject. Thanks to everyone!!Fredi
What if I only want to change at each JTable level? In case that I want to set different background to every table.Consummation
O
3

Using Nimbus properties (+1 to @Kleopatra for proving me wrong :() you can set alternating row colours by

UIManager.put("Table.alternateRowColor", Color.PINK);
UIManager.getLookAndFeelDefaults().put("Table:\"Table.cellRenderer\".background", Color.RED);

Alternatively by:

extends JTable and overrides prepareRenderer(TableCellRenderer renderer, int row, int column) in order to paint cells the color needed (RED and PINK).

Here is a short example I did hope it helps.

Extra feature: It also overrides paintComponent(..) which will call paintEmptyRows(Graphics g) which will paint rows for the entire height and width of JScrollPane viewport, however this only applies if setFillsViewPortHeight is set to true on the MyTable:

enter image description here

import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Rectangle;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.WindowConstants;
import javax.swing.plaf.nimbus.NimbusLookAndFeel;
import javax.swing.table.TableCellRenderer;

public class JTableAlternateRowColors {

    public JTableAlternateRowColors() {
        initComponents();
    }

    public static void main(String[] args) {

        try {
            UIManager.setLookAndFeel(new NimbusLookAndFeel());
        } catch (UnsupportedLookAndFeelException e) {
            e.printStackTrace();
        }

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new JTableAlternateRowColors();
            }
        });
    }

    private void initComponents() {

        final JFrame jFrame = new JFrame("Nimbus alternate row coloring");

        MyTable table = new MyTable(new String[][]{
                    {"one", "two", "three"},
                    {"one", "two", "three"},
                    {"one", "two", "three"}
                }, new String[]{"col1", "col2", "col3"});

        table.setFillsViewportHeight(true);//will fill the empty spaces too if any

        table.setPreferredScrollableViewportSize(table.getPreferredSize());

        JScrollPane jScrollPane = new JScrollPane(table);

        jFrame.getContentPane().add(jScrollPane);
        jFrame.pack();
        jFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        jFrame.setVisible(true);
    }
}

class MyTable extends JTable {

    public MyTable(String[][] data, String[] fields) {
        super(data, fields);
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);

        if (getFillsViewportHeight()) {
            paintEmptyRows(g);
        }
    }

    /**
     * Paints the backgrounds of the implied empty rows when the table model is
     * insufficient to fill all the visible area available to us. We don't
     * involve cell renderers, because we have no data.
     */
    protected void paintEmptyRows(Graphics g) {
        final int rowCount = getRowCount();
        final Rectangle clip = g.getClipBounds();
        if (rowCount * rowHeight < clip.height) {
            for (int i = rowCount; i <= clip.height / rowHeight; ++i) {
                g.setColor(colorForRow(i));
                g.fillRect(clip.x, i * rowHeight, clip.width, rowHeight);
            }
        }
    }

    /**
     * Returns the appropriate background color for the given row.
     */
    protected Color colorForRow(int row) {
        return (row % 2 == 0) ? Color.RED : Color.PINK;
    }

    /**
     * Shades alternate rows in different colors.
     */
    @Override
    public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
        Component c = super.prepareRenderer(renderer, row, column);
        if (isCellSelected(row, column) == false) {
            c.setBackground(colorForRow(row));
            c.setForeground(UIManager.getColor("Table.foreground"));
        } else {
            c.setBackground(UIManager.getColor("Table.selectionBackground"));
            c.setForeground(UIManager.getColor("Table.selectionForeground"));
        }
        return c;
    }
}

References:

Overmatter answered 22/10, 2012 at 11:21 Comment(11)
+1 prepateRenderer, and to add notice about convertXxxXxx for Sorting or FilteringCapone
The FillViewPortHeight thing is very nice and very polished (I like it), but it's not the topic here so I won't comment on it...Fredi
The FillViewPortHeight thing is very nice and very polished (I like it), but it's not the topic here so I won't comment on it... What is important though is that I [don't want to | cannot] extend JTables for the whole application. Some JTables are created by 3rd party libraries and the solution you present is not maintainable. I want the styling to be independante and the responsability of the PLAF (like a CSS). The way I see things (and tell me if I'm wrong), colors must/should be defined only in Nimbus. But how???? that's the question!Fredi
@Fredi besides all the extras all one really needs to do is override prepareRenderer(...) of JTable do not all 3rd party tables extend JTable? thus will implement prepareRenderer(...)Overmatter
@DavidKroukamp 3rd Parties do extend JTable but how to extend a JTable embedded in a JComponent hierarchy? How to extend a final class? How do you handle obfuscated code? How do you handle JTables provided by plug-ins? I know how to alternate colors. I've already done it. I'm sorry I can't accept your answer But I can only be satisfied with a solution that works with Nimbus properties only. If that is not possible, then Nimbus is rubbish! That's as simple as that! Otherwise, why would it provide the property "Table.alternateRowColor"????Fredi
@David Kroukamp if I am who will prove it :?) why there are 2pct that you're not ableCapone
@Capone ;) I have been palying around with the defaults for Table in Nimbus but no results, however, I might have missed something hence the 2%Overmatter
@DavidKroukamp If they did not expect us to alternate between "Table.background" and "Table.alternateRowColor", could someone explain me the code in javax.swing.plaf.synth.SynthTableUI#paintCell?Fredi
@Fredi if you need to vent your frustration in shouting, please do so into the direction of Oracle: Nimbus IS buggy!Photochronograph
@Fredi Kleopatra is right (+1 to her comment). See my updated answer.Overmatter
@David Kroukamp buggy or not, development ended in 1st/2nd quater, this L&F is about colors theme not about aquarium full of separate glittering fishes, there are a few xml files for glittering color schemes, then isn't required Nimbus#uinstallUI for a few buggy Keys that are with null value, allowed by default and (short_cuts) used for Renderer conceptCapone

© 2022 - 2024 — McMap. All rights reserved.