简体   繁体   English

Java Swing中的垂直堆叠布局?

[英]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 . 我想在scrollPane中安排一些标签,以便它们一个堆叠在另一个之下,并且它们中的每一个都必须足够高(不多于!)以适合它包含的文本,并且与相应的容器一样宽
The labels' text is formatted with html to achieve the word wrap effect. 标签的文本使用html格式化以实现自动换行效果。

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. 我使用了gridBagLayout来保持图像和右边描述之间的宽度比。
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. 或者是一个boxLayout,它允许填充水平空间并重新计算首选高度。

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: 编辑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) 这是一个小例子,抱歉,如果需要一段时间,但建议我从头开始做(我使用eclipse的窗口构建器)

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. 但是,您的MCVE显示文本确实没有换行。 So the problem is not your layout manager but the implementation of the Scrollable interface of the panel added to the scroll pane. 所以问题不在于您的布局管理器,而是添加到滚动窗格的面板的Scrollable接口的实现。 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). 这将导致标签的文本换行并动态重新计算高度(因为JLabel处理HTML的方式)。

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. 因此,您需要为面板实现Scrollable接口并覆盖getScrollableTracksViewportWidth()方法以返回“true”,这将强制面板的宽度适合滚动窗格视口的宽度,因此水平滚动条将永远不会出现。

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. 或者更简单的方法是使用Scrollable Panel ,它具有允许您控制`Scrollable接口属性的方法。

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. 然后是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)); } 

    } }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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