7

包含 HTML 文本的 JLabel 使用可用空间自动换行。如果有人将该 JLabel 添加到 JSrollPane 中,他必须将 preferredSize 设置为一个合适的值,否则它不会换行。所有这些都应该在使用 LayoutManager 的 JPanel 中的其他组件上正常工作。

因为我想要一个可调整大小的应用程序窗口,我扩展了 JScrollPane 以跟踪调整大小事件并动态更改同步到视口宽度的大小。基本上它可以工作,但有时布局管理器对首选高度的计算是错误的(值太大或太小)。例如,穿过第一行的红色边框的可见性表明高度的计算是错误的。

抓帧

我无法使用单个包装 JLabel 重现失败。

import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;

public class WrappedLabel implements Runnable {

    public static void main( String[] args ){
        SwingUtilities.invokeLater( new WrappedLabel() );
    }

    @Override
    public void run(){
        final JPanel panel = new JPanel( new GridBagLayout() );
        final GridBagConstraints gc = new GridBagConstraints();
        gc.fill = GridBagConstraints.BOTH;
        gc.weightx = 1.0;
        gc.weighty = 1.0;
        {
            gc.gridx = 0;
            gc.gridy = 0;
            final JLabel label = new JLabel(
                "<html>" + "please add some more text here"
            );
            label.setBorder( BorderFactory.createLineBorder( Color.RED ) );
            panel.add( label, gc );
        }
        {
            gc.gridx = 0;
            gc.gridy = 1;
            final JLabel label = new JLabel(
                "<html>" + "please add some more text here"
            );
            label.setBorder( BorderFactory.createLineBorder( Color.RED ) );
            panel.add( label, gc );
        }
        final JFrame frame = new JFrame();
        frame.add( new ScrollPane( panel ) );
        frame.setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE );
        frame.setSize( 256, 256 );
        frame.setVisible( true );
    }

    private class ScrollPane extends JScrollPane implements ComponentListener {

        ScrollPane( Container view ){
            super( view );
            this.viewport.addComponentListener( this );
        }

        @Override
        public void componentHidden( ComponentEvent ce ){
        }

        @Override
        public void componentMoved( ComponentEvent ce ){
        }

        /** calculating required height is a 3 step process
          * 1. sync width of client and viewport, set height of client to high value
          * 2. let GridbagManager calculate required minimum size
          * 3. set preferredSize and revalidate
         **/
        @Override
        public void componentResized( ComponentEvent ce ){
            assert( this.viewport == ce.getSource() );
            final Container view = (Container) this.viewport.getView();
            final int width = this.viewport.getExtentSize().width;
            view.setPreferredSize( new Dimension( width, Integer.MAX_VALUE ) );
            final int height = view.getLayout().preferredLayoutSize( view ).height;
            view.setPreferredSize( new Dimension( width, height ) );
            view.revalidate();
        }

        @Override
        public void componentShown( ComponentEvent ce ){
        }

    }

}
4

1 回答 1

1

显然,它要么是 GridBagLayout 中的错误,要么是您以开发人员完全出乎意料的方式使用布局引擎。里面有HTML的几个多行Label,设置首选大小,后门立即询问首选大小?啊!

我注意到有时在减小窗口大小时布局工作不正确:滚动窗格内的面板没有减小并且出现水平滚动条。(顺便说一句,我正在使用Windows)。

当减少

另外,有时,如果垂直滚动条可见并且面板高度很大,然后我增加窗口大小,面板高度仍然过大并且标签周围出现间隙:

在此处输入图像描述

对我来说,每隔一次我减小窗口时布局都是错误的;增加效果更好,但如果出错,每隔一次也是不正确的。我尝试调试并将值打印到控制台;似乎这view.getLayout().preferredLayoutSize( view )不仅取决于view.setPreferredSize面板和滚动面板的当前大小,还取决于当前大小。GridBagLayout 的代码太复杂,无法深入研究。

肮脏的黑客

既然其他调整大小都会产生正确的结果,为什么不调整两次呢?在ScrollPane.componentResized处理程序中复制内容不成功,可能是因为 ScrollPane 的大小保持不变。ScrollPane 本身需要用不同的值调整大小两次。为了以最简单的方式对其进行测试,我对 JFrame 进行了子类化:它componentResized两次侦听并调整其子窗口的大小。第二次调整大小必须通过SwingUtilities.invokeLater.

替换行

final JFrame frame = new JFrame();
frame.add( scroll );

经过

final MyFrame frame = new MyFrame(scroll);

并添加以下类:

private class MyFrame extends JFrame implements ComponentListener {

    private Component child;

    public MyFrame(Component child){
        this.child=child;
        setLayout(null);
        getContentPane().add(child);
        addComponentListener(this);
    }

    public void componentResized(ComponentEvent e) {
        Dimension size=getContentPane().getSize();
        child.setSize(new Dimension(size.width-1,size.height));
        validate();
        SwingUtilities.invokeLater(new ResizeRunner(size));
    }

    public void componentMoved(ComponentEvent e) {}
    public void componentShown(ComponentEvent e) {}
    public void componentHidden(ComponentEvent e) {}

    private class ResizeRunner implements Runnable {
        private Dimension size;
        public ResizeRunner(Dimension size){
            this.size=size;
        }
        public void run() {
            child.setSize(size);
            validate();
        }
    }

}

同样可以通过子类化布局管理器来实现。

显然,这种方法不优雅且效率低下,但作为 JRE 错误的解决方法,如果没有其他帮助的话...... ;-)

于 2012-11-16T10:50:53.197 回答