Search code examples
javaswingjtablelayout-managergridbaglayout

GridBagLayout: how to fill vertically using preferred width horizontally


I have an issue trying to fill vertically a JScrollPane (which contains a JTable) inside a JPanel: the panel should not be filled horizontally, instead, the JScrollPane should take only the "necessary" space.

I'm using some code found on SO to set the table preferred width according to its content (i'm also overrding JTable's getPreferredScrollableViewportSize method).

This is a screenshot of what i want to achieve:

enter image description here

The JPanel is wider because in my application it will be part of a CardLayout, so the other cards width and height will determine this panel size (in the code below i'm overriding getPreferredSize method for convenience, but i don't want to use it).

This result can be achieved using the below code, you only need to change this line of code:

Object [][] data = new Object [100][1];

to:

Object [][] data = new Object [10][1];

The problem is when the JTable has more rows, in this case the vertical scrollbar will appear, but the scroll pane will "shrink":

enter image description here

This is the code i'm using:

import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.TableColumn;
public class TableTest
{
    public static void main (String [] a) {
        SwingUtilities.invokeLater (new Runnable () {
            @Override public void run () {
                createAndShowGUI ();
            }
        });
    }
    private static void createAndShowGUI () {
        JFrame frame = new JFrame ("Table Test");
        frame.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
        frame.setContentPane (new MainPanel ());
        frame.pack ();
        frame.setLocationRelativeTo (null);
        frame.setVisible (true);
    }
}
class MainPanel extends JPanel
{
    private static int MAX_HEIGHT = 600;
    private static int MAX_WIDTH = 600;

    private JTable table;

    protected MainPanel () {
        super (new GridBagLayout ());
        Object [][] data = new Object [100][1];
        for (int i = 0; i < 10; i ++) data [i] = new String [] {"Some data ..."};
        table = new JTable (data, new String [] {""}) {
            @Override public Dimension getPreferredScrollableViewportSize () {
                int width = 0;
                for (int column = 0; column < getColumnCount (); column ++) width += columnModel.getColumn (column).getPreferredWidth ();
                return new Dimension (Math.min (MAX_WIDTH, width), Math.min (MAX_HEIGHT, getRowHeight () * getRowCount ()));
            }
        };
        table.setAutoResizeMode (JTable.AUTO_RESIZE_OFF);
        table.setShowGrid (false);
        table.setTableHeader (null);
        resizeColumns ();
        JScrollPane scrollPane = new JScrollPane (table);
        scrollPane.setBackground (table.getBackground ());
        scrollPane.getViewport ().setBackground (table.getBackground ());
        // --- Adding components ---
        GridBagConstraints c = new GridBagConstraints ();
        c.fill = GridBagConstraints.VERTICAL;
        c.weightx = 0.0;
        c.weighty = 1.0;
        add (scrollPane, c);
    }
    @Override public Dimension getPreferredSize () {
        return new Dimension (500, 400);
    }
    protected void resizeColumns () {
        for (int column = 0; column < table.getColumnCount (); column ++) {
            TableColumn tableColumn = table.getColumnModel ().getColumn (column);
            int width = 0;
            for (int row = 0; row < table.getRowCount (); row ++) {
                width = Math.max (table.prepareRenderer (table.getCellRenderer (row, column), row, column).getPreferredSize ().width, width);
            }
            tableColumn.setPreferredWidth (width + table.getIntercellSpacing ().width);
        }
    }
}

If i set c.fill to GridBagConstraints.BOTH and c.weightx to any positive value, the JScrollPane will fill the parent panel both horizontally and vertically.

How can i let the table's preferred width to be used without manually setting the JScrollPane size?

EDIT

My code is very minimal, since i saw that my problem was not related to the number of table columns. But my real program has to deal with more columns, and a huge number of rows (a million or more in many cases).

A JList was my first try, since i liked the way i could use a prototype value, but it was too slow to load the data (which can also be updated at runtime), all the GUI freezed with more than 200 000 rows, also due to the fact that i'm using a custom renderer.

Also, i already tried to use a BorderLayout, but i really want the scrollpane height to fill my panel, i have tried different solutions, but still i can't solve it.

EDIT2

As @camickr suggests, i could use a BorderLayout for my panel, adding the scrollpane at BorderLayout.WEST to fill the panel vertically wihout taking all the horizontal space. This is the best solution for now, but this will cause the horizontal center alignment to be lost. Any ideas?


Solution

  • (in the code below i'm overriding getPreferredSize method for convenience, but i don't want to use it

    I agree. Get rid of the override.

    The painting problem when you decrease the height of the frame is because the GridBagLayout will paint the component at its minimum size when there is not enough space.

    So, although in general I don't like using any of the set??? methods to hardcode a size the following seems to work:

    scrollPane.setMinimumSize(scrollPane.getPreferredSize());
    

    This will keep the scrollpane width from decreasing.

    If you don't like playing with hardcoded sizes you could use the Relative Layout. This will easily allow you to center a component.

    Then basic code would be:

    RelativeLayout rl = new RelativeLayout(RelativeLayout.X_AXIS);
    rl.setFill( true );
    setLayout(rl);
    
    ...
    
    add(Box.createHorizontalGlue(), new Float(1));
    add(scrollPane);
    add(Box.createHorizontalGlue(), new Float(1));