简体   繁体   中英

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: 模式

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: 屏幕 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

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

I hope I didn't misunderstand your question, so basically you want the wrapped text label to use available width and adjust it's height accordingly, right? I'm not sure whether the built-in layout managers can solve your problem, but a custom layout manager will be the easiest approach in my opinion.

First we have the frame as test drive, it has a scroll pane which contains the panel that applied the custom layout. That custom layout need to "know" the components so we just pass them to the constructor:

public class MainFrame extends JFrame {
    private final JTextPane txt1 = createTextPane();
    private final JTextPane txt2 = createTextPane();
    private final JTextPane txt3 = createTextPane();

    public MainFrame() {
        JPanel panel = new JPanel();

        panel.setLayout(new CustomLayout(txt1, txt2, txt3));

        panel.add(txt1);
        panel.add(txt2);
        panel.add(txt3);

        add(new JScrollPane(panel));

        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setBounds(10, 10, 100, 100);
    }

And I used text pane to allow for the word wrap functionality:

private static JTextPane createTextPane() {
    JTextPane result = new JTextPane();

    result.setContentType("text/html");
    result.setEditable(false);
    result.setText("<html>Hello World Hello World Hello World Hello World Hello World Hello World</html>");

    return result;
}

Then the code for CustomLayout. The most important methods are how you layout the components and return the preferred parent size. The idea is:

  • Set the component's width to the available width (the height you set do not matter so I just pass zero), and get preferred height will be the height for the wrapped content.
  • Note that I used the parent's parent to get available width, because the parent is not yet laid-out so its width will be incorrect, while parent's parent is already laid-out. You can say it is a dangerous assumption, so it really depend on the component hierarchy and structure of your actual situation.

     public class CustomLayout implements LayoutManager { private final JTextPane txt1; private final JTextPane txt2; private final JTextPane txt3; public CustomLayout(JTextPane aTxt1, JTextPane aTxt2, JTextPane aTxt3) { txt1 = aTxt1; txt2 = aTxt2; txt3 = aTxt3; } @Override public void addLayoutComponent(String name, Component comp) { } @Override public void removeLayoutComponent(Component comp) { } @Override public Dimension preferredLayoutSize(Container parent) { int width = parent.getParent().getWidth(); int height = 0; height += calculateComponentPreferredHeightForWidth(txt1, width); height += calculateComponentPreferredHeightForWidth(txt2, width); height += calculateComponentPreferredHeightForWidth(txt3, width); return new Dimension(width, height); } private static int calculateComponentPreferredHeightForWidth(JComponent component, int width) { component.setSize(width, 0); return component.getPreferredSize().height; } @Override public Dimension minimumLayoutSize(Container parent) { return preferredLayoutSize(parent); } @Override public void layoutContainer(Container parent) { int width = parent.getWidth(); int layoutX = 0; int layoutY = 0; txt1.setBounds(layoutX, layoutY, width, calculateComponentPreferredHeightForWidth(txt1, width)); layoutY = txt1.getY() + txt1.getHeight(); txt2.setBounds(layoutX, layoutY, width, calculateComponentPreferredHeightForWidth(txt2, width)); layoutY = txt2.getY() + txt2.getHeight(); txt3.setBounds(layoutX, layoutY, width, calculateComponentPreferredHeightForWidth(txt3, width)); } 

    }

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