Home > Swing > A properly sized JTable

A properly sized JTable

Task: Create a table with two columns. The columns should fit the size of the content. If the table’s width is smaller than the panel, make the last column fill the panel, if the table is wider than the panel, show horizontal scrollbars. Sounds so easy? How many of you have wasted a good amount of time making a table behave like this? The good thing is: it is easy. And here is how it works.
I must admit that I have spent more than an hour devising different solutions to the scrollbar-problem and they all looked or worked bad. Switching between column resize-modes, listening to components resizing, it was all bullshit and it’s all due to the notorious bug #4127936. Luckily it’s all much simpler than that, had I just known before!

Resizing column widths to the content size

First of all, let’s produce a method that would fit the column sizes to the column content. This is the easiest task. I have seen numerous solutions, where arbitrary amounts of space have been added, labels have been used as surrogates to the cell renderers and whatnot. Why give yourself such a hard time? Just use the cell renderers themselves:

public static void fitToContentWidth(final JTable table, final int column) {
 int width = 0;
 for (int row = 0; row < table.getRowCount(); ++row) {
  final Object cellValue = table.getValueAt(row, column);
  final TableCellRenderer renderer = table.getCellRenderer(row, column);
  final Component comp 
   = renderer.getTableCellRendererComponent(table, cellValue, false, false, row, column);
  width = Math.max(width, comp.getPreferredSize().width);
 }
 final TableColumn tc = table.getColumn(table.getColumnName(column));
 width += table.getIntercellSpacing().width * 2;
 tc.setPreferredWidth(width);
 tc.setMinWidth(width);
}

Voila! One of the things that are easily forgotten is the “intercell-spacing” and you need to add that or you could get some nasty surprises. Margins and insets are all handled by the cell renderers themselves. Call the method whenever you think fits best, you should, however, not call it if the table is empty.

Horizontal scrollbars if table too large, fill panel if table too small

This sentence no verb. This solution easy:

final JTable table = new JTable() {
 //workaround for  http://bugs.sun.com/view_bug.do?bug_id=4127936
 @Override
 public boolean getScrollableTracksViewportWidth() {
  return getRowCount() == 0 ? super.getScrollableTracksViewportWidth()
         : getPreferredSize().width < getParent().getWidth();
  }
};

The method “getScrollableTracksViewportWidth” is a naming disaster, every time I read it, I’m confused, it should really be called something like “isViewportWidthDecisive” and what it means is that this method should return true, if the width of this scrollable component should match the viewport width. This implementation simply states that if the component is larger than the viewport, size it as you always do, but if this table is smaller than its parent (the viewport) then let the viewport width be the one that counts.

SSCCE – A short self contained compilable example

Just compile and run this little code and resize the window. You will see that the column widths will shrink until they match the content, and if you make the window smaller, a scroll bar will appear.


import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;

public class TableResizingExample extends JPanel {

 TableResizingExample() {
  setLayout(new BorderLayout());

  final JTable table = new JTable() {
   // workaround for http://bugs.sun.com/view_bug.do?bug_id=4127936
   @Override
   public boolean getScrollableTracksViewportWidth() {
    return getRowCount() == 0 ? super.getScrollableTracksViewportWidth() 
           : getPreferredSize().width < getParent().getWidth();
   }
  };
  // table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
  table.setAutoCreateColumnsFromModel(true);

  table.setModel(new DefaultTableModel(
   // data:
   new Object[][] {
    new Object[] { "Fred Flintstone", "Footmobile Golden Rocket" }, 
    new Object[] { "Inspector Gadget", "Gadgetmobile" }, 
    new Object[] { "Marty McFly", "De Lorean DMC-12 Time Machine" }, 
    new Object[] { "James Bond", "Aston Martin DB5" }, 
    new Object[] { "Michael Knight", "Pontiac Firebird SE - Knight Industries Two Thousand" }, 
   },
   // column names:
   new Object[] { "Name", "Value" })
  );

  final JScrollPane scroll = new JScrollPane(table);
  add(scroll, BorderLayout.CENTER);

  fitToContentWidth(table, 0);
  fitToContentWidth(table, 1);
 }

 public static void fitToContentWidth(final JTable table, final int column) {
  int width = 0;
  for (int row = 0; row < table.getRowCount(); ++row) {
   final Object cellValue = table.getValueAt(row, column);
   final TableCellRenderer renderer = table.getCellRenderer(row, column);
   final Component comp 
    = renderer.getTableCellRendererComponent(table, cellValue, false, false, row, column);
   width = Math.max(width, comp.getPreferredSize().width);
  }
  final TableColumn tc = table.getColumn(table.getColumnName(column));
  width += table.getIntercellSpacing().width * 2;
  tc.setPreferredWidth(width);
  tc.setMinWidth(width);
 }

 public static void main(final String[] args) {
  EventQueue.invokeLater(new Runnable() {
   @Override
   public void run() {
    final JFrame frame = new JFrame();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.getContentPane().add(new TableResizingExample());
    frame.setVisible(true);
    frame.pack();
   }
  });
 }
}

Things you might like to try:

  1. Put the overridden method in the JTable into comments, you will see that the scroll bar will no longer appear if you make the window smaller than the table.
  2. With the same modification as in point 1, additionally use the “table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);”. The scrollbar will appear, but if you make the window larger than the table, the table will leave an ugly empty space on the right.

Feel free to experiment with setting a maximum size for your columns.

Advertisements
Categories: Swing Tags: , , , ,
  1. Clint
    January 26, 2016 at 19:55

    I arrived here by accident, looking at other JTable things…So this doesn’t work right when your table is inside a JScrollPane that is NORTH/EAST//WEST/SOUTH in BorderLayout (Java 8, and probably earlier). BorderLayout behavior seems to override everything except getMin/getPreferredSize, so my table always was too wide (like 500 px)

    • January 29, 2016 at 18:53

      You’re absolutely right.
      The reason for that is that the BorderLayout will always use the component’s current size (which defaults to the preferredSize) for a component in the border, thus the viewport will not automatically get smaller than the preferredSize, which means you will not see the scrollbars.
      This can be seen in the BorderLayout’s “layoutContainer(Container target)” method:

             if ((c=getChild(WEST,ltr)) != null) {
                  c.setSize(c.width, bottom - top);
                  Dimension d = c.getPreferredSize();
                  c.setBounds(left, top, d.width, bottom - top);
                  left += d.width + hgap;
              }
              if ((c=getChild(CENTER,ltr)) != null) {
                  c.setBounds(left, top, right - left, bottom - top);
              }
      

      So you’d either have to put the table into the center of the BorderLayout, or use another LayoutManager, such as the excellent “FormLayout”.

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: