简体   繁体   中英

Swing - Key Problems (Arrow Keys, Tab, etc.) in GUIs with TextComponents

I want to bind the arrow keys to some specific actions for the whole window (no matter which component is focused). In particular I want to move the selection bar in a JList with the arrow keys. My window contains a JTextArea and different JScrollPanes.

I guess that following problem occurs: When I change the list selection the textarea gains focus (which lies withing the logic which I want to realize). When the JTextArea or the JScrollPane have focus all arrow-up, etc. key events 'get lost' (or rather only affect the TextComponent/Pane).

Here is a little example which demonstrates the problem:

import java.awt.BorderLayout;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.swing.AbstractAction;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.KeyStroke;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingUtilities;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

public class KeyProblemExample extends JFrame implements ListSelectionListener {

private JList<Integer> list;
private JTextArea textArea;
private JLabel label;

public KeyProblemExample() 
{
    Font font = new Font("Dialog", Font.PLAIN, 20);
    Integer[] listValues = {1, 2, 3};
    list = new JList<>(listValues);
    list.setFixedCellWidth(50);
    list.setFont(font);
    textArea = new JTextArea();
    textArea.setEditable(false);
    textArea.setLineWrap(true);
    String text = Stream.generate(()-> "xyz").limit(300).collect(Collectors.joining());
    textArea.setText(text);
    textArea.setFont(font);
    label = new JLabel("bla bla bla");
    label.setFont(font);
}


private void buildLogic() 
{
    //list selection listener
    list.addListSelectionListener(this);
    //up and down keys
    AbstractAction down = new AbstractAction() {    
        @Override
        public void actionPerformed(ActionEvent e) {
            if (list.getSelectedIndex() >= 0 && list.getSelectedIndex() < list.getModel().getSize() - 1)
                list.setSelectedIndex(list.getSelectedIndex() + 1);
        }
    };
    AbstractAction up = new AbstractAction() {  
        @Override
        public void actionPerformed(ActionEvent e) {
            if (list.getSelectedIndex() >= 1 && list.getSelectedIndex() < list.getModel().getSize())
                list.setSelectedIndex(list.getSelectedIndex() - 1);
        }
    };
    KeyStroke keyDown = KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, KeyEvent.ALT_MASK);
    label.getActionMap().put("indexDown", down);
    label.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(keyDown, "indexDown"); 
    KeyStroke keyUp = KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0);
    label.getActionMap().put("indexUp", up);
    label.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(keyUp, "indexUp");
}


private void displayGUI() 
{
    this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);       
    JPanel panel = new JPanel(new BorderLayout());
    JScrollPane sp1 = new JScrollPane();
    sp1.setViewportView(textArea);
    sp1.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
    sp1.setWheelScrollingEnabled(true);
    JScrollPane sp2 = new JScrollPane();
    sp2.setViewportView(list);
    sp2.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
    sp2.setWheelScrollingEnabled(true);
    panel.add(sp1, BorderLayout.CENTER);
    panel.add(sp2, BorderLayout.EAST);
    panel.add(label, BorderLayout.SOUTH);
    this.getContentPane().add(panel);      
    this.pack();
    this.setSize(400,400);
    this.setLocationByPlatform(true);
    this.setVisible(true);
}


@Override
public void valueChanged(ListSelectionEvent e) {
    textArea.requestFocus();
//  label.requestFocus();
}


public static void main(String[] args) {
    Runnable r = new Runnable() {
        @Override
        public void run() {
            KeyProblemExample x = new KeyProblemExample();
            x.buildLogic();
            x.displayGUI();
        }
    };
    SwingUtilities.invokeLater(r);
}

}

In this example the Alt-ArrowDown command works, whereas the plain ArrowUp doesn't work. If I change the requestFocus() line, so that the label requests focus, ArrowUp also works (since now the label and not the textarea gains focus).

My question: What can I do to bind keys like arrow-keys, etc., 'window-wide' to specific actions (especially if I have text components in my window).

The key bindings for the text components take precedence when they have focus. So you need to remove the binding from the text components with code like:

textComponent.getInputMap().put(KeyStroke.getKeyStroke("UP"), "none");

See the section from the Swing tutorial on How to Make/Remove Bindings for more information.

thank you @ camickr, the problem could be solved by your hints.

... anyway here are some further informations / solutions:

  1. Disable key-actions for a whole component type (like described in camickr's link ):

     InputMap textAreaInputMap = (InputMap)UIManager.get("TextArea.focusInputMap"); InputMap scrollPaneInputMap = (InputMap)UIManager.get("ScrollPane.ancestorInputMap"); textAreaInputMap.put(KeyStroke.getKeyStroke("UP"), "none"); textAreaInputMap.put(KeyStroke.getKeyStroke("DOWN"), "none"); scrollPaneInputMap.put(KeyStroke.getKeyStroke("UP"), "none"); scrollPaneInputMap.put(KeyStroke.getKeyStroke("DOWN"), "none"); 

    This disables up-arrow and down-arrow key inputs for all TextArea and ScrollPane components (one object of each type has to be instantiated before calling this commands)

  2. Disable keys for the whole window via KeyEventDispatcher :

     KeyboardFocusManager manager = KeyboardFocusManager.getCurrentKeyboardFocusManager(); manager.addKeyEventDispatcher(new KeyEventDispatcher() { @Override public boolean dispatchKeyEvent(KeyEvent e) { if (e.getID() == KeyEvent.KEY_PRESSED) { int keyCode = e.getKeyCode(); if (keyCode == KeyEvent.VK_DOWN) { //doAction return true; } else if (keyCode == KeyEvent.VK_UP) { //doAction return true; } } return false; } }); 

    This executes the 'doAction' command for up-arrow respectively down-arrow key pressed 'window-wide'. Since the method returns true in both cases the key-event will not be dispatched to other components, so this keys get disabled for the textarea and the scrollpane in the example.

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