Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nimbus and alternate row colors

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);
}

}

like image 621
Pigelvy Avatar asked Oct 22 '12 09:10

Pigelvy


2 Answers

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:

  • http://elliotth.blogspot.com/2004/09/alternating-row-colors-in-jtable.html
  • JTable with striped background
like image 31
David Kroukamp Avatar answered Oct 16 '22 19:10

David Kroukamp


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"

like image 93
kleopatra Avatar answered Oct 16 '22 20:10

kleopatra