简体   繁体   English

Java KeyAdapter 与 Swing 键绑定?

[英]Java KeyAdapter vs. Swing Key Bindings?

I have a java swing program that I was previously controlling with a the KeyAdapter class.我有一个 java swing 程序,我之前使用KeyAdapter class 进行控制。 For several reasons, I have decided to switch over to using swing's built in key binding system (using InputMap and ActionMap ) instead.出于几个原因,我决定改用 swing 的内置键绑定系统(使用InputMapActionMap )。 While switching, I have run into some confusing behaviors.在切换时,我遇到了一些令人困惑的行为。

In order to test these systems, I have a simple JPanel:为了测试这些系统,我有一个简单的 JPanel:

public class Board extends JPanel {

    private final int WIDTH = 500;
    private final int HEIGHT = 500;
    
    private boolean eventTest = false;

    public Board() {
        initBoard();
        initKeyBindings();
    }

    // initialization
    // -----------------------------------------------------------------------------------------
    private void initBoard() {
        setPreferredSize(new Dimension(WIDTH, HEIGHT));
        setFocusable(true);
    }

    private void initKeyBindings() {

        getInputMap().put((KeyStroke.getKeyStroke(KeyEvent.VK_SHIFT, 0), "Shift Pressed");

        getActionMap().put("Shift Pressed", new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
                eventTest = true;
            }
        });

    }

    // drawing
    // -----------------------------------------------------------------------------------------
    @Override
    protected void paintComponent(Graphics g) {
        // paint background
        super.paintComponent(g);

        g.setColor(Color.black);
        g.drawString("Test: " + eventTest, 10, 10);
        eventTest = false;
    }

Also in my program, I have a loop calling the repaint() method 10 times per second, so that I can see eventTest get updated.同样在我的程序中,我有一个循环调用repaint()方法每秒 10 次,以便我可以看到 eventTest 得到更新。 I am expecting this system to display eventTest as true on a frame where the shift key becomes pressed, and false otherwise.我希望该系统在按下 shift 键的帧eventTest显示为 true,否则为 false。 I also have tested other keys by changing the relevant key codes.我还通过更改相关键码测试了其他键。

When I want to test the KeyAdapter, I add this block to the initBoard() method, and comment out initKeyBindings() in the constructor:当我想测试 KeyAdapter 时,我将这个块添加到initBoard()方法中,并在构造函数中注释掉initKeyBindings()

this.addKeyListener(new KeyAdapter() {
    @Override
    public void keyPressed(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_SHIFT) {
            eventTest = true;
        }
    }
});

When using the KeyAdapter class, this works as expected.使用KeyAdapter class 时,它按预期工作。 However, when I switch over to using key bindings, it becomes confusing.但是,当我切换到使用键绑定时,它变得令人困惑。 For some reason, eventTest is only displayed as true when I press down both shift keys.出于某种原因, eventTest仅在我按下两个 shift 键时才显示为 true。 If I hold either shift key down, event test becomes true on the frame when I press the other, and then returns to false.如果我按住任一 shift 键,当我按下另一个时,事件测试在框架上变为 true,然后返回 false。 I would like it to do this when one shift key is pressed, without having to hold the other one.我希望它在按下一个 shift 键时执行此操作,而不必按住另一个。

Additionally, when I set it to trigger on right arrow presses instead, a slightly different behavior happens.此外,当我将其设置为在右箭头按下时触发时,会发生稍微不同的行为。 In both the KeyAdapter and key bindings modes, what happens is that eventTest becomes true on the frame I press the right arrow, returns to false for a short time, and then becomes true for as long as I hold the arrow.KeyAdapter和键绑定模式中,发生的情况是 eventTest 在我按下右箭头的帧上变为真,然后在短时间内返回假,然后只要我按住箭头就变为真。 From reading the documentation online, it appears that this is caused by an OS dependent behavior (I am running Ubuntu 18.04) to continue sending out KeyPressed events while a key is held down.从在线阅读文档来看,这似乎是由依赖于操作系统的行为(我正在运行 Ubuntu 18.04)在按住键时继续发送KeyPressed事件引起的。 What I am confused about is why this behavior would be different for the shift key than for the right arrow.我感到困惑的是,为什么 shift 键的这种行为与右箭头的行为不同。 If possible, I would like to find a way to make eventTest true only on the first frame a key is pressed.如果可能的话,我想找到一种方法使eventTest仅在按下键的第一帧上为真。

Any ideas as to what is causing this?关于造成这种情况的任何想法? Thanks!谢谢!

I have found at least a partial answer.我至少找到了部分答案。

For the issue where I had to hold down both shift keys to generate a key pressed event when using key bindings, there is a simple fix.对于在使用键绑定时我必须同时按住两个 shift 键来生成按键事件的问题,有一个简单的修复方法。 All that needs to be done is to change the what is added to the InputMap from:所需要做的就是将添加到InputMap的内容从:

getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_SHIFT, 0), "pressed");

to

getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_SHIFT, KeyEvent.SHIFT_DOWN_MASK), "pressed");

I am not completely sure why the input map counts pressing a single shift key as as KeyEvent with a key code of VK_SHIFT AND the SHIFT_DOWN_MASK , but that appears to be what it is doing.我不完全确定为什么输入 map 将按下单个 shift 键视为KeyEvent ,键码为VK_SHIFTSHIFT_DOWN_MASK ,但这似乎就是它正在做的事情。 It would make more intuitive sense to me if the mask was applied only for if there is already one shift key pressed and the user attempts to press the other one, but interestingly, this binding no longer detects events for if one shift key is held and the other is pressed.如果仅在已经按下一个 shift 键并且用户尝试按下另一个 shift 键的情况下应用掩码,这对我来说会更直观,但有趣的是,如果按住一个 shift 键并且此绑定不再检测事件并且另一个被按下。 Weird.诡异的。

The problems with other keys have slightly less clean solutions.其他键的问题有稍微不太干净的解决方案。 As to the question of why shift behaves differently than other keys.至于为什么 shift 的行为与其他键不同的问题。 I believe this is an intentional design built into the OS.我相信这是操作系统内置的有意设计。 For example, if the user presses and holds the right arrow (or many other keys, such as the every text character key), it is reasonable to assume that they want to repeat the action that is tied to that key.例如,如果用户按住右箭头(或许多其他键,例如每个文本字符键),则可以合理地假设他们想要重复与该键相关的操作。 Ie if a user is typing, and presses and holds "a", they likely want to input multiple "a" characters in quick succession into the text document.即,如果用户正在打字,并且按住“a”,他们可能想要快速连续地将多个“a”字符输入到文本文档中。 However, auto-repeating the shift key in a similar manner is not (in most cases) useful to the user.但是,以类似方式自动重复 shift 键(在大多数情况下)对用户没有用处。 Therefore, it makes sense that no repeated events for the shift key are generated.因此,没有为 shift 键生成重复事件是有道理的。 I don't have any sources to back this up, it is just a hypothesis, but it makes sense to me.我没有任何来源支持这一点,这只是一个假设,但对我来说很有意义。

In order to remove these extra events, there doesn't seem to be a good solution.为了消除这些额外的事件,似乎没有一个好的解决方案。 One thing that works, but is sloppy, is to store a list of all keys currently pressed, and then have your action map check if the key is pressed before executing its action.一件有效但草率的事情是存储当前按下的所有键的列表,然后让您的操作 map 在执行其操作之前检查该键是否被按下。 Another approach would be to use timers and ignore events that occur to close in time to one another (see this post for more details).另一种方法是使用计时器并忽略发生的事件以彼此及时关闭(有关更多详细信息,请参阅这篇文章)。 Both of these implementations require more memory usage and code for every key you wish to track, so they are not ideal.这两种实现都需要更多的 memory 使用和您希望跟踪的每个键的代码,因此它们并不理想。

A slightly better solution (IMO) can be achieved using KeyAdapter instead of Key Bindings.使用KeyAdapter而不是 Key Bindings 可以实现稍微更好的解决方案 (IMO)。 The key to this solution lies in the fact that pressing down one key while another is held will interrupt the stream of auto-repeat events, and it will not resume again for the original key (even if the second key is released).该解决方案的关键在于按住一个键的同时按下一个键会中断自动重复事件的stream,并且不会再次恢复原键(即使释放第二个键)。 Because of this, we really only have to track the last key pressed in order to accurately filter out all auto-repeat events, because that is the only key that could be sending those events.正因为如此,我们实际上只需要跟踪最后一次按下的键,以便准确过滤掉所有自动重复事件,因为这是唯一可以发送这些事件的键。

The code would look something like this:代码看起来像这样:

addKeyListener(new KeyAdapter() {
    @Override
    public void keyPressed(KeyEvent e) {
        int keyCode = e.getKeyCode();
        
        if (keyCode != lastKeyPressed && keyCode != KeyEvent.VK_UNDEFINED) {
            // do some action
            lastKeyPressed = keyCode;
        }
    }

    @Override
    public void keyReleased(KeyEvent e) {
        // do some action
        lastKeyPressed = -1; // indicates that it is not possible for any key 
                             // to send auto-repeat events currently
    }
});

This solution of course looses some of the flexibility provided by swing's key binding system, but that has an easier workaround.这种解决方案当然会失去 Swing 的键绑定系统提供的一些灵活性,但它有一个更简单的解决方法。 You can create your own map of int to Action (or really any other type that is convenient to describe what you want to do), and instead of adding key bindings to InputMap s and ActionMap s, you put them in there.您可以创建自己的 map 的intAction (或者实际上任何其他方便描述您想要做什么的类型),而不是向InputMapActionMap添加键绑定,而是将它们放在那里。 Next, instead of putting the direct code for the action you want to do inside of the KeyAdapter , put something like myMap.get(e.getKeyCode()).actionPerformed();接下来,不要将您想要执行的操作的直接代码放在KeyAdapter中,而是放置类似myMap.get(e.getKeyCode()).actionPerformed();的内容。 . . This allows you to add, remove, and change key bindings by performing the corresponding operation on the map.这允许您通过在 map 上执行相应的操作来添加、删除和更改键绑定。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM