Monday, January 02, 2006

Getting JTable columns widths to fit the data

I've been stumped on a JTable problem for quite a while now: how to get the columns to fit the width of the data in the cells. Out of the box, JTable uses the same width for all columns in a table. This is very annoying for non-editable tables which just display data. I want my Java tables to looks similar to HTML tables which resize column widths according to the cell contents.

A likely solution is simply that I did not search Google well enough, but information was frustratingly rare to find for me.

I did, however, eventually stumble across the prepareRenderer(TableCellRenderer renderer, int row, int column):Component method (and likewise prepareRenderer(TableCellEditor editor, int row, int column):Component). A flash of insight illuminated the solution at once:

{
    final TableCellRenderer renderer = getTableHeader()
            .getDefaultRenderer();

    for (int i = 0; i < getColumnCount(); ++i)
        getColumnModel().getColumn(i).setPreferredWidth(
                renderer.getTableCellRendererComponent(this,
                        getModel().getColumnName(i), false, false, 0, i)
                        .getPreferredSize().width);
}

public Component prepareRenderer(final TableCellRenderer renderer,
        final int row, final int column) {
    final Component prepareRenderer = super
            .prepareRenderer(renderer, row, column);
    final TableColumn tableColumn = getColumnModel().getColumn(column);

    tableColumn.setPreferredWidth(max(
            prepareRenderer.getPreferredSize().width,
            tableColumn.getPreferredWidth()));

    return prepareRenderer;
}

My solution has two parts. Addressing first prepareRenderer: as the table is drawn, adjust the preferred width of a column to be at least as wide as the widest cell seen so far. This was 90% of my problem right there. Immediately I stopped having truncated cells, and the table adjusted gracefully to new cell data which did not fit the old column widths.

The second part is more obscure: the instance initializer. Although my cell contents were now always visible, the proportions among the columns did not look quite right: narrow columns were not narrow enough with excess slop on either side of column titles. So I instructed the table to shrink column widths down to just that necessary to show the title (prepareRenderer will widen them again as needed for cell contents). Perfect!

What surprises me is that I was unable to find this or a similar solution anywhere in my Google searches. I can scarcely believe this is the first attempt at this approach. Hopefully Sun will include a solution in the JDK—this is not an uncommon JTable problem.

UPDATE: This topic continues to deliver: Alex Boosmeeuw posts with a different approach.

17 comments:

Anonymous said...

Nice solution.

Anonymous said...

Thank You! This saved me a ton of time!

miguel said...

Thanks!!!! It's much more nicer this way!!

Anonymous said...

@-)

is the first part a method though?? i can't tell since there's no method name and parameters..

Brian Oxley said...

@Anonmyous: The first bit is anonymous instance initilization. I could have subclasses, but wanted to keep things simple. Perhaps that was not simple at all. Read the paragraph about the "second part is more obscure".

Unknown said...

Sorry for being slow, but could you show how your code is used with a jTable, I don't understand what your code actually is doing. Are you creating your own class? Trying to get a jTable columns the correct width has been driving me crazy for the last few hours.
Thanks,
Peter.

Brian Oxley said...

Peter, drop me an email -- we can discuss this separately from the comments.

Dewi said...

Can you please upload the full source. As I can not get this to work. Thanks

Anonymous said...

THANKS A LOT

ptha said...

Just extend JTable with your own class, and add Binkley's code.

Anonymous said...

you are the man!! ;)
saved me tons of work!
thanks so much for post that

Anonymous said...

Excellent solution, just what I needed!
Since I don't have any column headers, I just set the initial preferredWidths to something really small, and let the prepareRenderer code adjust them according to contents. Also, to get the JPanel to fit the table, I also set its preferred width to that of the table.

Paul Holser said...

Thanks for this!

Anonymous said...

I agree with Anonymous (12th June 2009). Upon first read, it's unclear where you are using the first chunk of code. You say it's anonymous instance initialization, but you don't explain where you are assigning this to.

I think if you can clear this confusion up (by editing the post itself), then you've got yourself an excellent, useful post. Thanks.

Anonymous said...

Please also note that your capture code doesn't work when I try and comment with an OpenID. Hence why the last comment is anonymous.

Anonymous said...

genius but if you need to modify the tableModel it is using you need to run the first block of code again. I instead used the first block to override the layout() method (a depreciated JTable method) so I could still use it as an anonymous inner class and have access to the method without knowing the instance details. Since it is depreciated you don't remove anything important and you just have to call layout() on the table every time you put a new tableModel in. It also has a fail safe; If for some odd reason your customized JTable is replaced with a normal one, then calling layout() although silly will not cause and problems. And if warnings bug you, you can just put a @SuppressWarnings("deprecation") on your class somewhere.

Anonymous said...

genius but if you need to modify the tableModel it is using you need to run the first block of code again. I instead used the first block to override the layout() method (a depreciated JTable method) so I could still use it as an anonymous inner class and have access to the method without knowing the instance details. Since it is depreciated you don't remove anything important and you just have to call layout() on the table every time you put a new tableModel in. It also has a fail safe; If for some odd reason your customized JTable is replaced with a normal one, then calling layout() although silly will not cause and problems. And if warnings bug you, you can just put a @SuppressWarnings("deprecation") on your class somewhere.