JLabel将包装的HTML文本作为JScrollPane客户端

时间:2012-11-12 12:42:47

标签: java swing jscrollpane jlabel textwrapping

包含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 ){
        }

    }

}

1 个答案:

答案 0 :(得分:1)

显然,这可能是GridBagLayout中的错误,或者您正在以开发人员完全出乎意料的方式使用布局引擎。几个带有HTML的多行标签,设置首选尺寸并立即通过后门询问首选尺寸?啊!

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

when decreasing

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

enter image description here

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

DIRTY HACK

由于每个其他调整大小都会产生正确的结果,为什么不调整大小两次呢?在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错误的解决方法,如果没有别的帮助......; - )