2

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

Mark W
  • 2,791
  • 1
  • 21
  • 44
  • You're looking for resizing behavior more like [this](http://stackoverflow.com/a/8504753/230513)? – trashgod Oct 28 '14 at 01:15
  • Your code does not seem to reproduce the problem (see my answer). Please recheck it. – user1803551 Oct 28 '14 at 09:55
  • I dont think so trashgod, but I did read the bit about setXXXXXSize approach. Im going to try to set a maximum size on the temporary panel objects I use. Kinda surprised I didnt try that the first time around :/ – Mark W Oct 28 '14 at 13:34
  • @trashgod Using the maximum size property at Integer.MAX, 30 had the desired result. Still wonder why the vertical glue shares the available height on the form with the JPanels, when a HorizontalGlue doesnt share the width with say, labels or text fields. – Mark W Oct 28 '14 at 13:37

1 Answers1

3

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).
user1803551
  • 12,965
  • 5
  • 47
  • 74
  • yeah, but the problem is that when you change the size of the form, the vertical glue doesnt keep the controls justified to the top. Space between the domain name and server ip, domain name and the ok button, and the ok button and the bottom of the form grows, instead of just the space below the Ok button. – Mark W Oct 28 '14 at 13:25
  • @MarkW First of all, you can make the frame not-resizeable. Secondly, `BoxLayout` doesn't seem like the best option here. Are you sure you want to keep this approach? – user1803551 Oct 28 '14 at 17:58
  • I dont see an issue using box layouts. I've used the same approach on numerous occasions using a combination of box layouts with X and Y axis setups to build forms which have a layout of this nature. Im sure im going to keep it this way, because it works well. I just dont remember having to set the min/max sizes on my components when I use glue objects, but im pretty sure at this point I've always had to. Also making the frame not resizable isnt an option. Users have varying resolutions on their displays, and to not support resize is just lazy. – Mark W Oct 28 '14 at 19:21
  • @MarkW I don't see what resolution has to do with resizing of the window. The window will have its preferred size without extra space regardless of a resolution. I suggest you try this on your computer. – user1803551 Oct 28 '14 at 20:08
  • I appreciate your effort, but setting the maximum size on my temporary panels already solved the problem I was having. See my comment in response to trashgod in the OP – Mark W Oct 28 '14 at 20:26
  • @MarkW I saw, but an answer is not just 1 line (trashgod of course knows this). I wanted to add the relevant explanation and a coherent way to solve this (did you actually understand why trashgod's solution works? Did you understand why mine works?), along with the extra information which can be used outside the scope of this question. – user1803551 Oct 28 '14 at 20:51
  • Yes I understand why setting the maximum height works. Again, I simply dont remember having to mess with that in the past, and expected the components to use their preferred size and the glue to consume all of the space. I was in error. – Mark W Oct 28 '14 at 20:57