I have a JScrollPane, with a customized Scrollable view to match its width, allowing/requiring only vertical scrolling. It is populated with varying quantities of JLabels, which almost always word-wrap due to their length. I'm using a BoxLayout to ensure they are always full-width, laid out vertically, and it mostly works.
A major design requirement involves knowing precisely where each of these JLabels are, so that the scroll bar can be programmatically moved to center it. If you've used NetBeans, you should be familiar with the idea; Find in Chrome, though it doesn't jump there, also indicates where things are.
The problem I'm encountering is that, at some point, the layout just gives up. You can tell by the way that the scroll bar jumps around during scrolling that it hasn't actually calculated everything yet. Because of this, the reported getY
values are useless.
I've added the following bit of debug code;
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());
}
});
As expected - but not desired - the Y position of the last (or any other) element continues to increase as the panel is scrolled, as more and more JLabels properly calculate their size. The getHeight()
check indeed reveals that they are set at the height of a single line, unaware that they need to word-wrap, until they come close to being visible.
Once the entire panel has been scrolled through, it's happy... for the moment. It does not un-calculate when those JLabels go back out of view. But it also does not re-calculate when the window is resized, causing the values to once again become useless. (And, incidentally, break math; components too high above the rendered area will also report bad heights, potentially adding to far greater than the new height of their container!)
I am therefore seeking, quite simply, a way to ensure that every single component in this container will always have completely accurate and current dimension and location values. And despite that seeming to be just the way things ought to work - how unprofessional is a jittery scrollbar? - I haven't found any resources on achieving that.
EDIT: It bothers me greatly that people who don't know the answer think that's only because they can't see the code. It's okay! You can't know everything; if you haven't encountered and resolved the problem before, that doesn't make you any less of a coder, and I'm not expecting you to answer. But, I have managed to pare down the issue to a very basic single file:
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;
}
}
}
As you can see, there's nothing inherently special; literally the bare minimum required to make a frame, drop a JScrollPane in, make it so the view fits the width, and make some JLabels arranged vertically via BoxLayout. Precisely as described, no special tricks.