When using JTables in Swing, you will notice that by default, all columns have the same size. Either the user can resize the columns to better represent the data in the column (which they need to do every time they open the screen), or the programmer can decide to set a specific width on each column. Of course, the programmer might not always know what kind of data will show up in the table, and what width is required for each column.
The easiest solution to this problem, is to automically resize table columns based on the data in the table, like shown in the video below:
The way this works, is that we render the entire table (invisible), and check the maxium required width for each column. If the table is big enough to display all data, we size all columns to display everything. If the table is too small, for instance when one of the cells contains a very long string, we make the biggest column(s) smaller until the data does fit.
The code to execute the resize is very simple:
ColumnsAutoSizer.sizeColumnsToFit(table);
We can also hook up an event listener to automatically resize the columns every time data is added or changed in the table, like this:
table.getModel().addTableModelListener(new TableModelListener() { public void tableChanged(TableModelEvent e) { ColumnsAutoSizer.sizeColumnsToFit(table); } });
You’ll find the source for ColumnsAutoSizer and the demo app below:
package be.alex.examples; import javax.swing.*; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.JTableHeader; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import java.awt.Component; import java.awt.FontMetrics; public class ColumnsAutoSizer { public static void sizeColumnsToFit(JTable table) { sizeColumnsToFit(table, 5); } public static void sizeColumnsToFit(JTable table, int columnMargin) { JTableHeader tableHeader = table.getTableHeader(); if(tableHeader == null) { // can't auto size a table without a header return; } FontMetrics headerFontMetrics = tableHeader.getFontMetrics(tableHeader.getFont()); int[] minWidths = new int[table.getColumnCount()]; int[] maxWidths = new int[table.getColumnCount()]; for(int columnIndex = 0; columnIndex < table.getColumnCount(); columnIndex++) { int headerWidth = headerFontMetrics.stringWidth(table.getColumnName(columnIndex)); minWidths[columnIndex] = headerWidth + columnMargin; int maxWidth = getMaximalRequiredColumnWidth(table, columnIndex, headerWidth); maxWidths[columnIndex] = Math.max(maxWidth, minWidths[columnIndex]) + columnMargin; } adjustMaximumWidths(table, minWidths, maxWidths); for(int i = 0; i < minWidths.length; i++) { if(minWidths[i] > 0) { table.getColumnModel().getColumn(i).setMinWidth(minWidths[i]); } if(maxWidths[i] > 0) { table.getColumnModel().getColumn(i).setMaxWidth(maxWidths[i]); table.getColumnModel().getColumn(i).setWidth(maxWidths[i]); } } } private static void adjustMaximumWidths(JTable table, int[] minWidths, int[] maxWidths) { if(table.getWidth() > 0) { // to prevent infinite loops in exceptional situations int breaker = 0; // keep stealing one pixel of the maximum width of the highest column until we can fit in the width of the table while(sum(maxWidths) > table.getWidth() && breaker < 10000) { int highestWidthIndex = findLargestIndex(maxWidths); maxWidths[highestWidthIndex] -= 1; maxWidths[highestWidthIndex] = Math.max(maxWidths[highestWidthIndex], minWidths[highestWidthIndex]); breaker++; } } } private static int getMaximalRequiredColumnWidth(JTable table, int columnIndex, int headerWidth) { int maxWidth = headerWidth; TableColumn column = table.getColumnModel().getColumn(columnIndex); TableCellRenderer cellRenderer = column.getCellRenderer(); if(cellRenderer == null) { cellRenderer = new DefaultTableCellRenderer(); } for(int row = 0; row < table.getModel().getRowCount(); row++) { Component rendererComponent = cellRenderer.getTableCellRendererComponent(table, table.getModel().getValueAt(row, columnIndex), false, false, row, columnIndex); double valueWidth = rendererComponent.getPreferredSize().getWidth(); maxWidth = (int) Math.max(maxWidth, valueWidth); } return maxWidth; } private static int findLargestIndex(int[] widths) { int largestIndex = 0; int largestValue = 0; for(int i = 0; i < widths.length; i++) { if(widths[i] > largestValue) { largestIndex = i; largestValue = widths[i]; } } return largestIndex; } private static int sum(int[] widths) { int sum = 0; for(int width : widths) { sum += width; } return sum; } }
Demo app:
package be.alex.examples; import javax.swing.*; import javax.swing.event.TableModelEvent; import javax.swing.event.TableModelListener; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; public class ResizeColumnsDemo extends JPanel { public ResizeColumnsDemo() { super(new FlowLayout()); String[] columnNames = {"First Name", "Last Name", "Sport", "# of Years", "Vegetarian"}; Object[][] data = { {"Kathy", "Smith", "Snowboarding", new Integer(5), new Boolean(false)}, {"John", "Doe", "Rowing", new Integer(3), new Boolean(true)}, {"Sue", "Black", "Knitting", new Integer(2), new Boolean(false)}, {"Jane", "White", "Speed reading", new Integer(20), new Boolean(true)}, {"Joe", "Brown", "Pool", new Integer(10), new Boolean(false)} }; final JTable table = new JTable(data, columnNames); table.setPreferredScrollableViewportSize(new Dimension(500, 70)); table.setFillsViewportHeight(true); // automatically resize the columns whenever the data in the table changes table.getModel().addTableModelListener(new TableModelListener() { public void tableChanged(TableModelEvent e) { ColumnsAutoSizer.sizeColumnsToFit(table); } }); JButton autoSizeButton = new JButton("Auto-size columns"); // resize the columns when the user clicks the button autoSizeButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { ColumnsAutoSizer.sizeColumnsToFit(table); } }); JButton setLongNameButton = new JButton("Set longer name"); // set a longer name to test automatic resizing after value changes setLongNameButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { table.getModel().setValueAt("Kathy Kathy Kathy", 0, 0); } }); JButton setVeryLongNameButton = new JButton("Set very long name"); // set a longer name to test automatic resizing after value changes setVeryLongNameButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { table.getModel().setValueAt("Kathy Kathy Kathy Kathy Kathy Kathy Kathy Kathy Kathy Kathy Kathy", 0, 0); } }); //Create the scroll pane and add the table to it. JScrollPane scrollPane = new JScrollPane(table); //Add the scroll pane to this panel. add(scrollPane); add(autoSizeButton); add(setLongNameButton); add(setVeryLongNameButton); } /** * Create the GUI and show it. For thread safety, * this method should be invoked from the * event-dispatching thread. */ private static void createAndShowGUI() { //Create and set up the window. JFrame frame = new JFrame("SimpleTableDemo"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //Create and set up the content pane. ResizeColumnsDemo newContentPane = new ResizeColumnsDemo(); newContentPane.setOpaque(true); //content panes must be opaque frame.setContentPane(newContentPane); //Display the window. frame.setSize(600, 200); frame.setVisible(true); } public static void main(String[] args) { //Schedule a job for the event-dispatching thread: //creating and showing this application's GUI. javax.swing.SwingUtilities.invokeLater(new Runnable() { public void run() { createAndShowGUI(); } }); } }