Java Tips Weblog

  • Blog Stats

    • 1,104,377 hits
  • Categories

  • Archives

Scrolling a Form

Posted by Rob Camick on May 9, 2010

It is easy to create a form, you just create a panel and add some components to it. It is also easy to make the form scrollable, you just add the panel to a scroll pane. However, maybe you’ve noticed that as you tab from component to component the viewport of the scroll pane does not scroll automatically when the focused component is no longer in the viewport? In many cases the form would be more usefull to the user if it would scroll automatically as focus changes from component to component.

Adding an automatic scrolling feature can be implemented in two basic steps:

  • handle focus changes – we need to listen for focus changes but we don’t want to add a FocusListener to every component on the form. In this case we can use a tip from the “Global Event Listeners” entry and use the KeyboardFocusManager to listen for focus changes.
  • scroll the viewport – this is done by using the scrollRectToVisible(…) method of JComponent. All we need to do is determine the Rectangle values to be used by the scroll method.

The FormScroller class will manage these two steps for you. There are only a couple of properties you can set to control the behaviour of the scroller. First, you can control the scroll type by using the setType(…) method:

  • Type.COMPONENT (default) – the component that has focus should be visible in the viewport.
  • Type.PARENT – the parent Container of the component that has focus should be visible in the viewport. If the Container does not fit completely in the viewport, then Type.COMPONENT scrolling will be used.
  • Type.CHILD – the child Container of the viewport view component which contains the focused component should be visible in the viewport. If the child Container does not fit completely in the viewport then Type.PARENT scrolling will be used.

Additionally, you may want to specify scrolling insets by using the setScrollInsets(…) method. Normally, when the scrollRectToVisible() method is invoked the viewport will be positioned such that the edges of the component are against the edges of the scroll pane. Specifying the scrolling insets will allow a gap between the component and the scroll pane whenever possible.

Well, thats all there is to say about the FormScroller. The image below gives you an idea what the form will look like when you scroll to the first non visible component. The viewport has been moved so the component is positioned 5 pixels above the scroll pane.

In the Webstart demo below, automatic scrolling was added to the scroll pane using:

FormScroller scroller = new FormScroller( scrollPane );
scroller.setScrollInsets( new Insets(5, 0, 5, 0) );

Try The Demo

Launch – Using Java™ Web Start (JRE 6 required)

Get The Code

FormScroller.java

See Also

Global Event Listeners

About these ads

5 Responses to “Scrolling a Form”

  1. André Uhres said

    Thanks, very useful class. Works fine, my congratulations for this great achievement.

  2. James Lee said

    Thanks. This is the best solution I’ve seen for the auto-scrolling feature, even in 2013.

    I probably shouldn’t expect it to work perfectly in all edge case scenarios without customizations, but I’ve just encountered a small usability issue, after applying it to this scenario:
    1) The scroll pane contains tabs; and
    2) Each tab contains a large child component (e.g. JTable/JList/JTextPane); and
    3) The user clicks on a child component in any tab; then
    4) The tab headers will be hidden from view.

    After this, if the user scrolls back up to click on any other tab, the tab headers will be hidden from view again.

    This can be replicated by using the SSCCE below:

    import java.awt.BorderLayout;
    import java.awt.Insets;
    
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.JScrollPane;
    import javax.swing.JTabbedPane;
    import javax.swing.JTextArea;
    import javax.swing.SwingUtilities;
    
    public class TabbedPaneTest extends JPanel {
    
        public TabbedPaneTest() {
            super(new BorderLayout());
    
            JTabbedPane tabbedPane = new JTabbedPane();
    
            tabbedPane.addTab("Tab 1", buildPanelWithLargeComponent());
            tabbedPane.addTab("Tab 2", buildPanelWithLargeComponent());
            tabbedPane.addTab("Tab 3", buildPanelWithLargeComponent());
            tabbedPane.addTab("Tab 4", buildPanelWithLargeComponent());
    
            JScrollPane scrollPane = new JScrollPane(tabbedPane);
            FormScroller scroller = new FormScroller(scrollPane);
            scroller.setScrollInsets(new Insets(5, 0, 5, 0));
    
            add(scrollPane);
        }
    
        private JPanel buildPanelWithLargeComponent() {
            JPanel panel = new JPanel();
    
            // this can be any type of large component (e.g. JTable/JList/JTextPane)
            panel.add(new JScrollPane(new JTextArea(10, 25)));
    
            return panel;
        }
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    JFrame frame = new JFrame("TabbedPane Test");
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
                    frame.add(new TabbedPaneTest());
    
                    frame.setSize(350, 200);
                    frame.setVisible(true);
                }
            });
        }
    
    }
    

    The best idea I can think of is to let the program scroll to the tab header whenever the focus is transferred to the 1st child component under a tab, but even that would have several loopholes and undesired side effects.

    So I need to ask: Is there a recommended way to solve this issue?

    Thanks in advance.

    • Rob Camick said

      Interesting issue. Your suggestion seems reasonable.

      Another thought I had is that maybe you need to use the FormScroll.Type.PARENT in this case. Then the code would need to be modified to get the appropriate parent. That is you would need to check if the component has a JTabbedPane as an ancestor using SwingUtilities.getAncestorOfClass(…). If so then use the tabbed pane as the parent otherwise use the default code. Then the code will attempt to place the tabbed pane in the view. if it doesn’t all fit, then the component will take precedence. This way the tabs should always be visible for the first component.

      • James Lee said

        Thanks for the reply.

        You’re right. I forgot to mention that I modified my local copy of the auto-scroll implementation to use Type.PARENT by default, but I haven’t done the modification required to handle the scenario for JTabbedPane ancestors yet.

        Previously I thought it might have a problem with forms that have multiple levels of tabs and inner scroll panes, but now to think of it… I just need to stop at the nearest ancestor that is a JTabbedPane. I’ll try that now.

        Thanks again.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
Follow

Get every new post delivered to your Inbox.

Join 88 other followers

%d bloggers like this: