Search code examples
javaswinglayout-managergridbaglayoutboxlayout

Vertical stacked layout in Java Swing?


I want to arrange some labels in a scrollPane so that they are stacked one under the other, and each one of them has to be just tall enough (not more!) to fit the text it contains, and as wide as the respective container.
The labels' text is formatted with html to achieve the word wrap effect.

Here's the situation: schema

If I resize horizontally the frame, the total height of the viewport should increase or decrease, as the text has respectively less or more horizontal space.
I found myself in difficulty here because, as I know, layout Managers tend to fit the components in the available space, not the opposite.
I used the gridBagLayout to maintain a width ratio between the image and the descriptions on the right.
It seems to me a problem like the one that wrap layout solved, but from a vertical perspective, which is not contemplated by base flow layout,
or else a boxLayout which permits to fill the horizontal space and recalculate the preferred height.

Here's the screen: screen Tell me if you need some code

So, that's what I'm trying to achieve, now what approach do you suggest?

Edit 1:

Here's a little example, sorry if it took a while but as suggested I did it from scratch (i used eclipse's window builder)

package view;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;

import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingConstants;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;

public class Gui extends JFrame{

    private JPanel contentPane;
    private JScrollPane scrollPane;
    private JPanel panel;
    private JLabel lbThumbnail;
    private JPanel descriptions;
    private JPanel header;
    private JLabel lb1;
    private JLabel lb2;
    private JLabel lb3;
    private JLabel lb4;

    /**
     * Launch the application.
     */
    public static void main(String[] args){
        EventQueue.invokeLater(new Runnable(){
            @Override
            public void run(){
                try{
                    Gui frame=new Gui();
                    frame.setVisible(true);
                }catch(Exception e){
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * Create the frame.
     */
    public Gui(){
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setBounds(100,100,564,365);
        contentPane=new JPanel();
        contentPane.setBorder(new EmptyBorder(5,5,5,5));
        contentPane.setLayout(new BorderLayout(0,0));
        setContentPane(contentPane);

        scrollPane = new JScrollPane();
        scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
        scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
        contentPane.add(scrollPane, BorderLayout.CENTER);

        panel = new JPanel();
        panel.setBackground(Color.RED);
        panel.setBorder(null);
        scrollPane.setViewportView(panel);
        panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));

        header = new JPanel();
        header.setBackground(Color.GREEN);
        header.setBorder(null);
        panel.add(header);
        GridBagLayout gbl_header = new GridBagLayout();
        gbl_header.columnWidths = new int[] {120, 46};
        gbl_header.rowHeights = new int[] {14};
        gbl_header.columnWeights = new double[]{0.0, 0.0};
        gbl_header.rowWeights = new double[]{0.0};
        header.setLayout(gbl_header);

        lbThumbnail = new JLabel("thumbnail");
        lbThumbnail.setBorder(new LineBorder(Color.GRAY, 3));
        lbThumbnail.setHorizontalAlignment(SwingConstants.CENTER);
        lbThumbnail.setBackground(Color.RED);
        GridBagConstraints gbc_lbThumbnail = new GridBagConstraints();
        gbc_lbThumbnail.fill = GridBagConstraints.BOTH;
        gbc_lbThumbnail.weightx = 0.3;
        gbc_lbThumbnail.weighty = 1.0;
        gbc_lbThumbnail.gridx = 0;
        gbc_lbThumbnail.gridy = 0;
        header.add(lbThumbnail, gbc_lbThumbnail);

        descriptions = new JPanel();
        descriptions.setBackground(Color.ORANGE);
        descriptions.setBorder(null);
        GridBagConstraints gbc_descriptions = new GridBagConstraints();
        gbc_descriptions.weightx = 0.7;
        gbc_descriptions.fill = GridBagConstraints.BOTH;
        gbc_descriptions.gridx = 1;
        gbc_descriptions.gridy = 0;
        header.add(descriptions, gbc_descriptions);
        descriptions.setLayout(new BoxLayout(descriptions, BoxLayout.PAGE_AXIS));

        lb1 = new JLabel("<html>y y y y y y y y y y y y y y y y y y</html>");
        lb1.setBorder(new LineBorder(Color.GRAY, 3));
        lb1.setHorizontalAlignment(SwingConstants.CENTER);
        lb1.setAlignmentY(Component.TOP_ALIGNMENT);
        lb1.setAlignmentX(Component.CENTER_ALIGNMENT);
        descriptions.add(lb1);

        lb2 = new JLabel("<html>this label should fill the container width, and then eventually increase it's height y y y y y y y y y y y y y y y y y y</html>");
        lb2.setBorder(new LineBorder(Color.GRAY, 3));
        lb2.setHorizontalAlignment(SwingConstants.CENTER);
        lb2.setAlignmentY(Component.TOP_ALIGNMENT);
        lb2.setAlignmentX(Component.CENTER_ALIGNMENT);
        descriptions.add(lb2);

        lb3 = new JLabel("<html>the same y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y</html>");
        lb3.setBorder(new LineBorder(Color.GRAY, 3));
        lb3.setHorizontalAlignment(SwingConstants.CENTER);
        lb3.setAlignmentY(Component.TOP_ALIGNMENT);
        lb3.setAlignmentX(Component.CENTER_ALIGNMENT);
        descriptions.add(lb3);

        lb4 = new JLabel("<html>the upper panel should be as short as it can be, now it's too long<br/>y<br/>y<br/>y<br/></html>");
        lb4.setAlignmentX(0.5f);
        lb4.setBorder(new LineBorder(Color.GRAY, 3));
        lb4.setHorizontalAlignment(SwingConstants.CENTER);
        panel.add(lb4);
    }

}

output: gif

  • the top area is too tall
  • the right labels don't resize on width
  • the entire viewport panel resizes with the frame

Solution

  • Your original picture was misleading since it looked like your text was wrapping to fit into the width of the viewport which was confusing since normally the text doesn't wrap when displayed in a scroll pane as it is displayed at its preferred size.

    However, your MCVE shows that the text indeed does not wrap. So the problem is not your layout manager but the implementation of the Scrollable interface of the panel added to the scroll pane. The default behaviour is to display components add there preferred width/height and display a scrollbar when required.

    In your case you want to force the "width" to fit in the size of the viewport and therefore cause the text to wrap vertically as required and then have the vertical scrollbar appear as required. This will cause the text of the labels to wrap and the height to be recalculated dynamically (because of the way HTML is handled by the JLabel).

    So you need to implement the Scrollable interface for your panel and override the getScrollableTracksViewportWidth() method to return “true”, which will force the width of the panel to fit in the width of the viewport of the scroll pane so a horizontal scrollbar will never appear.

    Or an easier way to do this is to use the Scrollable Panel which has methods that allow you to control the properties of the `Scrollable interface.

    Using the above class you can change your code as follows:

    //panel = new JPanel();
    ScrollablePanel panel = new ScrollablePanel();
    panel.setScrollableWidth( ScrollablePanel.ScrollableSizeHint.FIT );