Auto-scrolling in Swing applications
March 21st, 2008 | 8 Comments »Auto-scrolling is a feature that can be found in most mainstream modern applications. Pressing the middle mouse button transfers the application into the auto-scrolling mode. In this mode, moving the mouse down starts scrolling down the contents even when the mouse is no longer moving. The scroll speed depends on the distance between the current mouse location and the mouse press location. This works for all four directions (up, down, left, right).
There are two existing Swing implementations that i’m aware of:
- Patrick Gotthardt replaces the original scroll pane view with a custom view.
- Santhosh Kumar installs a custom glass pane.
Both approaches have their drawbacks. The first one effectively changes the UI hierarchy and the second one uses a global frame resource, effectively resulting in repainting the entire UI on every scroll (even when the scroll pane is a small part of the application).
Christopher Deckers has already appeared as a guest author on this blog, and he was very helpful during the development cycles of last two versions of Substance look-and-feel. He has found numerous bugs (many thanks) and made numerous suggestions for enhancing existing features and adding new ones (many thanks). One of his “most wanted” features was the support for auto-scrolling, and after a few iterations he has contributed an elegant implementation that does not use a global resource and does not change the UI hierarchy.
The new LafWidget.AUTO_SCROLL client property can be installed on a specific scroll pane or globally on the UIManager. Once it is set to Boolean.TRUE, the corresponding scroll pane(s) will have the auto-scroll mode installed on them. There are two operation modes:
- Press the middle mouse button and release it. The scroll pane is transferred into the auto-scroll mode. It can be dismissed by clicking the mouse or scrolling the mouse wheel.
- Press the middle mouse button and start dragging. The auto-scroll is dismissed by releasing the middle mouse button.
The implementation itself installs a mouse listener on the scroll pane, and when the middle mouse button is clicked, it shows the auto-scroll icon wrapped in a popup menu (thus no need for glass pane). In addition to showing the menu, it also installs a global AWTListener that listens to mouse wheel and mouse move events, scrolling the pane as necessary.
This is the last new feature that was added to the version 4.3 of Substance. It is now in feature freeze state. The release candidate is scheduled on March 31st, with the final release scheduled on April 14.
Related posts:
- Demo for auto-scrolling in Swing applications The previous entry on auto-scrolling in Swing applications under the latest development drops of Substance...
- Animation blueprints for Swing – scrolling layout After adding such animation effects as fading, translucency, load progress and asynchronous load of images...
- Animations 202 – scrolling After seeing how the rules of physical world can be applied to animating colors, it’s...
- New Creme Coffee skin for Swing applications Just before the feature freeze of Substance 4.1, i added a new Coffee Cream skin...
Hello Kirill
It is interesting that I started my own implementation for this feature
about a month ago, so you are always a step ahead of me :-)
By the way, could you give more details about why glassPane approach
“repaints the entire UI on every scroll ”
What do you mean by the “entire UI”, is it the whole frame ?
Thanks
alexp
Alex,
It was my understanding that a non-opaque glass pane repaints everything (the whole frame) when it needs to repaint. If the specific glass pane implementation does not have any child elements (just overrides paintComponent()), a repaint of any sub-area of that frame will trigger the repaint of the glass pane which will trigger the repaint of the entire frame. Is my understanding of the scenario incorrect?
Thanks
Kirill
Hello Kirill
I wrote a simple test which shows how repainting is changed when glassPane is visible. I’ll add the sources at the end of this message
When you move the mouse over the checkBox and glassPane is invisible,
you can see in the console that checkBox is repainted within its bounds
By clicking on this checkBox you’ll make the glassPane visible,
move the mouse over it again and you’ll see that painting starts from
the RootPane now, goes to the checkBox and finally to the GlassPane
The painting process of Swing is smart enough
to quickly finds the origin component to be repainted,
and painting the small part of the transparent glassPane costs nothing
As you can see all painting are performed within the bounds of the CheckBox,
so it is very different from the “repainting the entire UI”
Would you agree?
Thanks
alexp
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
public class GlassPaneRepainting {
private static void createAndShowGui() {
final JFrame frame = new JFrame() {
protected JRootPane createRootPane() {
JRootPane rp = new JRootPane() {
public void paint(Graphics g) {
System.out.println(“”);
System.out.println(“JRootPane.paint”);
System.out.println(“g.getClipBounds() = ” + g.getClipBounds());
super.paint(g);
}
};
rp.setOpaque(true);
return rp;
}
};
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new FlowLayout());
frame.add(new JCheckBox(“Show the glassPane”) {
public void paint(Graphics g) {
System.out.println(“JCheckBox.paint”);
System.out.println(“g.getClipBounds() = ” + g.getClipBounds());
super.paint(g);
}
protected void fireActionPerformed(ActionEvent event) {
frame.getGlassPane().setVisible(!frame.getGlassPane().isVisible());
System.out.println(“”);
System.out.println(“getGlassPane().isVisible() = ” + frame.getGlassPane().isVisible());
super.fireActionPerformed(event);
}
});
frame.setGlassPane(new JPanel() {
{setOpaque(false);}
public void paint(Graphics g) {
System.out.println(“GlassPaneRepainting.paint”);
System.out.println(“g.getClipBounds() = ” + g.getClipBounds());
super.paint(g);
}
});
frame.setSize(200, 200);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
Alex,
This looks a little surprising to me. What happens when the painting done in glass pane overflows the component bounds (as i did for validation overlays)? When i need to update that painting, i’ll have to force repaint of the entire frame myself?
And look at the example from Santhosh – the scroller image is drawn centered around the mouse click location. While the test application that he shows only has one scroll pane and he clicked in the middle of that scroll pane, what would happen if you click in the top left corner of the scroll pane? Would the scroller image be cut? And if it would, would you have to force the repaint of the entire frame?
Thanks
Kirill
Hello Kirill
GlassPane is repainted when any component under it is repainted,
if you place a decoration outside e.g. button and want it to be in sync,
with the button’s state you certainly need to repaint a glassPane
but again, one of the key point of the fast painting is not to paint more
than you need, so in this case you should repaint only small part of the glassPane, which contains that decorations
About example from Santhosh:
> what would happen if you click in the top left corner of the scroll pane? > Would the scroller image be cut
I don’t know, I thought you had tested it :-)
In my opinion, in the implementation from Santhosh,
the whole frame is repainted once when
you press the wheel and the ScrollGlassPane becomes visible
if you need to move the scrolling indicator,
you just need to repaint the small area where it was
and the area where it is now
I don’t see any reasons why the frame should be repainted during scrolling
Thanks
alexp
Alex,
Thanks for the clarifications. I had the wrong idea that the entire frame is repainted once a non-opaque glass pane is installed.
Kirill
You are welcome my friend,
see you in the J1 soon
alexp
JXLayer 3.0 – MouseScrollableUI
Implementing auto-scrolling feature with JXLayer