Search code examples
javaswingconstraintsjscrollpanespringlayout

Java - Constraining Components below JScrollPanes with runtime change


I have a JFrame with a JScrollPane containing a JTable. Below the JTable I have placed a JButton with the SpringLayout:

l.putConstraint(SpringLayout.NORTH, but, 50, SpringLayout.SOUTH, sP);

The JButton adds a row to the JTable and updates the size of the JScrollPane. As they are constraint with each other I want the button to update his position and move downwards. However, it does not happen.
As I tried the same with just a plain JTable it worked fine but I need this JScrollPane to avoid that the JTable extends itself beyond the frame.

Here is my full test code:

package tests;

import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SpringLayout;
import javax.swing.table.DefaultTableModel;

public class StackOverflowQRuntimeAlignment extends JFrame{

    private JScrollPane sP;
    private JTable tab;
    private JButton but;
    private DefaultTableModel dtm;

    public static void main(String[] args){
        StackOverflowQRuntimeAlignment frame = new StackOverflowQRuntimeAlignment("Test");
        frame.setSize(new Dimension(1280, 720));
        frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
        frame.setVisible(true);
    }

    StackOverflowQRuntimeAlignment(String title){
        super(title);
        //DefaultTabelModel to create the Table
        dtm = new DefaultTableModel(new String[][]{{"data1", "data2"}}, new String[]{"Column1", "Column2"});
        tab = new JTable(dtm);
        //set TableHeaderHeight to RowHeight
        tab.getTableHeader().setPreferredSize(new Dimension(tab.getWidth(), tab.getRowHeight()));
        //create ScrollPane containing the Table
        sP = new JScrollPane(tab);
        //set the size of the ScrollPane
        sP.setPreferredSize(new Dimension(500, (dtm.getRowCount()+1)*16+3));//+1 for the Header, +3 to hide ScrollBars if they are not neccesary
        sP.setMaximumSize(new Dimension(tab.getWidth(), 360));

        //create the button to add an row to the table
        but = new JButton("Hit me");
        but.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                dtm.addRow(new String[]{"Banana", "Apple"});
                //updating the size of the ScrollPane
                sP.setPreferredSize(new Dimension(500, (dtm.getRowCount()+1)*16+3));
                sP.setSize(sP.getPreferredSize());
            }
        });

        //constraining the Components on the GUI
        SpringLayout l = new SpringLayout();
        this.setLayout(l);
        l.putConstraint(SpringLayout.HORIZONTAL_CENTER, sP, 0, SpringLayout.HORIZONTAL_CENTER, this.getContentPane());
        l.putConstraint(SpringLayout.NORTH, sP, 50, SpringLayout.NORTH, this.getContentPane());

        l.putConstraint(SpringLayout.HORIZONTAL_CENTER, but, 0, SpringLayout.HORIZONTAL_CENTER, sP);
        l.putConstraint(SpringLayout.NORTH, but, 50, SpringLayout.SOUTH, sP);

        //adding the Components to the GUI
        this.add(sP);
        this.add(but);
    }
}

Solution

  • The point of using a JScrollPane is that you don't keep adjusting its size. As the preferred size of the components added to the scrollpane increases, scrollbars will appear on the scrollpane. So create your GUI with a default preferred size for the scroll pane and let Swing layout manager work.

    Typically the scroll pane will be added to the CENTER of the BorderLayout on the frame so that as the user resizes the frame the space is allocated to the scroll pane.

    However the general rules for adding (or removing) components from a visible GUI is to use:

    panel.add(...);
    panel.revalidate(); // to invoke the layout manager
    panel.repaint(); // to paint the components