How do I scroll a JPanel with keyboard, while other components have focus?

217 Views Asked by At

I've been looking for a solution for this, I think, very common, problem, with Java Swing, but can't find specific answers.

I have this Swing JPanel with a scrollable JPanel on top, showing an image of a document.

Below there is more Swing components, e.g. a JTable and some textboxes, JButton controls etc. in another panel. (There are more panels in the same JWindow.)

When user edits data etc. in the boxes below, these of course gains focus as it should be. Now when the user wants to scroll for some specific data in the document, he/she needs to set focus (with mouse/tab) on this first, and then scroll.

So user would like to use e.g. arrow up/down keyboard without leaving focus from the other component he is working with.

I've tried to set keyboard handlers up, but can not get it to work without involving moving focus to the top.

Isn't there a way to scroll a scrollable Swing component without this component (and another) has focus?

2

There are 2 best solutions below

0
On

If the components are text fields in the scroll pane, it seems Page Down works if the (first, at least) text field is focused. This is the result of running such code and hitting Page Down a few times.

enter image description here

If this behavior is needed while components outside the scroll pane are focused, perhaps look to key bindings.

import java.awt.*;
import javax.swing.*;
import javax.swing.border.EmptyBorder;

public class KeyboardScroll {

    private JComponent ui = null;

    KeyboardScroll() {
        initUI();
    }

    public void initUI() {
        if (ui != null) {
            return;
        }

        ui = new JPanel(new BorderLayout(4, 4));
        ui.setBorder(new EmptyBorder(4, 4, 4, 4));

        JPanel textPanel = new JPanel(new GridLayout(0, 1));
        for (int ii = 1; ii < 101; ii++) {
            textPanel.add(new JTextField("Field " + ii, 30));
        }
        JScrollPane scrollPane = new JScrollPane(textPanel,
                JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
                JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        ui.add(scrollPane);
        Dimension d = textPanel.getPreferredSize();
        scrollPane.getViewport().setPreferredSize(
                new Dimension((int)d.getWidth(),100));
    }

    public JComponent getUI() {
        return ui;
    }

    public static void main(String[] args) {
        Runnable r = new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(
                            UIManager.getSystemLookAndFeelClassName());
                } catch (Exception useDefault) {
                }
                KeyboardScroll o = new KeyboardScroll();

                JFrame f = new JFrame("Use 'Page Down'");
                f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
                f.setLocationByPlatform(true);

                f.setContentPane(o.getUI());
                f.pack();
                f.setMinimumSize(f.getSize());

                f.setVisible(true);
            }
        };
        SwingUtilities.invokeLater(r);
    }
}
0
On

Isn't there a way to scroll a scrollable Swing component without this component (and another) has focus?

Then general solution is to use Key Bindings.

However, the problem is that each component can implement Key Bindings itself for the up/down event, in which case the related Action for that component is invoked.

The tutorial demonstrates how you can remove key bindings from a component.

a JTable and some textboxes

So these are examples of components the do implement up/down key bindings.

JButton controls

Is an example of a component that doesn't have default up/down key bindings.

So you could add your own with code like the following:

import java.awt.*;
import javax.swing.*;
import java.awt.event.*;

public class SSCCE extends JPanel
{
    public SSCCE()
    {
        setLayout( new BorderLayout() );

        JPanel top = new JPanel();
        top.add( new JTextField(10) );
        top.add( new JButton("Button") );
        add(top, BorderLayout.PAGE_START);

        JLabel label = new JLabel( new ImageIcon("mong.jpg") );
        JScrollPane scrollPane = new JScrollPane( label );
        add(scrollPane, BorderLayout.CENTER);

        JScrollBar scrollBar = scrollPane.getVerticalScrollBar();

        InputMap im = getInputMap(JScrollBar.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
        ActionMap am = getActionMap();
        im.put(KeyStroke.getKeyStroke("pressed UP"), "up");
        am.put("up", new AbstractAction()
        {
            public void actionPerformed(ActionEvent e)
            {
                scrollBar.setValue(scrollBar.getValue() - scrollBar.getUnitIncrement(-1));
            }
        });

        im.put(KeyStroke.getKeyStroke("pressed DOWN"), "down");
        am.put("down", new AbstractAction()
        {
            public void actionPerformed(ActionEvent e)
            {
                scrollBar.setValue(scrollBar.getValue() + scrollBar.getUnitIncrement(1));
            }
        });

    }

    private static void createAndShowGUI()
    {
        JFrame frame = new JFrame("SSCCE");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(new SSCCE());
        frame.setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        frame.pack();
        frame.setLocationByPlatform( true );
        frame.setVisible( true );
    }

    public static void main(String[] args) throws Exception
    {
        java.awt.EventQueue.invokeLater( () -> createAndShowGUI() );
    }
}

In the above example the image will scroll when focus is on the button, but not on the text field.