Swing中如何实现快捷键绑定和修改

在许许多多市面上常见的桌面软件中, 可以使用快捷键操作, 比如像微信一样,使用Alt+A 可以打开截图窗口,如果不习惯于Alt+A按键时,还可以打开设置去修改。

如果在swing中也想实现一个快捷键绑定和修改的操作,那么应该如何操作?

一、实现思路

1.1创建事件Action

创建一个Action 对象,实现actionPerformed方法即可。

// 该事件触发时, 会弹出bindKeyMapButton按钮上绑定的按键值。

java 复制代码
   
  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

java 复制代码
        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来监听键盘事件

当键盘被按下时,记录符合要求的按键值,并显示文本到输入框内。

java 复制代码
   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中设置的新按键值。

java 复制代码
        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

java 复制代码
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

java 复制代码
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代码修改。
java 复制代码
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);
    }
}
相关推荐
GISer小浪花努力上岸2 小时前
Java实现简易计算器功能(idea)
java·开发语言·intellij-idea
海海向前冲2 小时前
设计模式 -- 单例设计模式
java·开发语言·设计模式
就这样很好8802 小时前
排序算法总结
java·算法·排序算法
weixin_486681142 小时前
C++系列-STL中find相关的算法
java·c++·算法
学java的小菜鸟啊3 小时前
Java队列详细解释
java·开发语言·经验分享·python
帅得不敢出门3 小时前
安卓framework美化手势导航侧滑返回UI
android·java·ui·framework·安卓·开发·定制
我是真爱学JAVA3 小时前
第四章 类和对象 课后训练(1)
java·开发语言·算法
可儿·四系桜3 小时前
如何在Linux虚拟机上安装和配置JDK
java·linux·运维
丶白泽3 小时前
重修设计模式-结构型-装饰器模式
java·设计模式·装饰器模式
星空下夜猫子4 小时前
JAVA 使用POI实现单元格行合并生成
java·开发语言