Search code examples
javaswinglayout-managerboxlayout

How to use VerticalGlue in box layout


I'm trying to build a simple panel which I will throw some fields onto and capture some user data. I typically use a combination of GridBagLayouts (thanks to trashgod) and BoxLayouts to achieve the layout I want. I normally don't have any issues with using them and they just do what makes intuitive sense 99% of the time, but I can't seem to make this rather simple panel function properly. Can anyone tell me why?

The panel class:

import java.awt.Color;
import java.awt.Dimension;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;

public class EmailPanel extends JPanel {
    private JButton m_OkButton;
    private JPanel m_MainPanel;

    private JTextField m_ServerIPTF;
    private JTextField m_ServerPortTF;
    private JTextField m_DomainNameTF;
    private JTextField m_UnitNameTF;

    private JTextField m_Recipient1TF;
    private JTextField m_Recipient2TF;

    private final Dimension LARGE_TEXTFIELD_SIZE = new Dimension(125, 25);
    public EmailPanel() {
        init();
    }

    private void init() {
        this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
        JPanel tPanel;
        JLabel tLabel;

        Header: {
            tPanel = new JPanel();
            tPanel.setLayout(new BoxLayout(tPanel, BoxLayout.X_AXIS));
            tPanel.add(Box.createHorizontalGlue());
            tLabel = new JLabel("Email Settings");
            tPanel.add(tLabel);
            tPanel.add(Box.createHorizontalGlue());
            tPanel.setBorder(BorderFactory.createMatteBorder(0, 0, 3, 0, Color.red));
            this.add(tPanel);
        }

        MainPanel: {
            m_MainPanel = new JPanel();
            m_MainPanel.setLayout(new BoxLayout(m_MainPanel, BoxLayout.Y_AXIS));
            m_MainPanel.add(Box.createVerticalStrut(5));
            tPanel = new JPanel();
            tPanel.setLayout(new BoxLayout(tPanel, BoxLayout.X_AXIS));
            tPanel.add(Box.createHorizontalStrut(3));
            tLabel = new JLabel("Server IP Address:");
            tPanel.add(tLabel);
            tPanel.add(Box.createHorizontalStrut(3));
            m_ServerIPTF = new JTextField();
            m_ServerIPTF.setMinimumSize(LARGE_TEXTFIELD_SIZE);
            m_ServerIPTF.setMaximumSize(LARGE_TEXTFIELD_SIZE);
            m_ServerIPTF.setPreferredSize(LARGE_TEXTFIELD_SIZE);
            tPanel.add(m_ServerIPTF);
            tPanel.add(Box.createHorizontalStrut(25));
            tLabel = new JLabel("Server Port");
            tPanel.add(tLabel);
            tPanel.add(Box.createHorizontalStrut(3));
            m_ServerPortTF = new JTextField();
            m_ServerPortTF.setMinimumSize(LARGE_TEXTFIELD_SIZE);
            m_ServerPortTF.setMaximumSize(LARGE_TEXTFIELD_SIZE);
            m_ServerPortTF.setPreferredSize(LARGE_TEXTFIELD_SIZE);
            tPanel.add(m_ServerPortTF);
            tPanel.add(Box.createHorizontalGlue());
            m_MainPanel.add(tPanel);
            m_MainPanel.add(Box.createVerticalStrut(5));

            tPanel = new JPanel();
            tPanel.setLayout(new BoxLayout(tPanel, BoxLayout.X_AXIS));
            tPanel.add(Box.createHorizontalStrut(6));
            tLabel = new JLabel("Domain Name:");
            tPanel.add(tLabel);
            tPanel.add(Box.createHorizontalStrut(3));
            m_DomainNameTF = new JTextField();
            m_DomainNameTF.setMinimumSize(LARGE_TEXTFIELD_SIZE);
            m_DomainNameTF.setMaximumSize(LARGE_TEXTFIELD_SIZE);
            m_DomainNameTF.setPreferredSize(LARGE_TEXTFIELD_SIZE);
            tPanel.add(m_DomainNameTF);
            tPanel.add(Box.createHorizontalGlue());
            m_MainPanel.add(tPanel);

            this.add(m_MainPanel);
        }

        OKButton: {
            m_OkButton = new JButton("Ok");
            tPanel = new JPanel();
            tPanel.setLayout(new BoxLayout(tPanel, BoxLayout.X_AXIS));
            tPanel.add(Box.createHorizontalGlue());
            tPanel.add(m_OkButton);
            tPanel.add(Box.createHorizontalGlue());
            this.add(tPanel);
        }

        this.add(Box.createVerticalGlue());
    }
}

If you add this to / use this as a content pane, you will see that there are large gaps on the Y axis between the various controls. I'm under the impression that the vertical glue that I add at the end of the init method should grow to consume all the space below the OK button, and the controls would be pushed together as a consequence. What I'm seeing is that it seems to be splitting up the space evenly between the various instances of my temporary JPanel object tPanel and the vertical glue at the bottom. How do I make it stop doing that?

Edit: It seems that the behavior is the same both with and without the somewhat superfluous m_MainPanel object.

This is what I see when it renders and the form is made larger than needed for the controls. I would expect the vertical glue to fill the space below the OK button to keep the controls on the top of the form.

enter image description here


Solution

  • I copy-pasted your code and added the main method:

    public static void main(String[] args) {
    
        JFrame frame = new JFrame();
        frame.getContentPane().add(new EmailPanel());
        frame.pack();
        frame.setVisible(true);
    }
    

    This is the result:

    enter image description here

    with or without the line this.add(Box.createVerticalGlue());

    Is this what you wanted or not?


    Edit: Solution

    I edited your code to achieve the desired result:

    public class EmailPanel extends JPanel {
    
        private JButton okButton;
        private JTextField serverIPTF;
        private JTextField serverPortTF;
        private JTextField domainNameTF;
    
        public static void main(String[] args) {
    
            JFrame frame = new JFrame();
            frame.getContentPane().add(new EmailPanel());
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.pack();
            frame.setMinimumSize(frame.getPreferredSize());
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        }
    
        public EmailPanel() {
    
            init();
        }
    
        private void init() {
    
            setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
            JPanel tPanel;
            JLabel tLabel;
    
    // Header
            tLabel = new JLabel("Email Settings", JLabel.CENTER);
            tLabel.setAlignmentX(CENTER_ALIGNMENT);
            tLabel.setMaximumSize(new Dimension(Integer.MAX_VALUE, tLabel.getPreferredSize().height));
            tLabel.setBorder(BorderFactory.createMatteBorder(0, 0, 3, 0, Color.red));
            add(tLabel);
    
    // Fields
            JPanel fieldsPanel = new JPanel();
            fieldsPanel.setLayout(new BoxLayout(fieldsPanel, BoxLayout.Y_AXIS));
            fieldsPanel.setBorder(BorderFactory.createMatteBorder(5, 3, 5, 3, new Color(0, 0, 255, 255)));
    
    // Top fields
            serverIPTF = new JTextField(10);
            serverIPTF.setMaximumSize(serverIPTF.getPreferredSize());
            serverPortTF = new JTextField(10);
            serverPortTF.setMaximumSize(serverPortTF.getPreferredSize());
    
            tPanel = new JPanel();
            tPanel.setLayout(new BoxLayout(tPanel, BoxLayout.X_AXIS));
    
            tPanel.add(new JLabel("Server IP Address:"));
            tPanel.add(Box.createRigidArea(new Dimension(3, 0)));
            tPanel.add(serverIPTF);
            tPanel.add(Box.createRigidArea(new Dimension(25, 0)));
            tPanel.add(new JLabel("Server Port"));
            tPanel.add(Box.createRigidArea(new Dimension(3, 0)));
            tPanel.add(serverPortTF);
            tPanel.add(Box.createHorizontalGlue());
            fieldsPanel.add(tPanel);
    
            fieldsPanel.add(Box.createRigidArea(new Dimension(0, 5)));
    
    // Lower field
            domainNameTF = new JTextField(10);
            domainNameTF.setMaximumSize(domainNameTF.getPreferredSize());
    
            tPanel = new JPanel();
            tPanel.setLayout(new BoxLayout(tPanel, BoxLayout.X_AXIS));
    
            tPanel.add(new JLabel("Domain Name:"));
            tPanel.add(Box.createRigidArea(new Dimension(3, 0)));
            tPanel.add(domainNameTF);
            tPanel.add(Box.createHorizontalGlue());
            fieldsPanel.add(tPanel);
    
            add(fieldsPanel);
    
    // OK Button
            okButton = new JButton("OK");
            okButton.setAlignmentX(CENTER_ALIGNMENT);
            add(okButton);
        }
    }
    

    Explanation:

    BoxLayout says:

    When a BoxLayout lays out components from top to bottom, it tries to size each component at the component's preferred height. If the vertical space of the layout does not match the sum of the preferred heights, then BoxLayout tries to resize the components to fill the space. The components either grow or shrink to fill the space, with BoxLayout honoring the minimum and maximum sizes of each of the components. Any extra space appears at the bottom of the container.

    (emphasis mine)

    Which tells us that if we restrict the components' maximum height to their preferred height, all the extra vertical space will go to the bottom, just as you want. Hence, we added for all the text fields (the labels do not grow vertically) the line:

    nameTF.setMaximumSize(nameTF.getPreferredSize());
    

    and we don't need any vertical glue.

    Notes:

    • I created the text fields with 10 columns, you can change this value.
    • The top label does not need horizontal glue to stretch it, just relax the maximum width constraint and set the alignment (similarly to the bottom button).
    • Instead of creating a lot of rigid areas (you used struts), I created a border with the appropriate widths. It is blue for visual purposes, but you should set its alpha to 0 to make is transparent.
    • Use createRigidArea instead of createXXXStrut (see the note in the above link).
    • I used frame.setMinimumSize(frame.getPreferredSize()) to not let the window resize to a smaller size than its contents. This is optional.
    • Non-final fields and variables should not use underscore (_) in the name according to Java naming conventions.
    • You did not specify horizontal stretching behavior, so it does whatever it does.
    • I still think that box layout is not the best approach here, or at least do not allow resizing of the window at all (so to not deal with extra space).