java - 预先计算离屏组件尺寸?

标签 java swing boxlayout

我有一个 JScrollPane,带有自定义的可 ScrollView 以匹配其宽度,仅允许/要求垂直滚动。它填充了不同数量的 JLabel,由于它们的长度,它们几乎总是自动换行。我使用 BoxLayout 来确保它们始终是全宽、垂直布局,并且大部分情况下都能正常工作。

一个主要的设计要求涉及精确了解每个 JLabel 的位置,以便可以通过编程方式移动滚动条使其居中。如果您使用过 NetBeans,您应该熟悉这个想法;在 Chrome 中查找,虽然它不会跳到那里,但也会指示事物的位置。

我遇到的问题是,在某些时候,布局会放弃。您可以通过滚动条在滚动过程中跳跃的方式来判断它实际上尚未计算出所有内容。因此,报告的 getY 值毫无用处。

我添加了以下调试代码;

this.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
        public void adjustmentValueChanged(AdjustmentEvent evt) {
            //getVerseBlock grabs a component from the view, with sanity checks:
            System.out.println(ChapterTab.this.getVerseBlock(255).getY());
            System.out.println(ChapterTab.this.getVerseBlock(255).getHeight());
        }
    });

正如预期(但不是所期望的)那样,随着面板滚动,最后一个(或任何其他)元素的 Y 位置继续增加,因为越来越多的 JLabels 正确计算了它们的大小。 getHeight() 检查确实表明它们被设置为单行的高度,但没有意识到它们需要自动换行,直到它们接近可见为止。

一旦整个面板都滚动完毕,暂时就很高兴了。当这些 JLabels 返回 View 之外时,它不会取消计算。但当窗口大小调整时,它不会重新计算,导致这些值再次变得无用。 (顺便说一句,破坏数学;高于渲染区域的组件也会报告错误的高度,可能会增加到远远大于其容器的新高度!)

因此,我正在寻找一种非常简单的方法来确保该容器中的每个组件始终具有完全准确的当前尺寸和位置值。尽管这似乎就是事情应该进行的方式——一个紧张的滚动条有多不专业? - 我还没有找到任何实现这一目标的资源。

编辑:令我非常困扰的是,不知道答案的人认为这只是因为他们看不到代码。没关系!你不可能知道一切;如果您以前没有遇到并解决过这个问题,这并不会让您成为一名编码员,而且我也不指望您回答。但是,我已经设法将问题简化为一个非常基本的单个文件:

package jscrolltest;


import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.Scrollable;
import javax.swing.SwingConstants;

public class JScrollTest {

    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }

    private static void createAndShowGUI() {

        JFrame frame = new JFrame("JScrollTest");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().setLayout(new BorderLayout());

        ScrollableView view = new ScrollableView();
        view.setLayout(new BoxLayout(view, BoxLayout.Y_AXIS));

        JScrollPane pnlScroll = new JScrollPane(view);
        pnlScroll.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
        pnlScroll.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);

        view.add(new JLabel("<html>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc convallis sapien sed tempor scelerisque."));
        view.add(new JLabel("<html>Donec ut leo nec ligula tempus eleifend. Proin in porttitor velit."));
        view.add(new JLabel("<html>Sed a facilisis orci. Nunc a diam feugiat, suscipit nunc nec, porttitor velit."));
        view.add(new JLabel("<html>Maecenas sagittis, est et bibendum luctus, tortor orci hendrerit enim, vitae rhoncus augue libero sit amet est. Phasellus a neque et magna gravida euismod dignissim ac elit."));
        view.add(new JLabel("<html>Ut neque urna, ultrices fermentum quam et, tristique tempus nulla. Curabitur non feugiat leo."));
        view.add(new JLabel("<html>Aenean eu viverra ligula, eu tempor turpis. Suspendisse eu nunc ac urna blandit egestas quis id augue. "));
        view.add(new JLabel("<html>Suspendisse a pulvinar est. Maecenas id congue neque. Donec eleifend nisi quis nisl faucibus sollicitudin."));
        view.add(new JLabel("<html>Aliquam rutrum nulla neque, sit amet sollicitudin massa ultrices quis. Interdum et malesuada fames ac ante ipsum primis in faucibus."));
        view.add(new JLabel("<html>Praesent ut auctor nisl, eget convallis neque. Quisque et vestibulum massa."));
        view.add(new JLabel("<html>Quisque consectetur ex cursus risus interdum, tristique imperdiet ante viverra. Sed et nulla eget sem dapibus fringilla."));

        frame.getContentPane().add(pnlScroll, BorderLayout.CENTER);

        pnlScroll.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
            public void adjustmentValueChanged(AdjustmentEvent evt) {
                System.out.println(view.getComponent(9).getY());
                System.out.println(view.getComponent(9).getHeight());
            }
        });

        frame.setSize(200, 100);
        frame.setVisible(true);
    }

    public static class ScrollableView extends JPanel implements Scrollable {

        public Dimension getPreferredScrollableViewportSize() {
            return getPreferredSize();
        }

        public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
            return 10;
        }

        public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
            return ((orientation == SwingConstants.VERTICAL) ? visibleRect.height : visibleRect.width) - 10;
        }

        public boolean getScrollableTracksViewportWidth() {
            return true;
        }

        public boolean getScrollableTracksViewportHeight() {
            return false;
        }
    }
}

正如您所看到的,本质上没有什么特别的;从字面上看,这是制作框架所需的最低限度,放入 JScrollPane,使其 View 适合宽度,并通过 BoxLayout 垂直排列一些 JLabels。完全按照描述,没有特殊技巧。

最佳答案

In this case, the off-screen JLabels are either not given a width, or not given the paint() call that actually causes the word-wrap to occur (a known flaw in their design; they will not run those calculations until painted,

你说这是一个设计问题,所以我能想到的唯一解决方案最多就是黑客攻击。似乎适用于您的 SSCCE:

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.swing.*;

public class JScrollTest {

    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }

    private static void createAndShowGUI() {

        JFrame frame = new JFrame("JScrollTest");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().setLayout(new BorderLayout());

        final ScrollableView view = new ScrollableView();
        view.setLayout(new BoxLayout(view, BoxLayout.Y_AXIS));

        JScrollPane pnlScroll = new JScrollPane(view);
        pnlScroll.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
        pnlScroll.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);

        view.add(new JLabel("<html>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc convallis sapien sed tempor scelerisque."));
        view.add(new JLabel("<html>Donec ut leo nec ligula tempus eleifend. Proin in porttitor velit."));
        view.add(new JLabel("<html>Sed a facilisis orci. Nunc a diam feugiat, suscipit nunc nec, porttitor velit."));
        view.add(new JLabel("<html>Maecenas sagittis, est et bibendum luctus, tortor orci hendrerit enim, vitae rhoncus augue libero sit amet est. Phasellus a neque et magna gravida euismod dignissim ac elit."));
        view.add(new JLabel("<html>Ut neque urna, ultrices fermentum quam et, tristique tempus nulla. Curabitur non feugiat leo."));
        view.add(new JLabel("<html>Aenean eu viverra ligula, eu tempor turpis. Suspendisse eu nunc ac urna blandit egestas quis id augue. "));
        view.add(new JLabel("<html>Suspendisse a pulvinar est. Maecenas id congue neque. Donec eleifend nisi quis nisl faucibus sollicitudin."));
        view.add(new JLabel("<html>Sed a facilisis orci. Nunc a diam feugiat, suscipit nunc nec, porttitor velit."));
        view.add(new JLabel("<html>Maecenas sagittis, est et bibendum luctus, tortor orci hendrerit enim, vitae rhoncus augue libero sit amet est. Phasellus a neque et magna gravida euismod dignissim ac elit."));
        view.add(new JLabel("<html>Ut neque urna, ultrices fermentum quam et, tristique tempus nulla. Curabitur non feugiat leo."));
        view.add(new JLabel("<html>Aenean eu viverra ligula, eu tempor turpis. Suspendisse eu nunc ac urna blandit egestas quis id augue. "));
        view.add(new JLabel("<html>Suspendisse a pulvinar est. Maecenas id congue neque. Donec eleifend nisi quis nisl faucibus sollicitudin."));
        view.add(new JLabel("<html>Aliquam rutrum nulla neque, sit amet sollicitudin massa ultrices quis. Interdum et malesuada fames ac ante ipsum primis in faucibus."));
        view.add(new JLabel("<html>Praesent ut auctor nisl, eget convallis neque. Quisque et vestibulum massa."));
        view.add(new JLabel("<html>123Quisque consectetur ex cursus risus interdum, tristique imperdiet ante viverra. Sed et nulla eget sem dapibus fringilla."));

        frame.getContentPane().add(pnlScroll, BorderLayout.CENTER);

        pnlScroll.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
            public void adjustmentValueChanged(AdjustmentEvent evt) {
                System.out.println(view.getComponent(14).getY());
                System.out.println(view.getComponent(14).getHeight());
            }
        });

        frame.setSize(300, 200);
        frame.setVisible(true);
    }

    public static class ScrollableView extends JPanel implements Scrollable, ComponentListener {

        public ScrollableView()
        {
            addComponentListener( this );
        }

        public Dimension getPreferredScrollableViewportSize() {
            return getPreferredSize();
        }

        public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
            return 10;
        }

        public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
            return ((orientation == SwingConstants.VERTICAL) ? visibleRect.height : visibleRect.width) - 10;
        }

        public boolean getScrollableTracksViewportWidth() {
            return true;
        }

        public boolean getScrollableTracksViewportHeight() {
            return false;
        }

        //  Implement ComponentListener

        public void componentResized(ComponentEvent e)
        {
            SwingUtilities.invokeLater(new Runnable()
            {
                public void run()
                {
                    int width = getParent().getSize().width;

                    if (width == 0) return;

                    BufferedImage bi = new BufferedImage(width, 600, BufferedImage.TYPE_INT_RGB);
                    Graphics g = bi.getGraphics();

                    for (Component c: getComponents())
                    {
                        c.paint(g);
                    }

                    revalidate();
                    repaint();
                }
            });
        }

        public void componentHidden(ComponentEvent e) {}

        public void componentMoved(ComponentEvent e) {}

        public void componentShown(ComponentEvent e) {}

    }
}

不知道是否需要invokeLater()、revalidate()和repaint(),我只是通过它们。另外,我只是为BufferedImage选择了一个随机高度。据我所知,它甚至可以在高度为 1 的情况下工作。

关于java - 预先计算离屏组件尺寸?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29400026/

相关文章:

java - 在 JScrollingPanel 中滚动 JPanel

java - JPanels 文本字段 GBC

java - JFormatted Field 在不应该获得焦点时产生焦点

java swing,jcombobox 弹出窗口中的高亮项目

java - 强制Java使用用户输入作为消息进行编译错误

java - 为表生成序号的技术

java - 框布局中的列标题

java - BorderLayout 内的 BoxLayout

java - 盒子里的组件布局很奇怪

java - 在 BlackBerry 上播放声音的问题