在许许多多市面上常见的桌面软件中, 可以使用快捷键操作, 比如像微信一样,使用Alt+A 可以打开截图窗口,如果不习惯于Alt+A按键时,还可以打开设置去修改。
如果在swing中也想实现一个快捷键绑定和修改的操作,那么应该如何操作?
一、实现思路
1.1创建事件Action
创建一个Action 对象,实现actionPerformed方法即可。
// 该事件触发时, 会弹出bindKeyMapButton按钮上绑定的按键值。
Action actionListener = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
MessageUtil.ok(bindKeyMapButton, "Key:" + SwingCoreUtil.keyStroke2Str((KeyStroke) bindKeyMapButton.getClientProperty("activeKeyStroke")));
}
};
1.2.为按钮绑定默认快捷键
创建一个KeyStroke按键,绑定键盘Ctrl+Q ,那么键盘按下Ctrl+Q时,会触发事件Action
KeyStroke defaultKeyStroke = KeyStroke.getKeyStroke("ctrl Q");
bindKeyMapButton.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(defaultKeyStroke, "active");
bindKeyMapButton.getActionMap().put("active", actionListener);
bindKeyMapButton.putClientProperty("activeKeyStroke", defaultKeyStroke);
1.3. 创建Jtextfield来监听键盘事件
当键盘被按下时,记录符合要求的按键值,并显示文本到输入框内。
keymapText.addKeyListener(new KeyAdapter() {
private KeyStroke keyStroke;
@Override
public void keyPressed(KeyEvent e) {
keyStroke = null;
// 无ctrl/shift/alt 组合键
if (e.getModifiersEx() == 0 || e.getKeyCode() == KeyEvent.VK_UNDEFINED) {
keyStroke = null;
} else {
keyStroke = KeyStroke.getKeyStrokeForEvent(e);
}
}
@Override
public void keyReleased(KeyEvent e) {
if (keyStroke == null) {
return;
}
int keyCode = keyStroke.getKeyCode();
// 禁止某些键
if (keyCode == KeyEvent.VK_CONTROL || keyCode == KeyEvent.VK_SHIFT
|| keyCode == KeyEvent.VK_ALT || keyCode == KeyEvent.VK_ALT_GRAPH
|| keyCode == KeyEvent.VK_SCROLL_LOCK || keyCode == KeyEvent.VK_PRINTSCREEN
) {
return;
}
String keyStrokeText = SwingCoreUtil.keyStroke2Str(keyStroke);
keymapText.setText(keyStrokeText);
keymapText.putClientProperty("bindKeyStroke", keyStroke);
}
});
1.4. 监听Jtextfield文本变化更新button的按键绑定
对button移除按键操作,并绑定Jtextfield中设置的新按键值。
keymapText.getDocument().addDocumentListener(new ChangeDocumentListener() {
@Override
public void update(DocumentEvent e) {
bindKeyMapButton.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).remove((KeyStroke) bindKeyMapButton.getClientProperty("activeKeyStroke"));
KeyStroke keyStroke = (KeyStroke) keymapText.getClientProperty("bindKeyStroke");
if (keyStroke != null) {
bindKeyMapButton.putClientProperty("activeKeyStroke", keyStroke);
bindKeyMapButton.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(keyStroke, "active");
}
}
});
1.5. 完整示例代码
cn.note.swing.core 引用自swing-helper
import cn.note.swing.core.listener.ChangeDocumentListener;
import cn.note.swing.core.util.ButtonFactory;
import cn.note.swing.core.util.FrameUtil;
import cn.note.swing.core.util.MessageUtil;
import cn.note.swing.core.util.SwingCoreUtil;
import cn.note.swing.core.view.AbstractMigView;
import cn.note.swing.core.view.theme.ThemeFlatLaf;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import java.awt.event.ActionEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
/**
* 测试keymap
*
* @author jee
* @version 1.0
*/
public class KeymapV1Test extends AbstractMigView {
private JTextField keymapText;
private JButton bindKeyMapButton;
@Override
protected MigLayout defineMigLayout() {
return new MigLayout("center,nogrid","grow","grow");
}
@Override
protected void init() {
keymapText = new JTextField();
keymapText.setEditable(false);
bindKeyMapButton = ButtonFactory.primaryButton("");
}
@Override
public void bindEvents() {
// 绑定按钮按键
Action actionListener = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
MessageUtil.ok(bindKeyMapButton, "Key:" + SwingCoreUtil.keyStroke2Str((KeyStroke) bindKeyMapButton.getClientProperty("activeKeyStroke")));
}
};
KeyStroke defaultKeyStroke = KeyStroke.getKeyStroke("ctrl Q");
bindKeyMapButton.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(defaultKeyStroke, "active");
bindKeyMapButton.getActionMap().put("active", actionListener);
bindKeyMapButton.putClientProperty("activeKeyStroke", defaultKeyStroke);
bindKeyMapButton.setText("Init BindKey:" + SwingCoreUtil.keyStroke2Str(defaultKeyStroke));
// 监听按键更改
keymapText.addKeyListener(new KeyAdapter() {
private KeyStroke keyStroke;
@Override
public void keyPressed(KeyEvent e) {
keyStroke = null;
// 无ctrl/shift/alt 组合键
if (e.getModifiersEx() == 0 || e.getKeyCode() == KeyEvent.VK_UNDEFINED) {
keyStroke = null;
} else {
keyStroke = KeyStroke.getKeyStrokeForEvent(e);
}
}
@Override
public void keyReleased(KeyEvent e) {
if (keyStroke == null) {
return;
}
int keyCode = keyStroke.getKeyCode();
// 禁止某些键
if (keyCode == KeyEvent.VK_CONTROL || keyCode == KeyEvent.VK_SHIFT
|| keyCode == KeyEvent.VK_ALT || keyCode == KeyEvent.VK_ALT_GRAPH
|| keyCode == KeyEvent.VK_SCROLL_LOCK || keyCode == KeyEvent.VK_PRINTSCREEN
) {
return;
}
String keyStrokeText = SwingCoreUtil.keyStroke2Str(keyStroke);
keymapText.setText(keyStrokeText);
keymapText.putClientProperty("bindKeyStroke", keyStroke);
}
});
keymapText.getDocument().addDocumentListener(new ChangeDocumentListener() {
@Override
public void update(DocumentEvent e) {
bindKeyMapButton.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).remove((KeyStroke) bindKeyMapButton.getClientProperty("activeKeyStroke"));
KeyStroke keyStroke = (KeyStroke) keymapText.getClientProperty("bindKeyStroke");
if (keyStroke != null) {
bindKeyMapButton.putClientProperty("activeKeyStroke", keyStroke);
bindKeyMapButton.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(keyStroke, "active");
}
}
});
}
@Override
protected void render() {
view.add(new JLabel("keymap V1:"));
view.add(keymapText, "w 200!");
view.add(bindKeyMapButton, "wrap");
}
public static void main(String[] args) {
ThemeFlatLaf.install();
FrameUtil.launchTest(KeymapV1Test.class);
}
二、拓展
但是,就像上述操作一样,如果页面上只有一个按键绑定还好,如果有多个按键呢 ? 每一次更改都需要去实现KeyListener 和DocumentListener , 所以为什么大家比较认可java的核心概念:抽象、封装、继承、多态 呢?
- 继承
普通输入框既然不能满足了, 那么扩展一个可以支持按键的输入框,但是还想具备原来输入框的一些功能,
那么按键输入框只需要继承普通输入框即可。 - 封装与抽象
在第4步中输入框文本变化时,需要与绑定的按钮进行交互,耦合太大了,那么如何降低耦合呢? 可以让按键输入框释放一个事件操作,该事件操作与按钮建立绑定关系,那么这一步就是封装与抽象了。
如何更优雅的封装与抽象,取决于代码的汇总量和阅读优秀代码的眼界。
为什么经常想重构代码,觉得代码比较粗糙不堪呢?可能跟你的代码量上去了,阅读和抒写了更优秀的代码,所以大牛看小白的代码就是垃圾,小白看大牛的代码就是神作。
所以,如果你是过了这境界的大牛,还望忽略前进中的小牛。 - 多态
当确定了要创建一个按键输入框时,可能随着时间的推移或者功能上的扩展,造成它不够健壮了,但是它还占用了你喜欢的名字, 那么多态就是一个很好的解决方案了。比如: 在1.0的是否创建了一个KeymapTextField,2.0的时候创建一个增强版本ExtKeymapTextField
KeymapTextField keymapTextField=new KeymapTextField(); // V1.0
keymapTextField=new ExtKeymapTextField(); //V2.0
2.1 封装键盘输入框
JEKeymapTextField 内置封装了不可编辑 、禁止选择、监听键盘按键回显 功能,对外暴露了setKeyStrokeChangedListener 让外界可以感知实时变化的KeyStroke
import cn.note.swing.core.listener.ChangeDocumentListener;
import cn.note.swing.core.util.SwingCoreUtil;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.function.Consumer;
/**
* 快捷键绑定文本框
*
* @author jee
* @version 1.0
*/
public class JEKeymapTextField extends JTextField implements KeyListener {
/* 绑定按键*/
private KeyStroke keyStroke;
/* 快捷键改变监听*/
private Consumer<KeyStroke> keyStrokeChangedListener;
public JEKeymapTextField() {
super.setEditable(false);
// 绑定按键
super.addKeyListener(this);
bindEvents();
}
@Override
public void moveCaretPosition(int pos) {
//nothing 禁止光标移动,即不可选择
}
private void bindEvents() {
// 文本框内容改变时触发
this.getDocument().addDocumentListener(new ChangeDocumentListener() {
@Override
public void update(DocumentEvent e) {
if (keyStrokeChangedListener != null) {
keyStrokeChangedListener.accept(keyStroke);
}
}
});
}
@Override
public void keyTyped(KeyEvent e) {
//nothing
}
@Override
public void keyPressed(KeyEvent e) {
// 无ctrl/shift/alt 组合键时不绑定
if (e.getModifiersEx() == 0 || e.getKeyCode() == KeyEvent.VK_UNDEFINED) {
keyStroke = null;
} else {
keyStroke = KeyStroke.getKeyStrokeForEvent(e);
}
}
@Override
public void keyReleased(KeyEvent e) {
if (keyStroke == null) {
return;
}
int keyCode = keyStroke.getKeyCode();
// 禁止某些键
if (keyCode == KeyEvent.VK_CONTROL || keyCode == KeyEvent.VK_SHIFT
|| keyCode == KeyEvent.VK_ALT || keyCode == KeyEvent.VK_ALT_GRAPH
|| keyCode == KeyEvent.VK_SCROLL_LOCK || keyCode == KeyEvent.VK_PRINTSCREEN
) {
return;
}
String keyStrokeText = SwingCoreUtil.keyStroke2Str(keyStroke);
super.setText(keyStrokeText);
}
public void setKeyStrokeChangedListener(Consumer<KeyStroke> keyStrokeChangedListener) {
this.keyStrokeChangedListener = keyStrokeChangedListener;
}
}
2.1 封装后测试示例
- 代码简洁
- 耦合性更低
- 逻辑更清楚
如果对JEKeymapTextField 进行拓展增强 (非破坏性对外方法参数变更时)则不会影响KeymapV2Test代码修改。
import cn.note.swing.core.util.ButtonFactory;
import cn.note.swing.core.util.FrameUtil;
import cn.note.swing.core.util.MessageUtil;
import cn.note.swing.core.util.SwingCoreUtil;
import cn.note.swing.core.view.AbstractMigView;
import cn.note.swing.core.view.theme.ThemeFlatLaf;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
import java.awt.event.ActionEvent;
/**
* 测试keymap
*
* @author jee
* @version 2.0
*/
public class KeymapV2Test extends AbstractMigView {
private JEKeymapTextField keymapText;
private JButton bindKeyMapButton;
@Override
protected MigLayout defineMigLayout() {
return new MigLayout("center,nogrid","grow","grow");
}
@Override
protected void init() {
keymapText = new JEKeymapTextField();
bindKeyMapButton = ButtonFactory.primaryButton("");
}
@Override
public void bindEvents() {
// 绑定事件按钮
Action actionListener = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
MessageUtil.ok(bindKeyMapButton, "Key:" + SwingCoreUtil.keyStroke2Str((KeyStroke) bindKeyMapButton.getClientProperty("activeKeyStroke")));
}
};
KeyStroke defaultKeyStroke = KeyStroke.getKeyStroke("ctrl pressed Q");
bindKeyMapButton.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(defaultKeyStroke, "active");
bindKeyMapButton.getActionMap().put("active", actionListener);
bindKeyMapButton.putClientProperty("activeKeyStroke", defaultKeyStroke);
bindKeyMapButton.setText("Init BindKey:" + SwingCoreUtil.keyStroke2Str(defaultKeyStroke));
// 监听按键更改
keymapText.setKeyStrokeChangedListener(keyStroke -> {
bindKeyMapButton.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).remove((KeyStroke) bindKeyMapButton.getClientProperty("activeKeyStroke"));
bindKeyMapButton.putClientProperty("activeKeyStroke", keyStroke);
bindKeyMapButton.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(keyStroke, "active");
});
}
@Override
protected void render() {
view.add(new JLabel("keymap V2:"));
view.add(keymapText, "w 200!");
view.add(bindKeyMapButton, "wrap");
}
public static void main(String[] args) {
ThemeFlatLaf.install();
FrameUtil.launchTest(KeymapV2Test.class);
}
}