Just listen to Alex

August 7, 2011

Java Swing: automatically resize table columns to their contents

Filed under: Uncategorized — Tags: , — bosmeeuw @ 4:45 pm

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();
            }
        });
    }
}
About these ads

8 Comments »

  1. [...] Source and Code: http://bosmeeuw.wordpress.com/2011/08/07/java-swing-automatically-resize-table-columns-to-their-cont… Related Posts:Designing a Swing GUI in NetBeans IDEJava #N3 – Making a GUI in [...]

    Pingback by Java Swing: Automatically Resize Table Columns — August 19, 2011 @ 12:52 pm

  2. Hi there,

    Nice blog! Is there an email address I can contact you in private?

    Comment by Ilias Tsagklis — September 20, 2011 @ 6:09 pm

  3. Hey… I was wondering if you could update your “Batch downloading cover art with PHP and Google Image Search”-script? I’m working on a game (with Game Maker) which should use images from the google image-search (100×100 px) by various variables. Is there a possibility to change the script so it uses a variable from another source, looks for images with the name of the variable and downloads 20 or 30 of them to the harddisk?
    Would be great if you could help, as my programming skills are quite bad.

    Thanks a lot!

    HobbEs

    Comment by HobbEs — November 30, 2011 @ 2:23 pm

  4. Took me some time to get this fully working; I had to change as below:

    package com.innaxys.tiu.data.reports;

    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) {
    System.out.println(“Size”);
    sizeColumnsToFit(table, 5);
    }

    public static void sizeColumnsToFit(JTable table, int columnMargin) {
    JTableHeader tableHeader = table.getTableHeader();

    // can’t auto size a table without a header
    if (tableHeader == null)
    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 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]);
    table.getColumnModel().getColumn(i).setPreferredWidth(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 = table.getDefaultRenderer(Object.class);
    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.getMaximumSize().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 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;
    }

    }

    Comment by Mark — September 27, 2012 @ 2:34 pm

  5. Hello there! This is my first visit to your blog!
    We are a team of volunteers and starting a new initiative in a community in the same niche.
    Your blog provided us beneficial information to work on.

    You have done a wonderful job!

    Comment by where to buy how to make money on youtube in india pricing — July 9, 2013 @ 1:14 am

  6. Very helpful. Thanks

    Comment by Diogo — August 24, 2013 @ 11:45 pm

  7. Your demo doesn’t work as it stands!

    Comment by graahm — March 28, 2014 @ 2:26 pm

  8. This works… bad post… don’t post until you know it works dude!

    public static void sizeColumnsToFit(JTable table) {
    final TableColumnModel columnModel = table.getColumnModel();
    for (int column = 0; column < table.getColumnCount(); column++) {
    int width = 50; // Min width
    for (int row = 0; row < table.getRowCount(); row++) {
    TableCellRenderer renderer = table.getCellRenderer(row, column);
    Component comp = table.prepareRenderer(renderer, row, column);
    width = Math.max(comp.getPreferredSize().width, width);
    }
    columnModel.getColumn(column).setPreferredWidth(width);
    }
    }

    Comment by graahm — March 28, 2014 @ 2:49 pm


RSS feed for comments on this post. TrackBack URI

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

The Shocking Blue Green Theme Blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: