java - 带有包装 HTML 文本的 JLabel 作为 JScrollPane 客户端

标签 java swing jscrollpane jlabel word-wrap

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

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

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

    }

}

最佳答案

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

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

when decreasing

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

enter image description here

对我来说,当我缩小窗口时,布局每隔一段时间就会出错;增加效果更好,但如果出错,每隔一段时间也是不正确的。我尝试调试和打印值到控制台; view.getLayout().preferredLayoutSize( view ) 似乎不仅取决于 view.setPreferredSize,还取决于面板和滚动 Pane 的当前大小。 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 错误的解决方法,如果没有其他帮助... ;-)

关于java - 带有包装 HTML 文本的 JLabel 作为 JScrollPane 客户端,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13343909/

相关文章:

jquery - 添加或删除动态内容时,jScrollPane 不会保持其位置 (jQuery)

java - 如何在运行时将 JPanel 添加到另一个具有垂直滚动 Pane 的 JPanel 中?

java - HTTP 响应中的 XML

java - 将数字拆分为多个数字

java - Android:将简单的光标链接到 ListView

java - 更改 JTable 关键操作的行为

Java快速像素操作

java - react native : Volume Button Should Only Control Media (Android)

java - 尝试在模态对话框中更新 JLabel

java - 从滚动 Pane 中的文本 Pane 获取文本