4

tl;dr: I want to do what's in the second picture (ignore the red lines)

I understand how GroupLayout works, but I can't figure this out, or whether it's even possible. I initially had this code:

#Horizontal layout is a parallel group containing 3 sequences of components
layout.setHorizontalGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) #everything else to the right
                          .addGroup(layout.createSequentialGroup() #row 1
                                    .addComponent(startTimeLabel)
                                    .addComponent(self.lastUpdatedLabel))
                          .addGroup(layout.createSequentialGroup() #row 2
                                    .addComponent(self.progressBar)
                                    .addComponent(self.clearBtn))
                          .addGroup(layout.createSequentialGroup() #row 3
                                    .addComponent(self.fileProgLabel)
                                    .addComponent(self.sizeProgLabel)
                                    .addComponent(self.ETCLabel))))

which produced this: enter image description here

However, I want to align the 2 top labels at the start and end of the progress bar, and the 3 bottom labels at the start, middle, and end of the progress bar, like this (mspainted): enter image description here

My first approach at this was to try to split the components into the parallel groups I've made with the lines above. I put struts either side of the progress bar, aligning the 4 end labels to those, and aligned the center label to the progress bar itself:

#Horizontal layout is a sequence of 3 parallel groups of components, and an end component
layout.setHorizontalGroup(layout.createSequentialGroup()
                          .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) #starttime, strut, filesprog
                                    .addComponent(startTimeLabel)
                                    .addComponent(progLeft) #invisible, just for gluing things to
                                    .addComponent(self.fileProgLabel))
                          .addGroup(layout.createParallelGroup(GroupLayout.Alignment.CENTER) #progress bar, sizeprog
                                    .addComponent(self.progressBar)
                                    .addComponent(self.sizeProgLabel))
                          .addGroup(layout.createParallelGroup(GroupLayout.Alignment.TRAILING) #updatetime, strut, ETC
                                    .addComponent(self.lastUpdatedLabel)
                                    .addComponent(progRight) #invisible, just for gluing things to
                                    .addComponent(self.ETCLabel))
                          .addComponent(self.clearBtn))

However, as I expected, this forced the progress bar to squeeze into the horizontal space between the top 2 labels, like this: enter image description here

Finally, I thought of getting rid of the struts, and adding the progress bar to three separate parallel groups: aligned LEADING with the two left labels, CENTER with the middle label, and TRAILING with the right label:

#Horizontal layout is a sequence of 4 parallel groups of components
layout.setHorizontalGroup(layout.createSequentialGroup()
                          .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) #starttime, progleft, filesprog
                                    .addComponent(startTimeLabel)
                                    .addComponent(self.progressBar)
                                    .addComponent(self.fileProgLabel))
                          .addGroup(layout.createParallelGroup(GroupLayout.Alignment.CENTER) #progmid, sizeprog
                                    .addComponent(self.progressBar)
                                    .addComponent(self.sizeProgLabel))
                          .addGroup(layout.createParallelGroup(GroupLayout.Alignment.TRAILING) #updatetime, progright, ETC
                                    .addComponent(self.lastUpdatedLabel)
                                    .addComponent(self.progressBar)
                                    .addComponent(self.ETCLabel))
                          .addComponent(self.clearBtn))

However, Swing clearly ignores the second and third mentions of the progress bar, and I end up with this: enter image description here

I reckon I've had a pretty decent go at this, and I'm well and truly out of ideas. Is there any way to make a component span multiple parallel groups?

Cam Jackson
  • 11,860
  • 8
  • 45
  • 78
  • Why not a nested layout? See also this discussion of preferred size in [`ProgressTest`](http://stackoverflow.com/questions/7256775/how-to-overlay-resize-and-centre-a-component-on-a-jpanel/7262450#7262450). – trashgod Sep 02 '11 at 07:15
  • @CamJackson have you tried it with horizontal struts? – Neifen Sep 02 '11 at 07:55
  • @trashgod Hmmm, how would you nest it? Seems like that would get overly-complicated pretty quickly. – Cam Jackson Sep 03 '11 at 01:56
  • @Neifen That's what I have in the second code sample and third picture. I made the struts 0 or one pixels wide, but the parallel group's width sizes to match the upper labels, so the progress bar is squeezed inwards. – Cam Jackson Sep 03 '11 at 01:57

3 Answers3

3

It's obviously too late, but I found this question while I was looking for something similar, so someone else might find this answer useful.

This answer actually uses GroupLayout. The only thing in here which I consider a hack is setting a preferred size to the scrollbar (if you take it out, the labels will overlap.)

It's simpler than you might have suspected:

import javax.swing.DefaultBoundedRangeModel;
import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;

public class LayoutTest implements Runnable {
    private JLabel startTimeLabel = new JLabel("start time");
    private JLabel lastUpdatedLabel = new JLabel("last updated");
    private JLabel fileProgLabel = new JLabel("file progress");
    private JLabel sizeProgLabel = new JLabel("size progress");
    private JLabel etcLabel = new JLabel("etc");
    private JButton clearBtn = new JButton("Clear");
    private JProgressBar progressBar = new JProgressBar(new DefaultBoundedRangeModel(27, 0, 0, 100));

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new LayoutTest());
    }

    @Override
    public void run() {
        progressBar.setStringPainted(true);

        JPanel panel = new JPanel();
        GroupLayout layout = new GroupLayout(panel);
        layout.setAutoCreateContainerGaps(true);
        layout.setAutoCreateGaps(true);
        panel.setLayout(layout);

        layout.setHorizontalGroup(layout.createSequentialGroup()
                .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
                        .addComponent(progressBar, GroupLayout.DEFAULT_SIZE, 500, GroupLayout.DEFAULT_SIZE)
                        .addComponent(startTimeLabel, GroupLayout.Alignment.LEADING)
                        .addComponent(fileProgLabel, GroupLayout.Alignment.LEADING)
                        .addComponent(sizeProgLabel, GroupLayout.Alignment.CENTER)
                        .addComponent(lastUpdatedLabel, GroupLayout.Alignment.TRAILING)
                        .addComponent(etcLabel, GroupLayout.Alignment.TRAILING))
                .addComponent(clearBtn));

        layout.setVerticalGroup(layout.createSequentialGroup()
                .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
                        .addComponent(startTimeLabel)
                        .addComponent(lastUpdatedLabel))
                .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
                        .addComponent(progressBar)
                        .addComponent(clearBtn))
                .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
                        .addComponent(fileProgLabel)
                        .addComponent(sizeProgLabel)
                        .addComponent(etcLabel)));

        JFrame frame = new JFrame("Layout Test");
        frame.setContentPane(panel);
        frame.pack();
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }
}

There is probably a way to use a parallel group for the LEADING and TRAILING component pairs, but the layout completely rearranged when I tried to do that.

Sometimes the solution to GroupLayout problems is to ignore the vertical layout when doing the horizontal layout and ignore the horizontal layout when doing the vertical layout. Flatten the layout to a single dimension (rearranging similar rows and columns next to each other helps) and write the layout to match the 1D picture in your head.

I would particularly recommend against using GridBagLayout, boxes and the like, because getting the correct spacing between components in a cross-platform fashion is a nightmare.

Hakanai
  • 12,010
  • 10
  • 62
  • 132
3

Given your requirements, I would rather use a GridBagLayout. It's a bit more complex but I wrote a piece of code that does what you want.

class T extends JFrame {

public T() {
    setDefaultCloseOperation(EXIT_ON_CLOSE);

    String s1 = "Started at: 2011-09-12 15:33:38";
    String s2 = "Last updated: 2011-09-12 15:33:44";
    String s3 = "File copied:2/10";
    String s4 = "Bytes copied: 234/1000";
    String s5 = "ETC: 2011-09-02 15:34:02";
    JProgressBar progressBar = new JProgressBar();

    progressBar.setMinimum(100);
    progressBar.setStringPainted(true);
    progressBar.setString("23%");
    progressBar.setValue(23);

    setLayout(new GridBagLayout());

    GridBagConstraints c = new GridBagConstraints();

    c.fill = GridBagConstraints.HORIZONTAL;
    c.weightx = 1.0;
    c.insets = new Insets(5, 5, 5, 5);
    add(new JLabel(s1), c);

    c.gridx = 2;
    add(new JLabel(s2, JLabel.RIGHT), c);

    c.gridx = 0;
    c.gridy = 1;
    c.gridwidth = 3;
    add(progressBar, c);

    c.gridx = 3;
    add(new JButton("Clear"), c);

    c.gridx = 0;
    c.gridy = 2;
    add(new JLabel(s3), c);

    c.gridx = 0;
    add(new JLabel(s4, JLabel.CENTER), c);

    c.gridx = 2;
    add(new JLabel(s5, JLabel.RIGHT), c);

    setSize(600, 300);
    setVisible(true);

}

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

Here is the result: screenshot

aymeric
  • 3,877
  • 2
  • 28
  • 42
  • I've used GBL for some other parts of my GUI, but I wasn't sure how well it would work here because the width of some of the labels is not particularly well defined. So I didn't think the cells etc would scale well when the window is resized. I'll give this a go on Monday though, thanks. – Cam Jackson Sep 03 '11 at 02:00
  • This worked quite well. Main difference was that I set `weightx = 0.0` on all components except for the middle bottom "Bytes copied: x/x" label, which has `weightx = 1.0`. This way, when the window is resized horizontally, the progress bar stretches, and all other components follow appropriately. – Cam Jackson Sep 05 '11 at 03:32
3

How would you nest it?

As shown below.

Seems like that would get overly-complicated pretty quickly.

It depends on the requirements.

enter image description here

import java.awt.EventQueue;
import java.awt.GridLayout;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;

/** @see http://stackoverflow.com/questions/7279799 */
public class NestedLayout extends Box {

    private String s1 = "Started at: 2011-09-12 15:33:38";
    private String s2 = "Last updated: 2011-09-12 15:33:44";
    private String s3 = "File copied:2/10";
    private String s4 = "Bytes copied: 234/1000";
    private String s5 = "ETC: 2011-09-02 15:34:02";
    private JProgressBar pb = new JProgressBar();

    public NestedLayout(int axis) {
        super(axis);
        JPanel top = new JPanel(new GridLayout());
        top.add(new JLabel(s1, JLabel.LEFT));
        top.add(new JLabel(s2, JLabel.RIGHT));

        JPanel mid = new JPanel(new GridLayout());
        pb.setMaximum(100);
        pb.setStringPainted(true);
        pb.setString("23%");
        pb.setValue(23);
        mid.add(pb);

        JPanel bot = new JPanel(new GridLayout());
        bot.add(new JLabel(s3, JLabel.LEFT));
        bot.add(new JLabel(s4, JLabel.CENTER));
        bot.add(new JLabel(s5, JLabel.RIGHT));

        Box east = new Box(BoxLayout.Y_AXIS);
        east.add(Box.createVerticalGlue());
        east.add(new JButton("Clear"));
        east.add(Box.createVerticalGlue());

        JPanel center = new JPanel(new GridLayout(0, 1));
        center.add(top);
        center.add(mid);
        center.add(bot);

        this.add(center);
        this.add(east);
    }

    private void display() {
        JFrame f = new JFrame("NestedLayout");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.add(this);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

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

            @Override
            public void run() {
                new NestedLayout(BoxLayout.X_AXIS).display();
            }
        });
    }
}
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • Oh ok, that's surprisingly elegant, thanks. I think I'll give GBL a go first, but I'll keep this kind of approach in mind for the future. – Cam Jackson Sep 04 '11 at 23:34
  • Good plan. As layouts can be nested, I often solve problem areas first, occasionally using an IDE for the odd `GroupLayout`; related example [here](http://stackoverflow.com/questions/6769722). – trashgod Sep 05 '11 at 10:32
  • 1
    I'm actually using Jython, so no GUI builder for me! It's probably good to actually learn how it all works in code anyway though :) – Cam Jackson Sep 05 '11 at 23:26