Results 1 to 4 of 4
- 03-16-2012, 06:55 PM #1
Scroll bar knob is jumpy when component is growing
I've written a JComponent that manipulates its own scrolling behavior as its size dynamically changes. The component itself seems to work exactly as I intended. The details are in this thread.
The only remaining issue is a cosmetic one. When the component's size changes, it scrolls its preferred viewport back to the bottom if it was previously at the bottom. The problem is that when the component is growing rapidly, the scroll bar knob is jumpy. It ends up at the bottom like it's supposed to, but you see it jump up and then back down.
I think I understand why this is happening. When JScrollPane handles a call to revalidate() on the EDT, it updates the size of its client and then updates the position of the scroll bar knob based on the current viewport position. Immediately after that, the EDT executes the task that scrolls the viewport back to the bottom. The result is that the scroll bar is briefly drawn with the knob away from the bottom.
I can't see any way around this as far as the JComponent is concerned. There is no getPreferredScrollableViewport() method in the Scrollable interface. There should be, and JScrollPane should test it when it handles revalidate().
Unless someone has a better suggestion, I'm going to try to extend Scrollable and JScrollPane and see if I can fix this.Get in the habit of using standard Java naming conventions!
- 03-16-2012, 07:25 PM #2
Re: Scroll bar knob is jumpy when component is growing
Okay, this reduces but does not completely eliminate the jumpy scroll bar knob. I think a complete solution would require fully overriding the behavior of validate() instead of just calling super.validate().
Do you think this is worthy of suggesting for future JDK versions? It would dramatically simplify things like keeping a component scrolled to the bottom. Instead of queuing a Runnable to handle the scrolling, you would just need to call revalidate().
Java Code:import java.awt.Rectangle; import javax.swing.Scrollable; public interface EnhancedScrollable extends Scrollable { public abstract Rectangle getPreferredScrollableViewport(); }Java Code:import java.awt.Component; import javax.swing.JScrollPane; public class EnhancedJScrollPane extends JScrollPane { private static final long serialVersionUID = 1L; // TODO: imitate other JScrollPane constructors public EnhancedJScrollPane(Component view) { super(view); } @Override public void validate() { super.validate(); Component view = viewport.getView(); if(view instanceof EnhancedScrollable) { viewport.scrollRectToVisible(((EnhancedScrollable) view).getPreferredScrollableViewport()); } } }Last edited by kjkrum; 03-16-2012 at 07:34 PM.
Get in the habit of using standard Java naming conventions!
- 03-16-2012, 11:15 PM #3
Re: Scroll bar knob is jumpy when component is growing
Why do they call it rush hour when nothing moves? - Robin Williams
- 03-19-2012, 05:20 AM #4
Re: Scroll bar knob is jumpy when component is growing
Well, for future reference, I found a much better way to do this. The key change is to extend ViewportLayout and replace your JScrollPane viewport's layout manager with the modified one:
For convenience, I've also extended JScrollPane to perform that replacement:Java Code:import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Point; import java.awt.Rectangle; import javax.swing.JViewport; import javax.swing.ViewportLayout; public class StickyViewportLayout extends ViewportLayout { private static final long serialVersionUID = 1L; @Override public void layoutContainer(Container parent) { JViewport viewport = (JViewport) parent; Component client = viewport.getView(); if(client instanceof StickyScrollable) { StickyScrollable sticky = (StickyScrollable) client; Rectangle visible = viewport.getViewRect(); Dimension size = viewport.getViewSize(); boolean atBottom = (visible.y == size.height - visible.height); super.layoutContainer(parent); if(atBottom) { size = viewport.getViewSize(); visible.y = size.height - visible.height; viewport.setViewPosition(visible.getLocation()); // without this, it appears to freeze at the bottom, then goes black // because it always paints with this offset to fix the jitter problem sticky.resetDesiredRelativeScroll(); } else { Point relativeScroll; synchronized(sticky) { relativeScroll = sticky.getDesiredRelativeScroll(); sticky.resetDesiredRelativeScroll(); } visible.x += relativeScroll.x; if(visible.x < 0) visible.x = 0; visible.y += relativeScroll.y; if(visible.y < 0) visible.y = 0; viewport.setViewPosition(visible.getLocation()); } } else super.layoutContainer(parent); } }
And here's the StickyScrollable interface:Java Code:import java.awt.Component; import javax.swing.JScrollPane; public class StickyScrollPane extends JScrollPane { private static final long serialVersionUID = 1L; public StickyScrollPane() { super(); replaceViewportLayout(); } public StickyScrollPane(Component view, int vsbPolicy, int hsbPolicy) { super(view, vsbPolicy, hsbPolicy); replaceViewportLayout(); } public StickyScrollPane(Component view) { super(view); replaceViewportLayout(); } public StickyScrollPane(int vsbPolicy, int hsbPolicy) { super(vsbPolicy, hsbPolicy); replaceViewportLayout(); } protected void replaceViewportLayout() { viewport.setLayout(new StickyViewportLayout()); } }
The layout manager automatically keeps the viewport scrolled to the bottom if it's already at the bottom. The component doesn't have to worry about that at all. And if that's all I wanted to do, the methods in StickyScrollable wouldn't even be necessary. StickyScrollable could just be a marker interface.Java Code:import java.awt.Point; import javax.swing.Scrollable; public interface StickyScrollable extends Scrollable { // FIXME: these are dumb names, and Point is not the right type to use public abstract Point getDesiredRelativeScroll(); public abstract void resetDesiredRelativeScroll(); }
But if the component is displaying data from a scrolling circular buffer, a viewport position in the middle will show data scrolling by. StickyScrollable.getDesiredRelativeScroll() lets the layout manager know how much the component wants to be scrolled so that it continues to display the same data until it scrolls out of the buffer.
This is all very fast -- at least ten times faster than any other approach I've tried. It's so fast that I ran into a whole different race condition: the buffer was being updated between revalidation and repainting. This was causing jitter when displaying the same data as it scrolls. The solution is kind of a hack, and I don't consider it finished.
This is the method in my JComponent that gets called when something updates the buffer that backs it:
And this is its paintComponent method. It's worth noting that the value of desiredRelativeScroll you see here is in character cells, but the value returned by getDesiredRelativeScroll() is in pixels.Java Code:@Override synchronized public void extentsChanged() { // compute deltas Rectangle newExtents = buffer.getExtents(); int bufferYDelta = newExtents.y - oldExtents.y; oldExtents.setBounds(newExtents); int oldHeight = preferredSize.height; calculatePreferredSize(newExtents); int componentHDelta = preferredSize.height - oldHeight; if(componentHDelta != 0) { revalidate(); repaint(); } else if(bufferYDelta != 0) { desiredRelativeScroll.y -= bufferYDelta; revalidate(); repaint(); } }
Java Code:@Override protected void paintComponent(Graphics g) { Rectangle clip = g.getClipBounds(); ((Graphics2D) g).setBackground(VGAColors.background(VGAColors.BLACK)); // figure out the cell extents of the clip int x1 = clip.x / glyphSize.width; int y1 = clip.y / glyphSize.height; int x2 = (int) Math.ceil((float)(clip.x + clip.width) / glyphSize.width); int y2 = (int) Math.ceil((float)(clip.y + clip.height) / glyphSize.height); // use block synchronization so the buffer can't change // while the edt is in the middle of drawing it synchronized(buffer) { synchronized(this) { Rectangle extents = buffer.getExtents(); for(int row = y1; row < y2; ++row) { for(int col = x1; col < x2; ++col) { // translate display coordinates into buffer coordinate system // FIXME: adjusting by desiredRelativeScroll is kind of a hack int bufCol = col + extents.x + desiredRelativeScroll.x; int bufRow = row + extents.y + desiredRelativeScroll.y; // draw each cell if(extents.contains(bufCol, bufRow)) { font.drawGlyph(buffer.getAttributes(bufCol, bufRow), g, col, row); } else { g.clearRect(col * glyphSize.width, row * glyphSize.height, glyphSize.width, glyphSize.height); } } } } } }Get in the habit of using standard Java naming conventions!
Similar Threads
-
polygon growing
By ericT in forum New To JavaReplies: 1Last Post: 09-24-2011, 08:35 PM -
scroll popupmenu
By jperson in forum New To JavaReplies: 1Last Post: 11-18-2010, 05:17 AM -
smooth-scroll
By designer in forum Java AppletsReplies: 1Last Post: 07-21-2009, 07:10 PM -
Scroll message
By getkiran in forum Java AppletsReplies: 1Last Post: 03-05-2009, 04:29 AM -
Reading growing files
By ionna22 in forum Advanced JavaReplies: 0Last Post: 10-20-2008, 04:43 PM


LinkBack URL
About LinkBacks
Reply With Quote

Bookmarks