Java tip: How to add zebra background stripes to a JTable

Technologies: Java 5+

Table zebra stripes are alternating subtle background stripes painted behind the table's rows in a graphical user interface (GUI). They improve the readability of long rows in wide tables, but the JTable class in Java's Swing doesn't support them. This tip shows how to extend JTable to add zebra background stripes.

Code

The ZebraJTable class below extends JTable in Java's Swing package. It does three things:

  • It overrides the paintComponent() method to paint stripes throughout the table before the table draws its cells.
  • It overrides the prepareRenderer() and prepareEditor() methods to set the background color of opaque cell components. This insures that their backgrounds match the stripes underneath.
  • It chooses zebra stripe colors automatically based upon the current background and selection colors.

This much will get you zebra stripes under the table's cells. But there is one table quirk to handle. Unlike a JList or JTree, a JTable does not automatically grow to fill the full height of a JViewport in a JScrollPane. Instead, if the table is shorter than the viewport, the viewport's own background color is visible after the last row of the table. When using zebra stripes, this looks odd.

To fix this quirk, ZebraJTable overrides the table's getScrollableTracksViewportHeight() method to expand the table to fill the viewport. Expanding the table doesn't add more rows to the table, it just includes the empty space as part of the table's rectangular area. This lets us paint zebra strips in that space from the table's paintComponent() method.

The ZebraJTable class has been tested on all major operating systems, with all standard Java look and feel choices, and with default and application-specific table cell renderers and editors. It automatically updates its background stripe colors when the application changes colors, the look and feel changes colors, or when the user changes their OS-wide color theme (such as through the Windows Display control pane or the Mac or Linux Appearance preferences pane).

Here's the full code. It's long, but not complicated. In the sections after the code are usage examples and then a full explanation of each part of the approach.

A ZebraJTable with light blue zebra stripes

/**
 * A JTable that draws a zebra striped background.
 */
public class ZebraJTable
    extends javax.swing.JTable
{
    private java.awt.Color rowColors[] = new java.awt.Color[2];
    private boolean drawStripes = false;
 
    public ZebraJTable( )
    {
    }
    public ZebraJTable( int numRows, int numColumns )
    {
        super( numRows, numColumns );
    }
    public ZebraJTable( Object[][] rowData, Object[] columnNames )
    {
        super( rowData, columnNames );
    }
    public ZebraJTable( javax.swing.table.TableModel dataModel )
    {
        super( dataModel );
    }
    public ZebraJTable( javax.swing.table.TableModel dataModel,
        javax.swing.table.TableColumnModel columnModel )
    {
        super( dataModel, columnModel );
    }
    public ZebraJTable( javax.swing.table.TableModel dataModel,
        javax.swing.table.TableColumnModel columnModel,
        javax.swing.ListSelectionModel selectionModel )
    {
        super( dataModel, columnModel, selectionModel );
    }
    public ZebraJTable( java.util.Vector<?> rowData,
        java.util.Vector<?> columnNames )
    {
        super( rowData, columnNames );
    }
 
    /** Add stripes between cells and behind non-opaque cells. */
    public void paintComponent( java.awt.Graphics g )
    {
        if ( !(drawStripes = isOpaque( )) )
        {
            super.paintComponent( g );
            return;
        }
 
        // Paint zebra background stripes
        updateZebraColors( );
        final java.awt.Insets insets = getInsets( );
        final int w   = getWidth( )  - insets.left - insets.right;
        final int h   = getHeight( ) - insets.top  - insets.bottom;
        final int x   = insets.left;
        int y         = insets.top;
        int rowHeight = 16; // A default for empty tables
        final int nItems = getRowCount( );
        for ( int i = 0; i < nItems; i++, y+=rowHeight )
        {
            rowHeight = getRowHeight( i );
            g.setColor( rowColors[i&1] );
            g.fillRect( x, y, w, rowHeight );
        }
        // Use last row height for remainder of table area
        final int nRows = nItems + (insets.top + h - y) / rowHeight;
        for ( int i = nItems; i < nRows; i++, y+=rowHeight )
        {
            g.setColor( rowColors[i&1] );
            g.fillRect( x, y, w, rowHeight );
        }
        final int remainder = insets.top + h - y;
        if ( remainder > 0 )
        {
            g.setColor( rowColors[nRows&1] );
            g.fillRect( x, y, w, remainder );
        }
 
        // Paint component
        setOpaque( false );
        super.paintComponent( g );
        setOpaque( true );
    }
 
    /** Add background stripes behind rendered cells. */
    public java.awt.Component prepareRenderer(
        javax.swing.table.TableCellRenderer renderer, int row, int col )
    {
        final java.awt.Component c = super.prepareRenderer( renderer, row, col );
        if ( drawStripes && !isCellSelected( row, col ) )
            c.setBackground( rowColors[row&1] );
        return c;
    }
 
    /** Add background stripes behind edited cells. */
    public java.awt.Component prepareEditor(
        javax.swing.table.TableCellEditor editor, int row, int col )
    {
        final java.awt.Component c = super.prepareEditor( editor, row, col );
        if ( drawStripes && !isCellSelected( row, col ) )
            c.setBackground( rowColors[row&1] );
        return c;
    }
 
    /** Force the table to fill the viewport's height. */
    public boolean getScrollableTracksViewportHeight( )
    {
        final java.awt.Component p = getParent( );
        if ( !(p instanceof javax.swing.JViewport) )
            return false;
        return ((javax.swing.JViewport)p).getHeight() > getPreferredSize().height;
    }
 
    /** Compute zebra background stripe colors. */
    private void updateZebraColors( )
    {
        if ( (rowColors[0] = getBackground( )) == null )
        {
            rowColors[0] = rowColors[1] = java.awt.Color.white;
            return;
        }
        final java.awt.Color sel = getSelectionBackground( );
        if ( sel == null )
        {
            rowColors[1] = rowColors[0];
            return;
        }
        final float[] bgHSB = java.awt.Color.RGBtoHSB(
            rowColors[0].getRed( ), rowColors[0].getGreen( ),
            rowColors[0].getBlue( ), null );
        final float[] selHSB  = java.awt.Color.RGBtoHSB(
            sel.getRed( ), sel.getGreen( ), sel.getBlue( ), null );
        rowColors[1] = java.awt.Color.getHSBColor(
            (selHSB[1]==0.0||selHSB[2]==0.0) ? bgHSB[0] : selHSB[0],
            0.1f * selHSB[1] + 0.9f * bgHSB[1],
            bgHSB[2] + ((bgHSB[2]<0.5f) ? 0.05f : -0.05f) );
    }
}

Examples

Construct a ZebraJTable and add it to a scroll pane:

ZebraJTable table = new ZebraJTable( items, names );
JScrollPane scrollList = new JScrollPane( table );

Construct a ZebraJTable, set the row height and margin, and add the table to a scroll pane:

ZebraJTable table = new ZebraJTable( items, names );
table.setRowHeight( 20 );
table.setRowMargin( 5 );
JScrollPane scrollList = new JScrollPane( table );

Construct a ZebraJTable, set the background and selection colors, and add the table to a scroll pane:

ZebraJTable table = new ZebraJTable( items, names );
table.setBackground( Color.darkGray );
table.setForeground( Color.white );
table.setSelectionBackground( Color.yellow );
table.setSelectionForeground( Color.black );
JScrollPane scrollList = new JScrollPane( table );

Explanation

A Java JTable object is always painted in two steps:

  1. If the table is opaque, the table's area is painted with a solid background color.
  2. For each table cell, a component is retrieved from the table's cell renderer or editor and painted.

To add zebra background stripes, the ZebraJTable class overrides both of these steps.

For step 1, overridding background painting lets the class prefill the entire table background with stripes before anything is drawn on top. These stripes are visible if the table is empty, if it is too short to fill the viewport of a JScrollPane, or when the table's cells are painted with non-opaque components.

For step 2, overriding access to the TableCellRenderer and TableCellEditor enables ZebraJTable to set the background colors of opaque cell components before they are rendered atop the background.

If you don't override both of these steps, you'll only get an odd-looking partial result. For instance, if you only do step 1, you'll get stripes everywhere except under opaque list items. And if you only do step 2, you'll get stripes under those opaque list items, but nowhere else.

Painting background stripes on the JTable

Normally, Java's JTable paintComponent() method calls the table's UI delegate to draw the background and the table's cell components. ZebraJTable overrides paintComponent() and paints alternating background stripes the width of the table and the height of each table row, including row margins. It then temporarily turns off opacity and call's the JTable's normal paintComponent() method to let the UI delegate do its job and paint the table cells. With opacity disabled, the UI delegate won't repaint the table background, leaving the stripes visible.

While most tables are drawn with uniform row heights, JTable and ZebraJTable support rows of varying height. Background stripes will have the same height as individual table rows, including row margins. The height of the last table row is used as the height for stripes that fill out the remainder of the viewport beyond the end of the table.

Since non-opaque components shouldn't fill their backgrounds, ZebraJTable doesn't add zebra stripes unless the table is opaque.

Painting background stripes on table cells

Normally, Java's JTable getCellRenderer() and getCellEditor() methods get the component to render or edit a cell's value by using the table's TableCellRenderer and TableCellEditor. JTable calls these methods from prepareRenderer() and prepareEditor() during use. ZebraJTable overrides these latter two methods, calls the parent class to get components, then sets their background colors. When the cell components are painted, their backgrounds match the table's zebra stripes! This works for any application-specific TableCellRenderer or TableCellEditor, showing zebra stripes behind any type of table cell component.

Expanding the table to the height of the scrolling viewport

By default, a JTable is only tall enough to accommodate its rows of data. When the table is shown within a JViewport of a JScrollPane, if the viewport is larger than the table, the viewport's own background color fills the area after the end of the table. This behavior differs from similar uses of JList and JTree and causes zebra background stripes to end awkwardly at the last row of the table instead of extending for the full viewport.

To fix this, ZebraJTable overrides the getScrollableTracksViewportHeight() method to return true when the table is shorter than the viewport. This causes viewport handling to expand the table to the full viewport height, which enables paintComponent() to add background stripes to the entire area.

Setting zebra background stripe colors

Naturally, you can use any colors you like for the zebra stripes. But usually these are subtle colors that match the style of the user's color theme for their OS, or the application's own color theme.

Unfortunately, Java does not have standard SystemColor objects or look and feel properties for zebra stripe colors. So, ZebraJTable makes a guess. It uses JTable's background color for even stripes, and computes a slightly different background color for odd stripes. Both colors are updated on each repaint so that the table automatically changes any time the user, application, or look and feel changes to the color theme.

You can test the color guesses by showing a ZebraJTable then changing your OS color theme. Use the Display control panel in Windows, and the Appearance preferences pane on the Mac or Linux

By the way, table zebra stripes tend to look better if you disable the table's cell grid. But ZebraJTable leaves this choice to you and your application.

Testing

This Java class has been tested and works well on the CDE/Motif, GTK+, Mac OS X, Metal, Windows, and Windows Classic look and feel choices on Linux, Mac, and Windows platforms for Java 5 and Java 6. The automatic stripe color algorithm works reasonably well for all standard color schemes on these platforms, but you may wish to tune it for your own application's needs.

Further reading

Related tips

Other articles and specifications

Comments

custom cell renderer

Nice work. Very well explained.

The JXTable from SwingX also supports this via the HighlighterFactory.

How do/would you keep the alternating background consistent when it's necessary to replace one of the column's renderers (i.e. get the background color from an adjacent column on the fly)?

thanks for all to this help

thanks for all to this help

speed issue

hello

it looks for me, that after applying your code to my tables (around 7 in whole app), whole application is running much more slower, because of re-painting the tables all the time (not only, when the content changed). is there any idea, how to improve the speed?

ZebraJTable speed

In a tutorial article like this, the more optimizations and features I add, the more complicated the code and the more confusing the tutorial. So, I simplified and focused only on how to create the stripes. The code is correct, but its not as efficient as it could be.

To improve performance, paintComponent( ) should call getClipBounds( ) on the Graphics context to get the current clipping area. The returned rectangle is the only part of the table that needs to be repainted. Using the clip bounds complicates the stripe for-loops but is otherwise not difficult. For small tables, the performance gain is minimal. But if you have a large table, and particularly one that scrolls within a JScrollPane, the performance difference is significant.

You also could slightly improve performance by only recalculating the stripe colors when the table's background color or selection background color change. To do this, you'd have to override the setBackground( ) and setSelectionBackground( ) methods to note color changes from the application. You'll also have to register a listener with the UIManager to detect color changes that result from changes to the Look and Feel. And you'll have to handle the special case where dynamic SystemColor objects are used for the colors (very common). With all that, the performance gain is small compared to the code hassle and its probably better to just update the stripe color calculations on each repaint.

different implementation

Hi I've found a different, much simpler implementation for this. See "John Zukowski - The Definitive Guide to Java Swing" page 678.
import java.awt.*;
import javax.swing.*;
import javax.swing.table.*;

public class EvenOddRenderer implements TableCellRenderer {
  public static final DefaultTableCellRenderer DEFAULT_RENDERER = new DefaultTableCellRenderer();

  public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
    Component renderer = DEFAULT_RENDERER.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
    Color foreground, background;

    if (isSelected) {
      foreground = Color.YELLOW;
      background = Color.GREEN;
    } else {
      if (row % 2 == 0) {
        foreground = Color.BLUE;
        background = Color.WHITE;
      } else {
        foreground = Color.WHITE;
        background = Color.BLUE;
      }
    }
    renderer.setForeground(foreground);
    renderer.setBackground(background);
    return renderer;
  }
}

Re: different implementation

Unfortunately, Zukowski's implementation has several problems:

  • It requires a custom renderer. My implementation customizes the table, not the renderer, and will work transparently with any renderer.
  • It doesn't zebra stripe editors. My implementation adds stripes behind editors too.
  • It requires opaque cell content. My implementation also handles non-opaque content (like labels) or semi-opaque content (like checkboxes).
  • It doesn't honor the table's opacity. My implementation disables the background, as it should, when the table is non-opaque.
  • It assumes cell content fills the entire cell. My implementation also works with partial cell content (like labels).
  • It assumes the table fills the viewport. My implementation also works with short tables that are smaller than the viewport.
  • It uses non-standard selection colors. My implementation uses the look-and-feel's selection colors.
  • It uses fixed stripe colors. My implementation uses colors derived from the look-and-feel.

Zukowski's renderer is a nice demo of what renderers can do, but it isn't a complete or usable solution.

AUTO_RESIZE_OFF doesn't seem to apply to the ZebraJTable

First off, this is a great piece of work. I've been working on your examples, and I've noticed when I set the selectionMode on the table to AUTO_RESIZE_OFF, the table does not change this setting. When I resize the scrollpane by resizing the JFrame it is contained in, the columns all resize accordingly. Am I doing something wrong? Or does your overwriting of the paintComponent() or getScrollableTracksViewportHeight() methods affect this?
public class Zebra1 {
    public static void main(String[] args) {
        ZebraJTable table = new ZebraJTable(createItems(), createColumns());
        table.setSelectionMode(JTable.AUTO_RESIZE_OFF);
        JScrollPane scrollList = new JScrollPane(table);	

        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.setSize(640,480);
        frame.getContentPane().setLayout(new BorderLayout());
        frame.getContentPane().add(scrollList);
        frame.setVisible(true);
    }

    private static Vector> createItems() {
        Vector> rows = new Vector>();	
        Vector row = null;
        for (int i = 1; i < 15; i++) {
            row = new Vector();		
            for (int j = 1; j < 11; j++) {
                row.add(String.valueOf(i * j));
            }
            rows.add(row);
        }	
        return rows;
    }

    private static Vector createColumns() {
        Vector columns = new Vector();
        for (int i = 1; i < 11; i++) {
            columns.add("Column " + i);
        }		
        return columns;
    }
}
Thanks Jose

Re: AUTO_RESIZE_OFF doesn't seem to apply to the ZebraJTable

The overridden paintComponent( ) and getScrollableTracksViewportHeight( ) methods will have no impact on column resizing.

JTable column resizing modes are a mess. I've spent a lot of time fighting them to get the effect I want, and often abandon the effort entirely. I'm sorry but I can't offer any advice.

Horuizontal lines

Is it possible to implements also, that table will not have the ugly horizontal and vertical white lines when a cell, row or column is selected?

Thx
Marek

Re: Horuizontal lines

Call setShowGrid(false) on a JTable to turn off the cell grid. Or call setShowHorizontalLines(false) or setShowVerticalLines(false) to just turn off the horizontal or vertical lines.

reasonable

The whole issue could be accomplished much more easier with
UIManager.put("Table.alternateRowColor", Color.PINK);
table.setFillsViewportHeight(true);
!!! Anyway the blog is still valid for custom cells with custom backgrounds (==> prepareRenderer needs to be changed), for the alternate colors when no row is there and if the Borders need to be painted like
 private void paintEmptyRowGridLines(Graphics g) {
    Graphics newGraphics = g.create();

    // grab the y coordinate of the top of the first non-existent row (also
    // can be thought of as the bottom of the last row).

    newGraphics.getClipBounds();

    // paint the column grid dividers for the non-existent rows.
    int x = 0;
    for (int i = 0; i < getColumnCount(); i++) {
      TableColumn column = getColumnModel().getColumn(i);
      // increase the x position by the width of the current column.
      x += column.getWidth();
      newGraphics.setColor(Color.gray.brighter());
      // newGraphics.setColor(getGridColor());
      // draw the grid line (not sure what the -1 is for, but BasicTableUI
      // also does it.
      newGraphics.drawLine(x - 1,
                           g.getClipBounds().y,
                           x - 1,
                           g.getClipBounds().y + g.getClipBounds().height - 1);
    }

    newGraphics.dispose();
  }
Interesting, that I spent several hours to figure it out, how to set a simple UIManager-Property. Do not know, since when it is there.

Re: reasonable

I have a related article that lists all of the UI defaults for standard look and feels:

All UI defaults names for common Java look and feels on Windows, Mac OS X, and Linux

From that article, note that the set of UI defaults varies from look and feel to look and feel. Also note that as of the JDK 1.6 I tested, there is no "Table.alternateRowColor" UI default for any of the standard look and feels (GTK+, Mac OS X, Metal, Windows, and Windows Classic). This doesn't mean that you're wrong — only that you're either using a different look and feel, or that you're using a newer JDK that added another UI default.

In any case, since the UI default is not shared by all current or recent standard look and feels, it is not a safe approach for cross-platform coding. It's fine to check if the default exists and use it if it does. But you need a fallback position when it isn't available. And thus the code in this article is still needed.

Thanks a lot

I was looking for this, it's really help my soft look better.
Thank you.

About the ZEBRA_JTABLE

Dear All,
thanks for your sharing this valuable codes.it is really help for me.

Thanks.

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.
  • Web page addresses and e-mail addresses turn into links automatically.

More information about formatting options

Nadeau software consulting
Nadeau software consulting