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,通过你的提示问题可以解决
...无论如何这里有一些进一步的信息/解决方案:
禁用整个组件类型的按键操作(如 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");
这将禁用所有 TextArea
和 ScrollPane
组件的向上箭头和向下箭头键输入(在调用此命令之前必须实例化每种类型的一个对象)
通过 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 在这两种情况下都为真,因此不会将按键事件分派给其他组件,因此示例中的文本区域和滚动窗格禁用了此按键。
我想将箭头键绑定到整个 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,通过你的提示问题可以解决
...无论如何这里有一些进一步的信息/解决方案:
禁用整个组件类型的按键操作(如 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");
这将禁用所有
TextArea
和ScrollPane
组件的向上箭头和向下箭头键输入(在调用此命令之前必须实例化每种类型的一个对象)通过
禁用整个 window 的按键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; } });
这将执行 'doAction' 命令,分别按下向上箭头键和向下箭头键 'window-wide'。由于方法 returns 在这两种情况下都为真,因此不会将按键事件分派给其他组件,因此示例中的文本区域和滚动窗格禁用了此按键。