tags:

views:

130

answers:

5

Using a different background color for odd and even rows is a commonly used trick to improve readability of large tables.

I want to use this effect in Swing's JTable. I started out by creating a custom table renderer, but this can only be used to paint actual cells, and I also want to add stripes to the "white" part of the table where there might be no cells. I can subclass JTable and override paintComponent(), but I would prefer an option where I can just change the table's rendering.

Is there a better way of doing this?

Edit: According to the answers so far this seems to be impossible without extending JTable. However, when I override JTable.paintComponent() it also only paints the area where there are rows. How can I paint the rest?

+3  A: 

The JXtable provided by swingx allow you to implements such rendering Take a look at http://swinglabs.org/docs/components/JXTable/tutorial.jsp?step=3#RowHighlighting

Sylvain M
Sorry, but I don't know if you could customize it to "add stripes to the "white" part of the table where there might be no cells".
Sylvain M
Seems like a good solution. I'll check it out.
Kees Kist
The highlighters are limited to cells, though.
Gnoupi
+1  A: 

Either use JXTable or if you are super-lazy ( or super time-short :-)) ) you can just use "Nimbus" look-and-feel, JTable looks there stripped by default :)

Xorty
Nimbus is only changing the default renderer behaviour, so it won't extend outside of the cells as well, though.
Gnoupi
Nope, I've said its super lazy one ... But tbh Nimbus looks best for me so I am definitelly comfortable with default one
Xorty
In the same kind of solution, you may also take a look at Substance (https://substance.dev.java.net/) LAF, I have used it in the past and I could easily configure it with a stripped background.
jfpoilpret
+1  A: 

You can also use a custom TableCellRenderer which will pick the color for you, with a part of code like this inside:

if (isSelected) //color remains the same while selected
{
    lFgColor = table.getSelectionForeground();
    lBgColor = table.getSelectionBackground();
}
else
{
    lFgColor = table.getForeground();
    if (row%2 != 0) //once out of two rows, change color
        lBgColor = table.getBackground();
    else
    {
        //New look and feels like nimbus declare this property, try to use it
        lBgColor = UIManager.getColor("Table.alternateRowColor"); 
        if (lBgColor == null) //If not, choose your own color
            lBgColor = UIManager.getColor("Table.light");
        }
    }
}

Edit: I missed the fact that you tried that already, and that you need to extend this color to the space without cells. To my knowledge, this is not possible with the current implementation of JTable or JXTable. The highlighters from JXTable are mostly sophisticated renderers, they still cater only to cells.

To extend the space, the only possibilities I see are:

  • draw it yourself, in your own component.
  • "hack" a way by adding a fake last column, with a custom JTableHeader which wouldn't display the last column header (and a renderer avoiding the grid for this last column). Also, the table resizing mode should be AUTO_RESIZE_LAST_COLUMN, in this case. This is a lot of conditions to make it work, and I'm not really sure it would work anyway.
Gnoupi
The problem with this approach is that it doesn't add a striped background for the part of the table where there are no cells.
Kees Kist
@kees - true. I usually have tables wider than the JScrollPane, so I forgot about this detail. I noticed only now your requirement in the question, sorry. Note that the highlighter from JXTable works only on cells as well, as it is used in the "renderer" context as well. If you absolutely need to extend it, though, you will need to do it yourself, as the way the tables are rendered is focused only on the actual "cells" area.
Gnoupi
+1  A: 

Use Table Row Rendering concepts which is easier than dealing with individual renderers.

This approach only works for rendered cells. If you want to paint outside the bounds of the table, then you will need to override the paintComponent() method to add custom painting.

camickr
+1  A: 

Use getCellRect( getRowCount() - 1, 0, true ).y to get the top y-coordinate of the empty space, and then paint some Rectangles and (Grid-)Lines with paintComponent( Graphics g ).

jtable with empty space grid

To make it much easier for you, here's a long (but complete) solution ;-)

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.UIManager;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;

public class StripedEvenInWhitePartsTable extends JTable
{

  public StripedEvenInWhitePartsTable( String[][] data, String[] fields )
  {
    super( data, fields );
    setFillsViewportHeight( true ); //to show the empty space of the table 
  }


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

    paintEmptyRows( g );
  }


  public void paintEmptyRows( Graphics g )
  {
    Graphics newGraphics = g.create();
    newGraphics.setColor( UIManager.getColor( "Table.gridColor" ) );

    Rectangle rectOfLastRow = getCellRect( getRowCount() - 1, 0, true );
    int firstNonExistentRowY = rectOfLastRow.y; //the top Y-coordinate of the first empty tablerow

    if ( getVisibleRect().height > firstNonExistentRowY ) //only paint the grid if empty space is visible
    {
      //fill the rows alternating and paint the row-lines:
      int rowYToDraw = (firstNonExistentRowY - 1) + getRowHeight(); //minus 1 otherwise the first empty row is one pixel to high
      int actualRow = getRowCount() - 1; //to continue the stripes from the area with table-data

      while ( rowYToDraw < getHeight() )
      {
        if ( actualRow % 2 == 0 )
        {
          newGraphics.setColor( Color.ORANGE ); //change this to another color (Color.YELLOW, anyone?) to show that only the free space is painted
          newGraphics.fillRect( 0, rowYToDraw, getWidth(), getRowHeight() );
          newGraphics.setColor( UIManager.getColor( "Table.gridColor" ) );
        }

        newGraphics.drawLine( 0, rowYToDraw, getWidth(), rowYToDraw );

        rowYToDraw += getRowHeight();
        actualRow++;
      }


      //paint the column-lines:
      int x = 0;
      for ( int i = 0; i < getColumnCount(); i++ )
      {
        TableColumn column = getColumnModel().getColumn( i );
        x += column.getWidth(); //add the column width to the x-coordinate

        newGraphics.drawLine( x - 1, firstNonExistentRowY, x - 1, getHeight() );
      }

      newGraphics.dispose();

    } //if empty space is visible

  } //paintEmptyRows



  public Component prepareRenderer( TableCellRenderer renderer, int row, int column )
  {
    Component c = super.prepareRenderer( renderer, row, column );

    if ( !isRowSelected( row ) )
    {
      c.setBackground( row % 2 == 0 ? getBackground() : Color.ORANGE );
    }

    return c;
  }


  public static void main( String[] argv )
  {
    String data[][] = { { "A0", "B0", "C0" }, { "A1", "B1", "C1" }, { "A2", "B2", "C2" }, { "A3", "B3", "C3" }, { "A4", "B4", "C4" } };
    String fields[] = { "A", "B", "C" };

    JFrame frame = new JFrame( "a JTable with striped empty space" );
    StripedEvenInWhitePartsTable table = new StripedEvenInWhitePartsTable( data, fields );
    JScrollPane pane = new JScrollPane( table );

    frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
    frame.add( pane );
    frame.setSize( 400, 300 );
    frame.setLocationRelativeTo( null );
    frame.setVisible( true );
  }

}

This example could be extended to:

  • fix the painted pseudogrid for variable RowHeights (I'm using the lowest height used in any row)
  • explain to the user why nothing happens if he clicks in the empty space to edit the cells (via tooltip)
  • add an extra row to the table model if the user clicks in the empty space (nooo! no Excel please!)
  • use the empty space to draw a reflection of the table (including all rendered data (what for?))
bobndrew
setFillsViewportHeight(true) is the thing I missed. Thanks!
Kees Kist