简体   繁体   中英

Scroll to focused textfield

So,i have a bunch of textfields inside a scrollpane. More textfields are added when the user focuses the last one from the bottom. My question is,how can i make the scrollpane scroll to the focused textfield? I mean,the user will press TAB or ENTER to jump to the next textfield but he won't be able to see it without scrolling himself. I could simulate pressing the down arrow or PageDown when the last textfield has focus but that would be ugly,even if it would do what i need to.

I tried something like this from what i've found thru searching but i couldn't make it work.

public void focusGained(FocusEvent e) {
            currentview = t1;
            int cons = i - 1;
             Rectangle r = new Rectangle(t1.getX(), t1.getY(), 1, 1);
            jScrollPane1.scrollRectToVisible(r);
            if (t1.getName().equals("prod" + cons)) {

                newproduct();
            };

        }

Two things arise.

Firstly, JTextField#scrollRectToVisible has being overridden and does something slightly different from other components...annoying....

Secondly, you need to be converting the fields location relative to the it's parent and then using the parent's scrollRectToVisible method, for example...

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Rectangle;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class ScrollFocusedField {

    public static void main(String[] args) {
        new ScrollFocusedField();
    }

    public ScrollFocusedField() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                FocusAdapter fh = new FocusAdapter() {

                    @Override
                    public void focusGained(FocusEvent e) {
                        JComponent comp = (JComponent) e.getComponent();
                        System.out.println("FocusGained");
                        Rectangle bounds = comp.getBounds();
                        bounds.setLocation(SwingUtilities.convertPoint(comp, bounds.getLocation(), comp.getParent()));

                        JComponent parent = (JComponent) comp.getParent();
                        parent.scrollRectToVisible(comp.getBounds());
                    }

                };

                JPanel panel = new JPanel(new GridBagLayout());
                GridBagConstraints gbc = new GridBagConstraints();
                for (int index = 0; index < 100; index++) {
                    gbc.fill = GridBagConstraints.NONE;
                    gbc.weightx = 0;
                    gbc.gridx = 0;
                    gbc.gridy = index;
                    panel.add(new JLabel(Integer.toString(index)), gbc);

                    JTextField field = new JTextField();
                    field.addFocusListener(fh);
                    gbc.fill = GridBagConstraints.HORIZONTAL;
                    gbc.weightx = 1;
                    gbc.gridx = 1;
                    panel.add(field, gbc);
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new JScrollPane(panel));
                frame.setSize(200, 400);
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

}

Check out the FormScroller . It is a simple class you can use to make sure the component will be visible when you tab to the component. The FormScroller can be used with any scrollpane and there is no need to add FocusListeners to every component on the form.

It provides a few options that allows you do control exactly how the scrolling works.

I solved this with a custom focus traversal policy. It wraps the container's original traversal policy and queues scroll requests when the next or previous component is requested:

private static class FocusTraversalPolicyAdapter extends FocusTraversalPolicy {
    FocusTraversalPolicy focusTraversalPolicy;

    FocusTraversalPolicyAdapter(FocusTraversalPolicy focusTraversalPolicy) {
        this.focusTraversalPolicy = focusTraversalPolicy;
    }

    @Override
    public Component getComponentAfter(Container container, Component component) {
        var nextComponent = focusTraversalPolicy.getComponentAfter(container, component);

        scrollToVisible(nextComponent);

        return nextComponent;
    }

    @Override
    public Component getComponentBefore(Container container, Component component) {
        var previousComponent = focusTraversalPolicy.getComponentBefore(container, component);

        scrollToVisible(previousComponent);

        return previousComponent;
    }

    @Override
    public Component getFirstComponent(Container container) {
        return focusTraversalPolicy.getFirstComponent(container);
    }

    @Override
    public Component getLastComponent(Container container) {
        return focusTraversalPolicy.getLastComponent(container);
    }

    @Override
    public Component getDefaultComponent(Container container) {
        return focusTraversalPolicy.getDefaultComponent(container);
    }

    void scrollToVisible(Component component) {
        var parent = component.getParent();

        if (parent instanceof JComponent) {
            SwingUtilities.invokeLater(() -> {
                ((JComponent)parent).scrollRectToVisible(SwingUtilities.convertRectangle(parent, component.getBounds(), parent));
            });
        }
    }
}

Note that this does rely on existing behavior and could theoretically break in the future.

Following up on my previous answer, a simpler and possibly more reliable approach would be to define a custom keyboard focus handler. Any time a component receives the input focus, the manager dispatches the event and scrolls the component into view:

public class ScrollingKeyboardFocusManager extends DefaultKeyboardFocusManager {
    @Override
    public boolean dispatchEvent(AWTEvent event) {
        var dispatched = super.dispatchEvent(event);

        if (dispatched && event.getID() == FocusEvent.FOCUS_GAINED) {
            var component = (Component)event.getSource();
            var parent = component.getParent();

            if (parent instanceof JComponent) {
                ((JComponent)parent).scrollRectToVisible(SwingUtilities.convertRectangle(parent, component.getBounds(), parent));
            }
        }

        return dispatched;
    }
}

The focus manager can be installed as follows:

KeyboardFocusManager.setCurrentKeyboardFocusManager(new ScrollingKeyboardFocusManager());

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM