Swing - 带有 TextComponents 的 GUI 中的关键问题(箭头键、Tab 等)

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

我想将箭头键绑定到整个 window 的某些特定操作(无论聚焦哪个组件)。特别是我想用箭头键移动 JList 中的选择栏。我的 window 包含一个 JTextArea 和不同的 JScrollPanes。

我猜想发生了以下问题:当我更改列表选择时,textarea 获得焦点(这与我想要实现的逻辑有关)。当 JTextArea 或 JScrollPane 获得焦点时,所有向上箭头等键事件 'get lost'(或者更确切地说,仅影响 TextComponent/Pane)。

这是一个演示问题的小例子:

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);
}

}

在此示例中,Alt-ArrowDown 命令有效,而普通的 ArrowUp 命令无效。如果我更改 requestFocus() 行,以便标签请求焦点,ArrowUp 也可以工作(因为现在是标签而不是文本区域获得焦点)。

我的问题:如何将方向键等键 'window-wide' 绑定到特定操作(特别是如果我的 window 中有文本组件)。

文本组件的键绑定在获得焦点时优先。因此,您需要使用如下代码从文本组件中删除绑定:

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

有关详细信息,请参阅 How to Make/Remove Bindings 上的 Swing 教程部分。

谢谢@camickr,通过你的提示问题可以解决

...无论如何这里有一些进一步的信息/解决方案:

  1. 禁用整个组件类型的按键操作(如 camickr 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");
    

    这将禁用所有 TextAreaScrollPane 组件的向上箭头和向下箭头键输入(在调用此命令之前必须实例化每种类型的一个对象)

  2. 通过 KeyEventDispatcher:

    禁用整个 window 的按键
    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;
        }
    });
    

    这将执行 'doAction' 命令,分别按下向上箭头键和向下箭头键 'window-wide'。由于方法 returns 在这两种情况下都为真,因此不会将按键事件分派给其他组件,因此示例中的文本区域和滚动窗格禁用了此按键。