Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I make JScrollPane scroll to follow input focus?

I have a Swing app with a large panel which is wrapped in a JScrollPane. Users normally move between the panel's subcomponents by tabbing, so when they tab to something out view, I want the scroll pane to autoscroll so the component with input focus is always visible.

I've tried using KeyboardFocusManager to listen for input focus changes, and then calling scrollRectToVisible.

Here's an SSCCE displaying my current strategy (just copy/paste and run!):

import java.awt.KeyboardFocusManager;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;

public class FollowFocus {

  public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {

      public void run() {
        final int ROWS = 100;
        final JPanel content = new JPanel();
        content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS));
        content.add(new JLabel(
          "Thanks for helping out. Use tab to move around."));
        for (int i = 0; i < ROWS; i++) {
          JTextField field = new JTextField("" + i);
          field.setName("field#" + i);
          content.add(field);
        }

        KeyboardFocusManager.getCurrentKeyboardFocusManager()
                            .addPropertyChangeListener("focusOwner", 
                     new PropertyChangeListener() {

          @Override
          public void propertyChange(PropertyChangeEvent evt) {
            if (!(evt.getNewValue() instanceof JComponent)) {
              return;
            }
            JComponent focused = (JComponent) evt.getNewValue();
            if (content.isAncestorOf(focused)) {
              System.out.println("Scrolling to " + focused.getName());
              focused.scrollRectToVisible(focused.getBounds());
            }
          }
        });

        JFrame window = new JFrame("Follow focus");
        window.setContentPane(new JScrollPane(content));
        window.setSize(200, 200);
        window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        window.setVisible(true);
      }
    });
  }
}

If you run this example, you'll notice it doesn't work very well. It does get the focus change notifications, but the call to scrollRectToVisible doesn't appear to have any effect. In my app (which is too complex to show here), scrollRectToVisible works about half the time when I tab into something outside of the viewport.

Is there an established way to solve this problem? If it makes any difference, the Swing app is built on Netbeans RCP (and most of our customers run Windows).

like image 568
gustafc Avatar asked Nov 23 '11 16:11

gustafc


People also ask

What is the difference between a scrollbar and a JScrollPane?

What is the difference between a Scrollbar and a JScrollPane ? A Scrollbar is a Component, but not a Container. A ScrollPane is a Container. A ScrollPane handles its own events and performs its own scrolling.

How do I make my JScrollPane scroll faster?

Just use the reference to your JScrollPane object, get the vertical scroll bar from it using getVerticalScrollBar , and then call setUnitIncrement on it, like this: myJScrollPane. getVerticalScrollBar(). setUnitIncrement(16);


2 Answers

My comment to the other answer:

scrollRectToVisible on the component itself is the whole point of that method ;-) It's passed up the hierarchy until a parent doing the scroll is found

... except when the component itself handles it - as JTextField does: it's implemented to scroll horizontally to make the caret visible. The way out is to call the method on the field's parent.

Edit

just for clarity, the replaced line is

    content.scrollRectToVisible(focused.getBounds());
like image 89
kleopatra Avatar answered Oct 24 '22 10:10

kleopatra


you have to take Rectangle from JPanel and JViewPort too, then compare, for example

notice (against down-voting) for final and nice output required some work for positions in the JViewPort

import java.awt.KeyboardFocusManager;
import java.awt.Rectangle;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;
//http://stackoverflow.com/questions/8245328/how-do-i-make-jscrollpane-scroll-to-follow-input-focus
public class FollowFocus {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            public void run() {
                final int ROWS = 100;
                final JPanel content = new JPanel();
                content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS));
                content.add(new JLabel(
                        "Thanks for helping out. Use tab to move around."));
                for (int i = 0; i < ROWS; i++) {
                    JTextField field = new JTextField("" + i);
                    field.setName("field#" + i);
                    content.add(field);
                }
                final JScrollPane scroll = new JScrollPane(content);
                KeyboardFocusManager.getCurrentKeyboardFocusManager().
                        addPropertyChangeListener("focusOwner", new PropertyChangeListener() {

                    @Override
                    public void propertyChange(PropertyChangeEvent evt) {
                        if (!(evt.getNewValue() instanceof JComponent)) {
                            return;
                        }
                        JViewport viewport = (JViewport) content.getParent();
                        JComponent focused = (JComponent) evt.getNewValue();
                        if (content.isAncestorOf(focused)) {
                            System.out.println("Scrolling to " + focused.getName());
                            Rectangle rect = focused.getBounds();
                            Rectangle r2 = viewport.getVisibleRect();
                            content.scrollRectToVisible(new Rectangle(rect.x, rect.y, (int) r2.getWidth(), (int) r2.getHeight()));
                        }
                    }
                });

                JFrame window = new JFrame("Follow focus");
                window.setContentPane(new JScrollPane(content));
                window.setSize(200, 200);
                window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                window.setVisible(true);
            }
        });
    }
}
like image 37
mKorbel Avatar answered Oct 24 '22 12:10

mKorbel