Java Tips Weblog

  • Blog Stats

    • 2,569,221 hits
  • Categories

  • Archives

Row Number Table

Posted by Rob Camick on November 18, 2008

Sometimes you may have a requirement for your table to look like a spreadsheet. That is you want column headings on the top and row numbers along the side. Well, a JTable has a default API to show a column header. It does this by adding the JTableHeader component to the column header area of a JScrollPane. Although there is no component in the API to display row numbers, the scroll pane also supports a row header area for a row header component. So all we need to do is create a row header component and the problem is solved.

The RowNumberTable is such a component. It is a simple extension of JTable that shares properties of the main table along with the TableModel and SelectionModel of the main table. Although it shares the TableModel it does not actually use any of the data in the model, it just needs to be notified when rows are added or removed so it can paint itself correctly.

You can use the RowNumberTable in your program with code like the following…

JTable mainTable = new JTable(...);
JScrollPane scrollPane = new JScrollPane(mainTable);
JTable rowTable = new RowNumberTable(mainTable);
scrollPane.setRowHeaderView(rowTable);
scrollPane.setCorner(JScrollPane.UPPER_LEFT_CORNER,
    rowTable.getTableHeader());

which will generate a table looking something like…

row-number-table

Get The Code

RowNumberTable.java

Related Reading

Using a Scroll Pane: Providing Custom Decorations

57 Responses to “Row Number Table”

  1. Jan Zitniak said

    We used your RowNumberTable.java solution. In the following case when we used first column of table as Boolean Type we got error:

    Exception in thread "AWT-EventQueue-0" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Boolean
    at javax.swing.JTable$BooleanRenderer.getTableCellRendererComponent(JTable.java:5406)
    at javax.swing.JTable.prepareRenderer(JTable.java:5729)
    ...

    Thank you for any help.

  2. Rob Camick said

    Thanks for the feedback.

    RowNumberTable code has been modified to make sure the included RowNumberRenderer is used to render the row numbers. The renderer is now attached directly to the TableColumn.

  3. Jan Zitniak said

    Thank you, now it works ok.

  4. M. said

    Thanks for that useful class.

  5. GP said

    Very useful indeed!… I know JTable class is already packed full of methods… but just wished there would be a few more useful functions like this on offer. Maybe they could include a JTableAuxiliary or JTableUtilities or JTableConfigure class to assist in collecting all these extra goodies! :-)
    ok thanks for tip.
    Cheers.

  6. S.Mac said

    Rob-

    I am having the same issue in my JTable, getting the exception ‘Exception in thread “AWT-EventQueue-0” java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Boolean
    at javax.swing.JTable$BooleanRenderer.getTableCellRendererComponent(unknownSource)
    at javax.swing.JTable.prepareRenderer(UnknownSource)…

    Can you tell how you fixed it in your RowNumberTable component?

    Many thanks.

    • Rob Camick said

      This is not a forum for general Java questions. Please stick to comments/issues with the code or concepts presented here.

      It sounds like you have a problem with the getColumnClass() method. You can try searching the Sun Java forum for my “TableBasic” example which shows how to use different renderers for different columns.

  7. Debasish Layek said

    I am using your RowNumberTable.java example but the row numbers not properly displaying the when I scrolling the actual table. It shows an empty area there. Please advice what to do?
    Thanks for any kind of help.

  8. Thorsten said

    Very good… but i.ve found some issues.

    – # Column is still moveable
    – row height ov main table is not respected

    Here my fix:


    import java.awt.*;
    import java.beans.*;
    import javax.swing.*;
    import javax.swing.event.*;
    import javax.swing.table.*;

    /*
    * Use a JTable as a renderer for row numbers of a given main table.
    * This table must be added to the row header of the scrollpane that
    * contains the main table.
    */
    public class RowNumberTable extends JTable
    implements ChangeListener, PropertyChangeListener {

    private final JTable table;

    public RowNumberTable(JTable table) {
    this.table = table;
    table.addPropertyChangeListener(this);

    setFocusable(false);
    setAutoCreateColumnsFromModel(false);

    updateRowHeight();
    updateModel();
    updateSelectionModel();

    TableColumn column = new TableColumn();
    column.setHeaderValue("");
    addColumn(column);
    column.setCellRenderer(new RowNumberRenderer());

    getColumnModel().getColumn(0).setPreferredWidth(50);
    setPreferredScrollableViewportSize(getPreferredSize());

    getTableHeader().setReorderingAllowed(false);
    }

    @Override
    public void addNotify() {
    super.addNotify();

    Component c = getParent();

    // Keep scrolling of the row table in sync with the main table.

    if (c instanceof JViewport) {
    JViewport viewport = (JViewport) c;
    viewport.addChangeListener(this);
    }
    }

    /*
    * Delegate method to main table
    */
    @Override
    public int getRowCount() {
    return table.getRowCount();
    }

    @Override
    public int getRowHeight(int row) {
    return table.getRowHeight(row);
    }

    /*
    * This table does not use any data from the main TableModel,
    * so just return a value based on the row parameter.
    */
    @Override
    public Object getValueAt(int row, int column) {
    return Integer.toString(row + 1);
    }

    /*
    * Don't edit data in the main TableModel by mistake
    */
    @Override
    public boolean isCellEditable(int row, int column) {
    return false;
    }

    // implements ChangeListener
    public void stateChanged(ChangeEvent e) {
    // Keep the scrolling of the row table in sync with main table

    JViewport viewport = (JViewport) e.getSource();
    JScrollPane scrollPane = (JScrollPane) viewport.getParent();
    scrollPane.getVerticalScrollBar().setValue(viewport.getViewPosition().y);
    }

    // implements PropertyChangeListener
    public void propertyChange(PropertyChangeEvent e) {
    // Keep the row table in sync with the main table

    if ("rowHeight".equals(e.getPropertyName())) {
    updateRowHeight();
    }

    if ("selectionModel".equals(e.getPropertyName())) {
    updateSelectionModel();
    }

    if ("model".equals(e.getPropertyName())) {
    updateModel();
    }
    }

    private void updateRowHeight() {
    setRowHeight( table.getRowHeight() );
    }

    private void updateModel() {
    setModel(table.getModel());
    }

    private void updateSelectionModel() {
    setSelectionModel(table.getSelectionModel());
    }

    /*
    * Borrow the renderer from JDK1.4.2 table header
    */
    private static class RowNumberRenderer extends DefaultTableCellRenderer {

    public RowNumberRenderer() {
    setHorizontalAlignment(JLabel.CENTER);
    }

    @Override
    public Component getTableCellRendererComponent(
    JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
    if (table != null) {
    JTableHeader header = table.getTableHeader();

    if (header != null) {
    setForeground(header.getForeground());
    setBackground(header.getBackground());
    setFont(header.getFont());
    }
    }

    if (isSelected) {
    setFont(getFont().deriveFont(Font.BOLD));
    }

    setText((value == null) ? "" : value.toString());
    setBorder(UIManager.getBorder("TableHeader.cellBorder"));

    return this;
    }
    }
    }

  9. Andreas said

    hi there,
    nice one. I’ve already created a RowHeader with a JList. This RowNumberTable looks better.

    But i found an issue, i mean.
    When i selected the row 2 or 3, the RowHeader View at the selected row goes away, it shows only the view background (grey) without Border, without Number. I think it’s a renderer problem.
    Anyone have the problem, too?
    – Sorry for my “bad” english”.

    greets
    Andreas

  10. Andreas said

    hi,
    thx for the answer.
    I using jdk1.6.0_21 on Windows 7 and the Metal LAF.
    Here’s a picture below of this what i mean:

    Edit: i tested it in a new Test Project and all to be good.
    Hmmm, i think i look at my code again… ;-)

  11. Andreas said

    i comment out this code row and the problem don’t exists:

    // this.setSelectionModel(this.jtable.getSelectionModel());

    But so i can’t set the selection in the rowheader…. :(

    • Andreas said

      ok, i rebooted my pc and start eclipse and my projecct. The problem is away. All looks good. It’s curious but now it works.
      greets

      • Andreas said

        Ok, i still had the problem….

        But now i know why and i have the answer. Anyone they have the same problem here’s the answer:
        I feel intense shame…. ;)
        My own JTable has a fixed cell height. The RowNumberTable must have the same Row Height like this (in the RowNumberTable):

        this.setRowHeight(this.jtable.getRowHeight());

        That’s all. All looks like good…
        Thx to all.

  12. Thank, this is help full

  13. Mykolas said

    Hello there,
    thanks for the class. Great job! I used it on in my project and it works perfectly fine. But what do I do to change column numeration? I want to have 00, 01, 02 and so on. Is it possible? Thanks in advance.

    • Rob Camick said

      Change the getValueAt(…) method to return the row number starting at 0. Change the renderer to format the value however you want.

      • Mykolas said

        I managed to deal with row numbers, but I still can’t get the culumn numbers the way I want. Could you be more specific about how I should change the code of RowNumberRenderer to make it work (I can’t find out how those letters A, B, C,… appear)

      • Rob Camick said

        The column headers have nothing to do with this class. I suggest you read the JTable API and follow the link to the Swing tutorial on “How to Use Table” for information on specifying the column header values.

      • Mykolas said

        I thought it’s RowNumberTable that renders column names. Sorry for stupid questions, everything is working as it should now.Thanks.

  14. I have a non-free solution at my site Hopsof.com, it takes care of custom resizing and all that for you. There is a demo you can look at too if you wish.

    http://www.hopsof.com/java-components-1/swing-components/rowheadertable

    hope that helps, and saves you some time/headache!

    Thanks!
    Brandon

  15. how to color the cells which only display row numbers.

    • Rob Camick said

      You need to further cusomize the renderer. The current customization just makes the text bold. If you want to change the color you would add your code in the same place.

      • Raghunandan Kavi said

        Thanks dude, i figured it out. Your code is awesome. This was a great help to me. Thanks a lot……………

  16. Anonymous said

    Thanks Man….. It look Complicating at the beginning !!! But after a little research …. No other can do simpler than this. …. taking little patient and the code is working ….. for any one who do not get it to work , please, download “RowNumberTable.java” on top of this page … try it again … Good Luck …

  17. I don’t even know the way I stopped up here, however I assumed this put up was once good. I don’t recognize who you’re but certainly you are going to a famous blogger in case you are not already. Cheers!

  18. read more said

    Pretty element of content. I just stumbled upon your site and in accession capital to say that I get actually loved account your weblog posts. Anyway I’ll be subscribing for your augment and even I achievement you get entry to consistently fast.

  19. Marefe said

    Hi. Your code is really helpful. My question is somehow related to Mykolas’ question: “But what do I do to change column numeration? I want to have 00, 01, 02 and so on.” My problem is my main table can be sorted and can be filtered and I want the row number table to rearrange as the main table is sorted or filtered. Is it possible? Thanks ahead.

    • Rob Camick said

      No, it is not possible to use sorting or filtering with this class. Maybe you can use the Fixed Column Table from this blog. You would need to add the row number as data in the first column of your TableModel. Then, because the row is part of the model it will be filtered and sorted with the rest of the TableModel.

  20. toushif said

    dude u r AWESOME …it worked

  21. George said

    Hello,

    How is it possible to print the whole table?

  22. VikarIT said

    Nice share broe,

  23. Sweet said

    Hi
    I want to change the color of the first row of the table, I tried to change your getTableCellRendererComponent function, but only works in the rowNumber column, so, I made a TableCellRenderer class, and use it in the main table, mainTable.setDefaultRenderer(String.class, new CellRenderer());, but it’s not working. Any idea why this is happening, and what else can I do?

    Thanks

    • Rob Camick said

      “but it’s not working.Any idea why this is happening” – your code is wrong. Adding a custom render to a table is a question you should ask in a forum. It has nothing to do with this class. Maybe you can check out the Table Row Rendering blog entry which shows a different approach to coloring a table row without using a custom renderer.

  24. ravi said

    hi,
    when I add a new row to existing table row numbers are not getting updated. Please help me in this case.

  25. George said

    Greetings Google and yahoo performs fine however your
    site is loading steadily which actually went on around a minute to be able
    to load up, I am not sure if it’s my own issue or maybe your site problem.
    Anyways, I’m going to thank you very much for including amazing content.
    I do believe this has already been extremely helpful user who actually click here.
    I really hope I will be able to find a lot more remarkable content
    and I should compliment you by saying you have done good work.
    I already have your site bookmarked to check out blogs you publish.

  26. divya kamboj said

    i got error RowNumberTable cannot find

  27. Глеб Г.Г. said

    Hello! I have an issue with your code. When I add a new row to existing table,
    RAM usage of my small java app increases significantly and eventually freezes the app.
    When I don’t use your code, RAM usage stays normal. Looks like on some point memory leak is created. Maybe because Table Model is constantly updated etc.
    Also I’d like to be able to edit row names.

    • Rob Camick said

      I can’t duplicate the problem. The memory usages stay constant with hundreds of line of data. Also I’d like to be able to edit row names. – Feel free to edit the code however you want. I guess you would need to keep a map of line number with the related text. Then before you paint the line you would check the Map to see if an edited name has been provided.

  28. [5-Apr-2019].Charlotte NC
    Hello Rob,

    This is a very nice piece. I found it used elsewhere (and properly attributed), tried it, and liked it. I was so pleased with it that I took some time to “improve” upon it. Perhaps you will disagree about the “improvements”, but I will toss them your way for consideration anyway. 6 methods are eliminated, none are added. The class now consists of 1 ctor, 2 methods, 1 Listener, and 1 Renderer and is a drop-in replacement for the original. My commentary on the reason for the changes follows the code. I left as much of the original in place as I could and commented out the un-needed lines.

    import java.awt.*;
    import java.awt.event.*;
    import java.beans.*;
    import javax.swing.*;
    import javax.swing.event.*;
    import javax.swing.table.*;
    /*
     * Use a JTable as a renderer for row numbers of a given main table.
     * This table must be added to the row header of the scrollpane that
     * contains the main table.
    */
    public class RowNumberTable extends JTable implements
         // ChangeListener,
         PropertyChangeListener,
         TableModelListener
    {
    
        private JTable main;
    
        public RowNumberTable( JTable table )
        {
            main = table;
            main.addPropertyChangeListener( this );
            main.getModel().addTableModelListener( this );
            setModel( new DefaultTableModel( main.getRowCount(), 1 ) ); // Added
    
            setFocusable( false );
            // setAutoCreateColumnsFromModel( false );
            setSelectionModel( main.getSelectionModel() );
    
            // TableColumn column = new TableColumn();
            TableColumn column = getColumnModel().getColumn( 0 );
            column.setHeaderValue( " " );
            // addColumn( column );
            column.setCellRenderer( new RowNumberRenderer() );
    
            // getColumnModel().getColumn( 0 ).setPreferredWidth( 50 );
            column.setPreferredWidth( 50 ); // Added
            setPreferredScrollableViewportSize( getPreferredSize() );
    
        }
    
    //  public void addNotify()
    //  {
    //      super.addNotify();
    //
    //      Component c = getParent();
    //
    //      // Keep scrolling of the row table in sync with the main table.
    //      if ( c instanceof JViewport)
    //      {
    //         JViewport viewport = (JViewport) c;
    //         viewport.addChangeListener( this );
    //      }
    //
    //  }
    
    //  /*
    //  * Delegate method to main table
    //  */
    //  public int getRowCount()
    //  {
    //      return main.getRowCount();
    //  }
    
    //  public int getRowHeight( int row )
    //  {
    //      int rowHeight = main.getRowHeight( row );
    //
    //      if ( rowHeight != super.getRowHeight( row ) )
    //      {
    //          super.setRowHeight( row, rowHeight );
    //      }
    //
    //      return rowHeight;
    //  }
    
    /*
    * Returns the value associated with the table cell at the supplied coordinate. // Added
    * If the {@code null} value is found, the row number of the table (view, // Added
    * not model) is returned instead. // Added
    */
        public Object getValueAt( int row, int col )
        {
            Object result = super.getValueAt( row, col ); // Added
            return result == null ? Integer.toString( row + 1 ) : result; // Added
    //         return Integer.toString( row + 1 );
         }
    
    // /*
    // * Don't edit data in the main TableModel by mistake
    // */
    //  public boolean isCellEditable( int row, int col )
    //  {
    //      return false;
    //  }
    
    // /*
    // * Do nothing since the table ignores the model
    // */
    //  public void setValueAt( Object value, int row, int col ) {}
    
    // /*
    // * Implement the ChangeListener
    // */
    //  public void stateChanged( ChangeEvent e )
    //  {
    //      // Keep the scrolling of the row table in sync with main table
    //
    //      JViewport viewport = (JViewport) e.getSource();
    //      JScrollPane scrollPane = (JScrollPane)viewport.getParent();
    //      scrollPane.getVerticalScrollBar().setValue(viewport.getViewPosition().y);
    //  }
    
    /*
     * Implement the PropertyChangeListener
     */
        public void propertyChange( PropertyChangeEvent e )
        {
            // Keep the row table in sync with the main table
    
            if ( "selectionModel".equals( e.getPropertyName() ) )
                setSelectionModel( main.getSelectionModel() );
    
            if ( "rowHeight".equals( e.getPropertyName() ) )
                super.setRowHeight( (Integer) e.getNewValue() ); // Added
    
            if ( "model".equals( e.getPropertyName() ) )
            {
                ( (DefaultTableModel) getModel() ).setRowCount( // Added
                main.getModel().getRowCount() ); // Added
                main.getModel().addTableModelListener( this );
                // revalidate();
            }
        }
    
    /**Implements a TableModelListener. This Listener is registered for both the // Added
     * [main] table and this RowNumberTable2. Therefore it will receive Events // Added
     * related to both. If the Event is from the [main] JTable, ensure that the // Added
     * rowCount of the TableModel backing this JTable is congruent with that of // Added
     * the [main] table. If the Event is from this JTable, let the super do the // Added
     * default processing instead. // Added
     * // Added
     * @param e the Event published by the TableModel // Added
     */ // Added
        public void tableChanged( TableModelEvent e )
        {
    
            if ( e.getSource() == getModel() )  // Added
            {
                super.tableChanged( e ); // Added
                return; // Added
            }  // Added
    
            int rc = main.getModel().getRowCount(); // Added
    
            if ( getRowCount() != rc ) // Added
                ( (DefaultTableModel) getModel() ).setRowCount( rc ); // Added
    
            // revalidate();
        }
    
        /*
         * Attempt to mimic the table header renderer
         */
        private static class RowNumberRenderer extends DefaultTableCellRenderer
        {
    
            public RowNumberRenderer()
            {
                setHorizontalAlignment( JLabel.CENTER );
            }
    
            public Component getTableCellRendererComponent(
                JTable table,
                Object value,
                boolean isSelected,
                boolean hasFocus,
                int row,
                int column )
             {
                if ( table != null )
                {
                    JTableHeader header = table.getTableHeader();
    
                    if ( header != null )
                    {
                        setForeground( header.getForeground() );
                        setBackground( header.getBackground() );
                        setFont( header.getFont() );
                     }
                 }
    
                setBorder( UIManager.getBorder( "TableHeader.cellBorder" ) );
    
                if ( isSelected )
                    setFont( getFont().deriveFont( Font.BOLD ) );
    
                setText( ( value == null ) ? "" : value.toString() );
    
                return this;
             }
    
        } // RowNumberRenderer()
    
    } 
    

    JTables always have an associated TableModel, whether or not the caller of the JTable ctor specifies one. Therefore I chose to use it. The cost to use it is keeping its rowCount equal to that of the [main] TableModel, but I see this as minor compared to the benefits, as follows:

    The ovverride of setValueAt() is removed, making the default behavior operative, allowing the user to annotate his row headers with something other than the hard-coded row number without having to modify this class’ code since he can now store his custom value in the TableModel, and the TableCellRenderer will use it.

    The override of getValueAt() is modified to implement the behavior described for setValueAt() above.

    The override of isCellEditable() isn’t needed – never was needed. This method routes to the RowNumberTable (RNT) instance, not the [main] table; and since the RNT is set not focusable, no cell editor can be launched within it.

    Since the RNT is registered as a PropertyChangeListener, it is receiving a slew of events from the two JTables already. When the property is “rowHeight”, repaint() won’t effect the change, but passing the value to the super will. This obviates the need for the getRowHeight() override. Doing this addresses one of the earlier poster’s complaints. But I note that this is insufficient if the [main] table permits row-specific heights – that requires a little more work.

    The override of addNotify() cannot function and is deleted. Both the RNT and the hosting JScrollPane are Swing objects being managed on the Swing EDT. Calling Notify on the currently-executing (and therefore not-blocked) thread has no effect.

    The stateChanged() Listener is superfluous. Swing will keep the rowHeader area of the JScrollPane in sync with the [main] table without being prompted.

    All calls to repaint() and revalidate() have been obviated. Swing handles all of this work correctly by default.

    I tested this modified version of the RNT using the JDK 1.6 on Windows with Windows LAF and Metal. It also works with JDK 1.4 if unboxing is added in the super.setRowHeight() call. I have not tested on any other JRE version, platform, or PLAF.

    Thanks for your example – it was quite helpful.

    • Rob Camick said

      I wrote the code 11 years ago, so I don’t remember all the reasons why I did what I did, but here are some comments:

      1. The ChangeListener is needed when you attempt to scroll the header. If you scroll the main table the header is kept in sync, but if you scroll the header, the main table is not kept in sync.

      2. Even if the RNT is not focusable, you can still double click on a cell putting it into edit mode, which is why I overrode the isCellEdiatble(…) method.

      3. I overrode getRowHeight(…) to support the scenario where each row could have a different height by using, for example, setRowHeight(0, 30) to change the height of the first row.

      • Anonymous said

        Thanks for the reply (and fixing the post to use

        
        

        – I couldn’t figure out how to make the blog take that).

        1) I can’t replicate that behavior – scrolling is working in my tests.
        2) You are right – I was wrong on that one. I checked F2, neglected to check dbl-click.
        3) I went at that in a different way for various reasons, but have now learned that the JTable does not publish rowHeight PropertyChange events for row-specific changes. The embedded JavaDoc tags for setRowHeight(int,int) imply (at least in JDK 1.6) that they intended to bind it, but the didn’t do it – it only publishes table-wide rowHeight changes. So I am going back to your way.

Leave a reply to Rob Camick Cancel reply