Samad Lotia
Samad Lotia

Reputation: 733

How to lay out components so that the middle component gets all the space, but the right-most component is aligned left?

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:

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);
            }
        });
    }
}

Upvotes: 4

Views: 326

Answers (2)

Samad Lotia
Samad Lotia

Reputation: 733

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);
            }
        });
    }
}

Upvotes: 0

MadProgrammer
MadProgrammer

Reputation: 347184

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();
    }

}

Upvotes: 4

Related Questions