1

This is a follow-up to this question. Any viable answer will also answer that one.

What layout may be used with as little modification as possible to replicate the aligning nature of a FlowLayout, but never linebreak and also be available in a from-top-to-bottom flavour?

The obvious candidate, BoxLayout, does not work nicely with JPanels. Consider the following two examples:

import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

class App
{
  public static void main(String[] args)
  {
    JFrame window = new JFrame();
    Box box = new Box(BoxLayout.Y_AXIS);
    for(int i = 0; i < 5; ++i)
    {
      JLabel label = new JLabel("XX");
      box.add(label);
    }
    box.add(Box.createVerticalGlue());
    window.add(box);
    window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    window.setVisible(true);
  }
}

This will properly display a vertical line of labels, beginning at the top and stretching as far towards the bottom as the labels take space. Good.

Modifying this, however, just a tiny bit:

  public static void main(String[] args)
  {
    JFrame window = new JFrame();
    Box box = new Box(BoxLayout.Y_AXIS);
    for(int i = 0; i < 5; ++i)
    {
      JLabel label = new JLabel("XX");
      JPanel panel = new JPanel();
      panel.add(label);
      box.add(panel);
    }
    box.add(Box.createVerticalGlue());
    window.add(box);
    window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    window.setVisible(true);
  }

This will stretch all components of the Box to the same height, placing the labels far away from each other. Bad.

Overriding the JPanel's getPreferredSize and getMaximumSize methods (with getMinimumSize) has no effect and would be a bad way to fix it, because it relied on the components rather than the container and its layout.


Addendum:
Here is an already pretty successful attempt using GroupLayout. Unfortunately it did not seem to occur to the designer that among DEFAULT_SIZE and PREFERRED_SIZE a choice MINIMUM_SIZE would have been a good idea.

Furthermore if it is possible to invert the sequence of GroupLayout.SequentialGroup, the API is no help to figure out how. I for one certainly have no clue how to even extend that class.

import java.awt.Component;
import java.awt.ComponentOrientation;
import java.awt.Container;
import javax.swing.GroupLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

class LineLayout extends GroupLayout
{
  public LineLayout(Container owner, int axis)
  {
    super(owner);
    this.direction = axis;
    this.direction |= owner.getComponentOrientation() != ComponentOrientation.LEFT_TO_RIGHT
                    ? LineLayout.RIGHT_TO_LEFT : LineLayout.LEFT_TO_RIGHT;
    this.setupGroups();
  }
  public LineLayout(Container owner, int axis, int orientation)
  {
    super(owner);
    this.direction = axis;
    this.direction |= orientation;
    this.setupGroups();
  }

  @Override // to replicate FlowLayout functionality : this method is called from owner.add
  public void addLayoutComponent(Component component, Object constraints)
  {
    if(constraints == null)
    {
      // REALLY surprised that this works, considering that overriding the JPanel's
      // getMaximumSize method with getPreferredSize had no effect
      this.horizontal.addComponent(component, GroupLayout.DEFAULT_SIZE,
                                              GroupLayout.DEFAULT_SIZE,
                                              GroupLayout.PREFERRED_SIZE);
      this.vertical.addComponent  (component, GroupLayout.DEFAULT_SIZE,
                                              GroupLayout.DEFAULT_SIZE,
                                              GroupLayout.PREFERRED_SIZE);
    }
    // TODO: else
  }

  protected void setupGroups()
  {
    super.setAutoCreateGaps(false); // does nothing
    if((this.direction & LineLayout.AXIS) == LineLayout.Y_AXIS)
    {
      this.horizontal = super.createParallelGroup();
      this.vertical   = (this.direction & LineLayout.ORIENTATION) == LineLayout.RIGHT_TO_LEFT
                      ? this.createSequentialInvertedGroup() : super.createSequentialGroup();
    }
    else
    {
      this.horizontal = (this.direction & LineLayout.ORIENTATION) == LineLayout.RIGHT_TO_LEFT
                      ? this.createSequentialInvertedGroup() : super.createSequentialGroup();
      this.vertical   = super.createParallelGroup();
    }
    super.setHorizontalGroup(this.horizontal);
    super.setVerticalGroup  (this.vertical);
  }

  // How!?
  // protected LineLayout.SequentialInvertedGroup createSequentialInvertedGroup() { return new LineLayout.SequentialInvertedGroup(); }
  protected GroupLayout.SequentialGroup createSequentialInvertedGroup() { return super.createSequentialGroup(); } // placeholder

  protected int direction;
  protected GroupLayout.Group horizontal;
  protected GroupLayout.Group vertical;

  // not sure how reliable the constant field values of BoxLayout are, whether it's smart to assume them unchanging over the ages
  public static final int AXIS = 0b1;
  public static final int X_AXIS = 0b0; // = BoxLayout.X_AXIS;
  public static final int Y_AXIS = 0b1; // = BoxLayout.Y_AXIS;
  public static final int ORIENTATION = 0b10;
  public static final int LEFT_TO_RIGHT = 0b00; // also top to bottom
  public static final int RIGHT_TO_LEFT = 0b10; // also bottom to top

  // No idea how; only has "add" methods; cannot actually do anything with the added components!?
  //protected static class SequentialInvertedGroup extends GroupLayout.SequentialGroup
  //{}
}

class Applikation
{
  public static void main(String[] args)
  {
    JFrame window = new JFrame();
    JPanel box = new JPanel();
    box.setLayout(new LineLayout(box, LineLayout.Y_AXIS));
    for(int i = 0; i < 5; ++i)
    {
      JLabel label = new JLabel("XX");
      JPanel panel = new JPanel();
      panel.add(label);
      box.add(panel);
    }
    window.add(box);
    window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    window.setVisible(true);
  }
}

If you try this out, you will note that there are still notable border spaces between the "XX" labels, taking up about 2/3 of an extra label per gap. While already much better than in the BoxLayout example, I do not think there is a good way to improve this spacing further.

Community
  • 1
  • 1
Zsar
  • 443
  • 3
  • 14
  • `Overriding the JPanel's getPreferredSize and getMaximumSize methods` - don't override the getPreferredSize() method. Just override the getMaximumSize() method to return the preferred size. – camickr Jan 20 '16 at 17:46
  • @camickr : Tried that. No visible effect. Would have been odd too, considering that overriding it with `getMinimumSize` had already been ignored. – Zsar Jan 21 '16 at 11:16
  • Added another example using GroupLayout. Much better results than BoxLayout. – Zsar Jan 21 '16 at 11:30
  • `Tried that. No visible effect.` - your code is wrong. Works fine for me with a simple change to your posted code. Much simpler solution than trying to use a GroupLayout. You override the maximum size of the child panels (since it is the child panels that you want to prevent from growing), not the panel using the BoxLayout. – camickr Jan 21 '16 at 15:32
  • @camickr : Oops, sorry, you are correct. I accidentally overrode the method of label instead of panel. It works as you suggest. However, this relies on extending the prospective components, which is very unwieldy in a number of cases (e.g. if they are supposed to be received from elsewhere, if their type is not known beforehand). Unless one could reflect in such a way as to override the method within the call to `add` for an arbitrary child of Component, I do not think this solution is viable. – Zsar Jan 22 '16 at 14:09

2 Answers2

0
private static int MAX_HEIGHT = 40;
private static final Dimension DIMENSION = new Dimension(Integer.MAX_VALUE, MAX_HEIGHT); 
public static void main(String[] args)
{
JFrame window = new JFrame();
Box box = new Box(BoxLayout.Y_AXIS){
    private static final long serialVersionUID = 1L;

    @Override
    public Component add(Component comp) {
        comp.setMaximumSize(DIMENSION);
        return super.add(comp);
    }
};
for(int i = 0; i < 5; ++i)
{
  JLabel label = new JLabel("XX");
  JPanel panel = new JPanel();
  panel.add(label);
  box.add(panel);
}
window.add(box);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.pack();
window.setVisible(true);
}
sami zahwan
  • 147
  • 4
  • 1
    Don't set a maximum size manually. Override the getMaximumSize() to return the preferred size. – camickr Jan 20 '16 at 17:48
  • What if an added component requires a higher height than MAX_HEIGHT? What if not all added component's size requirements are known beforehand? @camickr : What if the components to be added are not accessible from this part of the code (that's what a public add is meant for, after all)? – Zsar Jan 21 '16 at 09:42
  • @Zsar, that is why the getMaximumSize() method simply invokes the getPreferredSize() method. So as you dynamically add components both the preferred/maximum sizes are updated to be in sync. You should be overriding the maximum size of the child panels, not the panel with the box layout. – camickr Jan 21 '16 at 15:22
  • What max height? I never suggested you use a max height because as you state you don't know what the max height should be, that is why you want the dynamic calculation. – camickr Jan 21 '16 at 15:28
  • @camickr : The answerer has a `MAX_HEIGHT` in their code, which I was referring to. Only after your name was the comment meant to address yours. – Zsar Jan 22 '16 at 14:01
0

You are using a Box for adding your components into. And the Documentation says:

a Box can use only a BoxLayout.

Now lets look into the Documentation for BoxLayout. It says:

BoxLayout pays attention to a component's requested minimum, preferred, and maximum sizes.

Now we have found the reason for the different outputs of your two examples. In your first example you are adding JLabels directly to your Box. Since they have a default maximumSize depending on their content they are not scaled by the Box.
In your second example you are adding JPanels to the Box that have your JLabels in it. A JPanel does not have a default maximumSize and so it is scaled by the Box.

So if you want to get the same output with JPanels as without you need your JPanels to have a maximumSize depending on their content means the JLabels.

So you could set a maximumSize manually. Something like that:

panel.setMaximumSize(new Dimension(100,20));

Or you use a different LayoutManager with your JPanels. One that calculates its size depending on its components. One that pays attention to a component's requested minimum, preferred, and maximum sizes.
Does this sound familiar to you? Right its from the Documentation of BoxLayout. So try to use a BoxLayout on your JPanels and you will get exactly the same result as your first example.

panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
ArcticLord
  • 3,999
  • 3
  • 27
  • 47
  • 1
    Don't set a maximum size manually. Override the getMaximumSize() to return the preferred size. – camickr Jan 20 '16 at 17:47
  • Considering that the maximum size is a function of the various kinds of sizes of components added to that panel, I do not see this working out - it only pushes the problem down by one container node in the component tree, does not at all solve anything. I am all for using a different layout. As a matter of fact, what layout(s) are sensible to be used literally *is part of the question*. – Zsar Jan 21 '16 at 09:37