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…
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.
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.
Jan Zitniak said
Thank you, now it works ok.
M. said
Thanks for that useful class.
Rob Camick said
Glad you find it helpfull.
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.
Rob Camick said
Yes that was sort of the idea why I created the RXTable class. Sometimes you can add a static method as demonstrated by the
Table Column Reordering entry. Sometimes you need to override methods of the JTable as demonstrated by Table Select All Editor but sometime a separate class like this is more appropriate.
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.
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.
Rob Camick said
I’ve never seen this kind of problem. Create a SSCCE and post the code in an email using the “Contact Us” page.
Thorsten said
see my post (#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;
}
}
}
Thorsten said
Issue of poster #7 (row height not respected) should be most likely also fixed.
Debasish Layek said
Thanks Thorsten,
Now its working fine.
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
Rob Camick said
Sorry I can’t duplicate the problem. I tested using JDK6.7 on XP using the Metal and Windows LAF.
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… ;-)
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.
truongkimminh said
Thank, this is help full
Rob Camick said
Glad you found the class helpful.
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.
Brandon Murphy said
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
Raghunandan Kavi said
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……………
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 …
szkoła jazdy said
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!
Rob Camick said
Glad you found the code helpful.
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.
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.
Marefe said
Great! Thanks a lot. This blog is very helpful.
toushif said
dude u r AWESOME …it worked
Rob Camick said
Glad you figured out the problem.
George said
Hello,
How is it possible to print the whole table?
Rob Camick said
I’ve never tried to print a table. There is nothing special about this class related to printing. I suggest you ask your question in a forum.
VikarIT said
Nice share broe,
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.
ravi said
hi,
when I add a new row to existing table row numbers are not getting updated. Please help me in this case.
Rob Camick said
Thanks for the feedback. I added code to the class to listen for TableModel changes on the TableModel of the main JTable. I then revalidate() the RowNumberTable so it can repaint based on the data in the main table. You will need to download the new code.
Mofe-hendy Ejegi said
Please, the link to your code isn’t working. I really need the updated code as this problem in #24 pertains to me too
Rob Camick said
Link works fine for me.
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.
divya kamboj said
i got error RowNumberTable cannot find
Глеб Г.Г. 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.
Bruce M. Judd said
[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.
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.