Search code examples
javaswinglayout-managerjscrollpane

Java JSCrollPane won't resize below minimum size of JButton with text


I'm trying put a JPanel (GridLayout 0, 1) with JButtons it, within the ViewPort of a JScrollPane. This is easy enough to do, but it doesn't behave "properly" when resizing the parent JFrame. Buttons resize to fill the entire panel as intended, but with one major limitation. If I attempt to resize the frame horizontally (in run-time by corner dragging), it stops after a point. The panel will extend infinitely, but it won't contract below the minimum size necessary to display the Text on the button with the shortest Text.

Resizing the frame vertically has no such issue. The frame will contract down to nothing. The buttons will squish until they hit their MinimumSize, after which point they simply clip outside the frame. This is the behaviour I want, because I intend to stick the whole thing inside a JScrollPane, at which point a scrollbar would appear. I want the same behaviour horizontally, but I can't get it no matter what I try.

So far, I've tried using GridLayout and GridBagLayout. The latter seems to be needlessly heavy-handed for just a single column of buttons. However, because this is a column, I can't use FlowLayout as that arranges objects laterally and I don't know how to change it. More to the point - both GridLayout and GridBagLayout full space and justify buttons in a way that I like, which I haven't been able to accomplish with other layout managers.

Major edit

This issue has taken me to so many dead ends that I've decided to clip the individual stream of consciousness in favour of unified summary of what happened. The relevant example code looks like this:

public static void main(String[] args)
{
    JFrame frame = new JFrame();
    frame.setSize(500, 500);
    frame.setLocation(200, 200);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
    JButton button1 = new JButton("Button 1");
    JButton button2 = new JButton("------Button 2------");
    JButton button3 = new JButton("Button 3");
    
    JPanel buttonPanel = new JPanel(new GridLayout(0, 1));
    buttonPanel.add(button1);
    buttonPanel.add(button2);
    buttonPanel.add(button3);
    
    JButton button7 = new JButton("Button 1");
    JButton button8 = new JButton("------Button 2------");
    JButton button9 = new JButton("Button 3");
    
    JPanel buttonPanel2 = new JPanel(new GridLayout(0, 1));
    buttonPanel2.add(button7);
    buttonPanel2.add(button8);
    buttonPanel2.add(button9);
    
    JScrollPane scrollPaneA = new JScrollPane();
    scrollPaneA.getViewport().add(buttonPanel);
    scrollPaneA.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
    scrollPaneA.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
    
    JScrollPane scrollPaneB = new JScrollPane();
    scrollPaneB.getViewport().add(buttonPanel2);
    scrollPaneB.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
    scrollPaneB.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
    
    JTabbedPane tabbedPane = new JTabbedPane();
    tabbedPane.addTab("Left", scrollPaneA);
    tabbedPane.addTab("Right", scrollPaneB);
    
    JTextArea textArea = new JTextArea("Example Text");
    
    JScrollPane textScrollpane = new JScrollPane();
    textScrollpane.getViewport().add(textArea);
    textScrollpane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
    textScrollpane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
    
    JButton button4 = new JButton("Button 4");
    button4.addActionListener(new ButtonListener(tabbedPane, 0));
    JButton button5 = new JButton("------Button 5------");
    JButton button6 = new JButton("Button 6");
    button6.addActionListener(new ButtonListener(tabbedPane, 1));
    
    JPanel bottomPanel = new JPanel(new GridLayout(0, 1));
    bottomPanel.add(button4);
    bottomPanel.add(button5);
    bottomPanel.add(button6);
    
    JScrollPane rightScrollPane = new JScrollPane
    (
            bottomPanel,
            JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
            JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED
    );
    
    JSplitPane rightSplitPane = new JSplitPane
    (
            JSplitPane.VERTICAL_SPLIT,
            textScrollpane,
            rightScrollPane
    );
    rightSplitPane.setDividerLocation(100);
    
    JSplitPane mainSplitPane = new JSplitPane
    (
            JSplitPane.HORIZONTAL_SPLIT,
            tabbedPane,
            rightSplitPane
    );
    mainSplitPane.setDividerLocation(100);
    
    frame.add(mainSplitPane);
    frame.setVisible(true);
}

public class ButtonListener implements ActionListener
{
    private JTabbedPane tabbedPane;
    private int tab;

    public ButtonListener(JTabbedPane tabbedPane, int tab)
    {
        this.tabbedPane = tabbedPane;
        this.tab = tab;
    }

    @Override
    public void actionPerformed(ActionEvent e)
    {
        JButton button1 = new JButton("Button ?");
        JButton button2 = new JButton("------Button 2?------");
        JButton button3 = new JButton("Button ?");

        JPanel panel = new JPanel(new GridLayout(0, 1));
        panel.add(button1);
        panel.add(button2);
        panel.add(button3);

        this.tabbedPane.setComponentAt(this.tab, panel);
    }
}

This is essentially what I had in my programme. The JTabbedPane started out scrolling properly as one would expect. However, the moment I edited any of the button lists, scrolling broke. As you can see in the listener, I simply replace the entire panel associated with a tab. As soon as I did that, the JTabbedPane would stop scrolling.

I thought this was caused by the minimum size of the JButtons somehow overriding the JScrollPane's ability to scroll, hence the title of the question. While that was a factor, the actual root cause could be found in the ActionListener: this.tabbedPane.setComponentAt(this.tab, panel);

My design called for each tab to hold its own JScrollPane object, which held a JPanel within its Viewport. What I was actually doing was placing the JPanel directly into the tab. Of course it wouldn't scroll - I was orphaning the JScrollPane which was supposed to do the scrolling.

The solution was embarrassingly simple. I just fixed the tab replacement line to set the viewport on the JScrollPane, rather than replacing the Component in the tab. Essentially, this:

public void actionPerformed(ActionEvent e)
{
    JButton button1 = new JButton("Button ?");
    JButton button2 = new JButton("------Button 2?------");
    JButton button3 = new JButton("Button ?");
    
    JPanel panel = new JPanel(new GridLayout(0, 1));
    panel.add(button1);
    panel.add(button2);
    panel.add(button3);
    
    //this.tabbedPane.setComponentAt(this.tab, panel);
    JScrollPane scrollPane = (JScrollPane)this.tabbedPane.getComponentAt(tab);
    scrollPane.getViewport().add(panel);
}

And now it works exactly as intended. This was entirely my fault for getting lost in the structure, and forgetting that tabs are supposed to hold JScrollPanes, not JPanels.


Solution

  • So, after wrestling with this issue for a few days and going down multiple leads which turned into dead ends, I finally found the issue at the root of it - and it was PEBKAC on my part. It wasn't the JFrame title bar, it wasn't the layout managers, it wasn't minimum size.

    I was placing the JButtons inside a JPanel inside a JScrollPane inside the tabs of a JTabbedPane, which is inside one side of a JSplitPane. At certain events, the list of buttons was changed and the entire panel redrawn. In the process of rebuilding the JPanel, I was assigning that as the JSPlitPane's tab component. Of course it wouldn't scroll - I was orphaning the JSPlitPane and just using the panel directly.

    Changing the ActionListener to properly assign the JSplitPane as a Component for the Tab fixed the issue entirely. And made me feel really silly for basic this mistake was...