MigLayout resizing with JScrollPane

java, miglayout, swing

Solution

Focusing on your status area and using nested layouts produces the result shown below. Note in particular,

- Use `invokeLater()` to construct the GUI on the EDT.

- Use `javax.swing.Timer` to update the GUI on the EDT.

- Use `pack()` to make the window fit the preferred size and layouts of its subcomponents.

- Use the update policy of `DefaultCaret` to control scrolling.

- Avoid needless lazy instantiation in public accessors.

- Avoid `setXxxSize()`; override `getXxxSize()` judiciously.

- Critically examine the decision to extend `JFrame`.

import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JTabbedPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.Timer;
import javax.swing.border.TitledBorder;
import javax.swing.text.DefaultCaret;

public class Tab extends JFrame {

    private JTextArea messageTextArea;
    private JPanel optionPanel, messagePanel;
    private JTabbedPane plotTabPane;

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {

                final Tab tab = new Tab();
                tab.setVisible(true);
                Timer t = new Timer(200, new ActionListener() {

                    int count = 0;

                    @Override
                    public void actionPerformed(ActionEvent e) {
                        tab.printRawMessage("testMessage" + count++);
                    }
                });
                t.start();
            }
        });
    }

    public Tab() {
        initComponents();
    }

    private void initComponents() {
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JPanel mainPanel = new JPanel(new GridLayout(1, 0));
        Box leftPanel = new Box(BoxLayout.Y_AXIS);
        leftPanel.add(getLeftTopPanel());
        leftPanel.add(getMessagePanel());
        mainPanel.add(leftPanel);
        mainPanel.add(getRightPanel());
        this.add(mainPanel);
        this.pack();
        this.setLocationRelativeTo(null);
    }

    protected JPanel getLeftTopPanel() {
        optionPanel = new JPanel();
        optionPanel.setBorder(BorderFactory.createTitledBorder(null,
            "Configuration", TitledBorder.LEFT, TitledBorder.TOP,
            new Font("null", Font.BOLD, 12), Color.BLUE));
        JLabel label = new JLabel("Choose");
        label.setHorizontalAlignment(JLabel.RIGHT);
        optionPanel.add(label);
        optionPanel.add(new JSeparator(JSeparator.VERTICAL));
        optionPanel.add(new JComboBox(
            new String[]{"option1", "option2", "option3"}));
        optionPanel.add(new JLabel("Type"));
        optionPanel.add(new JTextField("3"));
        return optionPanel;
    }

    protected JTabbedPane getRightPanel() {
        plotTabPane = new JTabbedPane();
        plotTabPane.add("Tab1", new JPanel());
        plotTabPane.add("Tab2", new JPanel());
        return plotTabPane;
    }

    protected JPanel getMessagePanel() {
        messagePanel = new JPanel(new GridLayout());
        messagePanel.setBorder(BorderFactory.createTitledBorder(null,
            "Status Console", TitledBorder.LEFT, TitledBorder.TOP,
            new Font("null", Font.BOLD, 12), Color.BLUE));
        final JScrollPane sp = new JScrollPane(getMessageTextArea());
        sp.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
        messagePanel.add(sp);
        return messagePanel;
    }

    protected JTextArea getMessageTextArea() {
        messageTextArea = new JTextArea("", 10, 19);
        messageTextArea.setEditable(false);
        messageTextArea.setFont(new Font(null, Font.PLAIN, 20));
        messageTextArea.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
        DefaultCaret caret = (DefaultCaret) messageTextArea.getCaret();
        caret.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE);
        return messageTextArea;
    }

    public void printRawMessage(String rawMessage) {
        messageTextArea.append(rawMessage + "\n");
    }
}

Problem

I searched a little bit and did not find a good answer to my problem. I am working on a gui that has to be resizable. It contains a status `JTextArea` that is inside a `JScrollPane`. And this is my problem. As long as I don't manually resize my JFrame, the "initial" layout is kept and everything looks fine. As soon as I manually resize (if the `JTextArea` is already in scrolled mode), the layout gets messed up. Here is a SSCCE (I got rid of most of the parts while keeping the structure of the code. I hope it's more readable that way): ``` import java.awt.Color; import java.awt.Font; import javax.swing.BorderFactory; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSeparator; import javax.swing.JSlider; import javax.swing.JTabbedPane; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.border.TitledBorder; import net.miginfocom.swing.MigLayout; public class Tab extends JFrame { private static final long serialVersionUID = 1L; private JTextArea messageTextArea; private JPanel optionPanel, messagePanel; private JTabbedPane plotTabPane; public static void main(String[] args) { final Tab tab = new Tab(); tab.setSize(1000, 600); tab.setVisible(true); new Thread(new Runnable() { @Override public void run() { int count = 0; tab.printRawMessage("start"); while (true) { try { Thread.sleep(200); } catch (InterruptedException e) {} tab.printRawMessage("\ntestMessage" + count++); } } }).start(); } public Tab() { super(); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); initComponents(); } private void initComponents() { JPanel mainPanel = new JPanel(); mainPanel.setLayout(new MigLayout("insets 0", "", "")); mainPanel.add(getLeftTopPanel(), "shrinky, top, w 450!"); mainPanel.add(getRightPanel(), "spany 5, wrap, grow, pushx, wmin 400"); mainPanel.add(getMessagePanel(), "pushy, growy, w 450!"); JScrollPane contentScrollPane = new JScrollPane(mainPanel); contentScrollPane.setBorder(BorderFactory.createEmptyBorder()); setContentPane(contentScrollPane); } protected JPanel getLeftTopPanel() { if (optionPanel == null) { optionPanel = new JPanel(); optionPanel.setBorder(BorderFactory.createTitledBorder(null, "Configuration", TitledBorder.LEFT, TitledBorder.TOP, new Font("null", Font.BOLD, 12), Color.BLUE)); optionPanel.setLayout(new MigLayout("insets 0", "", "top, align 50%")); JLabel label = new JLabel("Choose"); label.setHorizontalAlignment(JLabel.RIGHT); optionPanel.add(label, "w 65!"); optionPanel.add(new JSeparator(JSeparator.VERTICAL), "spany 5, growy, w 2!"); optionPanel.add(new JComboBox(new String[] {"option1", "option2", "option3"}), "span, growx, wrap"); optionPanel.add(new JLabel("Type"), "right"); optionPanel.add(new JTextField("3"), "w 65!, split 2"); optionPanel.add(new JLabel("Unit"), "wrap"); optionPanel.add(new JLabel("Slide"), "right"); optionPanel.add(new JSlider(0, 100), "span, growx, wrap"); } return optionPanel; } protected JTabbedPane getRightPanel() { if (plotTabPane == null) { plotTabPane = new JTabbedPane(); plotTabPane.add("Tab1", new JPanel()); plotTabPane.add("Tab2", new JPanel()); } return plotTabPane; } protected JPanel getMessagePanel() { if (messagePanel == null) { messagePanel = new JPanel(); messagePanel.setBorder(BorderFactory.createTitledBorder(null, "Status Console", TitledBorder.LEFT, TitledBorder.TOP, new Font("null", Font.BOLD, 12), Color.BLUE)); messagePanel.setLayout(new MigLayout("insets 0", "", "top, align 50%")); messagePanel.add(new JScrollPane(getMessageTextArea()), "push, grow"); } return messagePanel; } protected JTextArea getMessageTextArea() { if (messageTextArea == null) { messageTextArea = new JTextArea(); messageTextArea.setEditable(false); messageTextArea.setFont(new Font(null, Font.PLAIN, 20)); messageTextArea.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4)); } return messageTextArea; } public void printRawMessage(String rawMessage) { getMessageTextArea().append(rawMessage); getMessageTextArea().setCaretPosition(getMessageTextArea().getText().length()); } } ``` The layout stuff basically happens in `initComponents()`. To see the problem: - Start the Application (I used miglayout-4.0-swing.jar). - Wait a bit (don't resize the window), until there are enough messages to create the scrollbar in the status text area. - Now this is what I want. The `JTextArea` goes all the way to the bottom of the JFrame and is scrolled if neccessary. - Now resize the window. As you can see, everything gets messed up. It will only be fine, if the window is maximized. Here are two screenshots. The first one is how I want it to be: The second one is after resizing: My question: Can somebody tell me, how I keep the layout the way it is before resizing? I want to have the `JTextArea` go all the way down to the bottom of the window. And if neccessary, the scrollbar should appear. The only way, the status panel can go below the bottom of the window is, if the window is too small (because the configuration panel has a fixed height). I hope I made myself clear. If not, please ask. ;) EDIT: You can see the behaviour I want, if you remove the top JScrollPanel (the one that holds all the components). Just change ``` JScrollPane contentScrollPane = new JScrollPane(mainPanel); contentScrollPane.setBorder(BorderFactory.createEmptyBorder()); setContentPane(contentScrollPane); ``` to ``` setContentPane(mainPanel); ``` to see what I mean. Unfortunately, this way I loose the scrollbars if the window is very small.

Original source

Related problems