简体   繁体   中英

FlowLayout without linebreaks and/or in vertical direction

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.

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM