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);
    }
}
相关推荐
七星静香13 分钟前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
Jacob程序员14 分钟前
java导出word文件(手绘)
java·开发语言·word
ZHOUPUYU14 分钟前
IntelliJ IDEA超详细下载安装教程(附安装包)
java·ide·intellij-idea
stewie618 分钟前
在IDEA中使用Git
java·git
Elaine20239133 分钟前
06 网络编程基础
java·网络
G丶AEOM34 分钟前
分布式——BASE理论
java·分布式·八股
落落鱼201335 分钟前
tp接口 入口文件 500 错误原因
java·开发语言
想要打 Acm 的小周同学呀36 分钟前
LRU缓存算法
java·算法·缓存
镰刀出海39 分钟前
Recyclerview缓存原理
java·开发语言·缓存·recyclerview·android面试
阿伟*rui3 小时前
配置管理,雪崩问题分析,sentinel的使用
java·spring boot·sentinel