4

I have a panel that consists of a button (X), a label (Y), and a progress bar (Z). Ideally I would like to lay them out like so:

| X-X Y---------------Y Z---Z ============= |  <-- expanded-size panel
                              ^ extra space

| X-X Y------Y Z---Z |                         <-- reduced-size panel

The diagram above shows:

  • When the panel expands, extra space should go to the label (Y) so that the label can completely show its text.
  • The progress bar (Z) should always remain next to the label (Y).
  • When the panel is reduced, the label (Y)'s size should be reduced.
  • The button (X) and the progress bar (Z)'s sizes should be constant, not the label (Y)'s size.

However, when I try using GroupLayout, this is what happens when the panel is expanded:

| X-X Y---------------Y ============= Z---Z |  <-- expanded-size panel (bad)

The problem is that when the panel has extra space, the label (Y) gets expanded beyond what it needs, which pushes the progress bar (Z) to the right. I would prefer that the progress bar's (Z) position is next to the label (Y).

How can I accomplish this layout?

Example code ("Blah.java"):

import java.awt.*;
import javax.swing.*;

public class Blah extends JPanel {
    public Blah() {
        final JButton X = new JButton("X");
        final JLabel Y = new JLabel("yyyyyyyyyyy");
        Y.setOpaque(true);
        Y.setBackground(Color.YELLOW);
        final JProgressBar Z = new JProgressBar();
        Z.setIndeterminate(true);

        final GroupLayout l = new GroupLayout(this);
        super.setLayout(l);

        l.setHorizontalGroup(
                l.createSequentialGroup()
                .addComponent(X, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
                .addComponent(Y, 0, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                .addComponent(Z, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE));

        l.setVerticalGroup(
                l.createParallelGroup()
                .addComponent(X)
                .addComponent(Y)
                .addComponent(Z));
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                final JFrame frame = new JFrame("Blah");
                frame.add(new Blah());
                frame.pack();
                frame.setVisible(true);
            }
        });
    }
}
Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
Samad Lotia
  • 723
  • 1
  • 7
  • 14

2 Answers2

4

It is very rare that one individual layout manager will meet all your needs. Instead you could use a compound layout approach.

That is, separate each individual layout requirement into a separate component, with it's own layout manager. Then added all these to your master component, managing the overall requirements.

As an example

enter image description hereenter image description here

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.LineBorder;

public class SimpleGridBagLayout {

    public static void main(String[] args) {
        new SimpleGridBagLayout();
    }

    public SimpleGridBagLayout() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        public TestPane() {

            setLayout(new GridBagLayout());

            GridBagConstraints gbc = new GridBagConstraints();
            gbc.gridx = 0;
            add(createLabel("XXX-XXX", Color.BLUE), gbc);

            JPanel panel = new JPanel();
            panel.add(createLabel("Y-Y", Color.RED));
            panel.add(createLabel("ZZZZZ---ZZZZZ", Color.GREEN), gbc);

            gbc.gridx++;
            gbc.weightx = 1;
            add(panel, gbc);

        }

        protected JLabel createLabel(String text, Color border) {

            JLabel label = new JLabel(text);
            label.setBorder(new LineBorder(border));
            return label;

        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 100);
        }
    }

}

There are a number of factors at play here. The preferred and minimum sizes of the child components will make a significant difference to how some layout managers layout there components when the available size is to small.

Updated

public TestPane() {

    setLayout(new GridBagLayout());

    GridBagConstraints gbc = new GridBagConstraints();
    gbc.gridx = 0;
    add(createLabel("XXX-XXX", Color.BLUE), gbc);

    JPanel panel = new JPanel(new GridBagLayout());
    gbc = new GridBagConstraints();
    gbc.gridx = 0;
    panel.add(createLabel("Y-Y", Color.RED), gbc);
    gbc.gridx = 1;
    panel.add(createLabel("ZZZZZ---ZZZZZ", Color.GREEN), gbc);

    gbc = new GridBagConstraints();
    gbc.gridx = 1;
    gbc.weightx = 1;
    add(panel, gbc);

}

Updated

Now with "non-shrinking" progress bar

public class TestPane extends JPanel {

    public TestPane() {

        setLayout(new GridBagLayout());

        GridBagConstraints gbc = new GridBagConstraints();
        gbc.gridx = 0;
        add(createLabel("XXX-XXX", Color.BLUE), gbc);

        JPanel panel = new JPanel(new GridBagLayout());
        gbc = new GridBagConstraints();
        gbc.gridx = 0;
        panel.add(createLabel("Y-Y", Color.RED), gbc);
        gbc.gridx = 1;
        MyProgressBar pb = new MyProgressBar();
        panel.add(pb, gbc);

        gbc = new GridBagConstraints();
        gbc.gridx = 1;
        gbc.weightx = 1;
        add(panel, gbc);

    }

    protected JLabel createLabel(String text, Color border) {

        JLabel label = new JLabel(text);
        label.setBorder(new LineBorder(border));
        return label;

    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(200, 100);
    }
}

public class MyProgressBar extends JProgressBar {

    @Override
    public Dimension getPreferredSize() {
        Dimension ps = super.getPreferredSize();
        ps.width = 75;
        return ps;
    }

    @Override
    public Dimension getMinimumSize() {
        return getPreferredSize();
    }

}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • I ran your code (on Java 6). The problem is that when you shrink the window, `Z` disappears. I'd prefer that `Z` stays the same size and `Y` shrinks. – Samad Lotia Jul 09 '13 at 01:11
  • Yep, that's cause the `JPanel` is using a `FlowLayout`, changing to use something like `GridBagLayout` should stop that, but this was an example ;) – MadProgrammer Jul 09 '13 at 01:12
  • `GridBagLayout` won't solve the issue. The fundamental problem is that when `Y` has extra space, it shouldn't expand anymore. Setting `Y`'s `weightx` to `1.0` causes it to expand out when the panel has extra space (when there's more than enough space for `Y` to show its entire text), pushing `Z` to the right. When the panel is shrunk (not enough space for `Y` to show its entire text), `weightx = 1.0` works just fine. – Samad Lotia Jul 09 '13 at 01:16
  • I didn't set `Y`'s `weightx`, I set the panel that `Y` is on. `Y` won't expand, in fact, I'm pretty sure that the child component it is on won't expand beyond the sum of the preferred size of it's children... – MadProgrammer Jul 09 '13 at 01:19
  • @MadProgrammer: I didn't try your code, but I noticed that you're using JLabels for all 3 components. You may want to rethink that. I'm not sure why, but in the code I'm trying (unsuccessfully), the JProgressBar wants to shrink first, then the JButton, and lastly the JLabel. If I put the JProgressBar in the middle and the JLabel on the right, this same order still holds true. So, I'm led to believe that the differences in the painting for each different type of JComponent is another part of the problem. JLabels seem to be the most stubborn in giving up real estate. Just my 2c. – splungebob Jul 09 '13 at 02:22
  • `GridBagLayout` will "squash" components to honor there minimum size if there isn't enough room to honor their preferred size. The basic solution would be to implement a `JProgress` bar which provides implementations of minimum and preferred sizes to meet your needs – MadProgrammer Jul 09 '13 at 02:45
  • _very rare that one individual layout manager will meet all your needs_ strongly disagree: MigLayout meets all my needs, layout-wise :-) Compound layouts are possible but notoriously tricky when it comes to cross-container alignment. Nothing new, just repeating the mantra ... – kleopatra Jul 09 '13 at 06:09
0

After banging my head against the wall for far too long, I discovered this little gem of documentation in SpringLayout's tutorial (http://docs.oracle.com/javase/tutorial/uiswing/layout/spring.html):

When a component's getMaximumSize and getPreferredSize methods return the same value, SpringLayout interprets this as meaning that the component should not be stretched.

Thus with SpringLayout and manually setting Y's and Z's maximum size, the ideal layout outlined above can be achieved. Here's the code:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class Blah extends JPanel {
    public Blah() {
        final JButton X = new JButton("X");
        final JLabel Y = new JLabel("yyyyyyyyyyy");
        Y.setOpaque(true);
        Y.setBackground(Color.YELLOW);
        final JProgressBar Z = new JProgressBar();
        Z.setIndeterminate(true);

        final SpringLayout l = new SpringLayout();
        super.setLayout(l);

        super.add(X);
        super.add(Y);
        super.add(Z);

        Z.setMaximumSize(Z.getPreferredSize());

        l.putConstraint(SpringLayout.WEST, X, 10, SpringLayout.WEST, this);
        l.putConstraint(SpringLayout.WEST, Y, 10, SpringLayout.EAST, X);
        l.putConstraint(SpringLayout.WEST, Z, 10, SpringLayout.EAST, Y);
        l.putConstraint(SpringLayout.EAST, this, 10, SpringLayout.EAST, Z);

        l.putConstraint(SpringLayout.NORTH, X, 10, SpringLayout.NORTH, this);
        l.putConstraint(SpringLayout.NORTH, Y, 10, SpringLayout.NORTH, this);
        l.putConstraint(SpringLayout.NORTH, Z, 10, SpringLayout.NORTH, this);

    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                final JFrame frame = new JFrame("Blah");
                frame.add(new Blah());
                frame.pack();
                frame.setVisible(true);
            }
        });
    }
}
Samad Lotia
  • 723
  • 1
  • 7
  • 14
  • glad you found a seemingly working solution :-) Just beware: a) SpringLayout is meant for usage in builders, so could get a bit hard to hand-code b) don't break a component's internal calculation of its sizing hints, that is [don't use setXXSize, ever](http://stackoverflow.com/a/7229519/203657) Unrelated: please learn java naming conventions and stick to them – kleopatra Jul 11 '13 at 08:54
  • @kleopatra: a) hard-coding `SpringLayout` has been much easier than `GridBagLayout` or even `SpringLayout`. b) I had to manually set the maximum size, because it is recommended by the documentation. c) I'm guessing you're referring to naming the components X, Y, and Z. I did that deliberately because that's what I wrote in the diagram above. – Samad Lotia Jul 11 '13 at 17:21
  • Erm, I mean: a) hard-coding `SpringLayout` has been much easier than `GridBagLayout` or even `GroupLayout`. – Samad Lotia Jul 11 '13 at 21:21