Comment faire défiler JScrollPane pour suivre le focus de saisie?

J’ai une application Swing avec un grand panneau qui est emballé dans un JScrollPane . Les utilisateurs se déplacent normalement entre les sous-composants du panneau en effectuant une tabulation. Ainsi, lorsqu’ils souhaitent passer en mode hors-vue, je souhaite que le volet de défilement défile automatiquement afin que le composant avec le focus d’entrée soit toujours visible.

J’ai essayé d’utiliser KeyboardFocusManager pour écouter les changements de focus d’entrée, puis d’appeler scrollRectToVisible .

Voici un SSCCE affichant ma stratégie actuelle (il suffit de copier / coller et de lancer!):

 import java.awt.KeyboardFocusManager; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import javax.swing.*; public class FollowFocus { public static void main(Ssortingng[] 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); } }); } } 

Si vous utilisez cet exemple, vous remarquerez que cela ne fonctionne pas très bien. Il reçoit les notifications de changement de focus, mais l’appel à scrollRectToVisible ne semble pas avoir d’effet. Dans mon application (qui est trop complexe pour être montrée ici), scrollRectToVisible fonctionne environ la moitié du temps lorsque je mets un élément en dehors de la fenêtre d’affichage.

Existe-t-il un moyen établi de résoudre ce problème? Si cela fait une différence, l’application Swing est construite sur Netbeans RCP (et la plupart de nos clients utilisent Windows).

Mon commentaire à l’autre réponse:

scrollRectToVisible sur le composant lui-même est le but de cette méthode 😉 Elle est passée dans la hiérarchie jusqu’à ce qu’un parent faisant le défilement soit trouvé

… sauf lorsque le composant le gère lui-même – comme JTextField: il est implémenté pour faire défiler horizontalement le curseur d’insertion. La solution consiste à appeler la méthode sur le parent du champ.

modifier

juste pour la clarté, la ligne remplacée est

  content.scrollRectToVisible(focused.getBounds()); 

vous devez également prendre Rectangle de JPanel et JViewPort , puis comparez, par exemple

avis (contre vote JViewPort ) pour la sortie finale et sympa nécessitait du travail pour les postes dans 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(Ssortingng[] 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); } }); } } 

Un problème majeur dans votre code est:

 focused.scrollRectToVisible(focused.getBounds()); 

Vous appelez scrollRectToVisible sur le composant lui-même! Probablement une faute de frappe. Faites de votre JScrollPane une dernière variable et appelez

 scrollPane.getViewport().scrollRectToVisible(focused.getBounds()); 

Ici jtextbox est le composant sur lequel vous voulez vous concentrer et jscrollpane est votre scrollpane:

 jScrollpane.getVerticalScrollBar().setValue(jtextbox.getLocation().x);