JScrollPane客户端使用换行HTML文本的JLabel

7

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

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

framegrabbing

我无法通过单个换行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 ){
        }

    }

}

5
嗯,不用了,我会留给您来做。事实上,为了更快得到帮助,请发布一个SSCCE - Andrew Thompson
1个回答

0

显然这可能是GridBagLayout的一个bug,或者你正在以开发人员完全意想不到的方式使用布局引擎。多个带有HTML的多行标签,设置首选大小并立即通过后门请求首选大小?呃!

我注意到当缩小窗口大小时,有时布局会出现错误:滚动窗格内部的面板不会减小,水平滚动条会出现。(顺便说一下,我正在使用Windows系统)。

when decreasing

有时,如果垂直滚动条可见且面板高度较大,然后我增加窗口大小,则面板高度仍然不合理,并且标签周围出现空隙。

enter image description here

对我来说,每次缩小窗口时布局都会出错;增大窗口的效果更好,但如果出错,则每隔一次也是不正确的。我尝试了调试和将值打印到控制台上;似乎 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 bug的一种变通方法,如果没有其他办法... ;-)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接