Java KeyAdapter vs. Swing Key Bindings? - java

I have a java swing program that I was previously controlling with a the 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. While switching, I have run into some confusing behaviors.
In order to test these systems, I have a simple 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. I am expecting this system to display eventTest as true on a frame where the shift key becomes pressed, and false otherwise. 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:
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. 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. If I hold either shift key down, event test becomes true on the frame when I press the other, and then returns to false. I would like it to do this when one shift key is pressed, without having to hold the other one.
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. 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. What I am confused about is why this behavior would be different for the shift key than for the right arrow. If possible, I would like to find a way to make eventTest true only on the first frame a key is pressed.
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. All that needs to be done is to change the what is added to the InputMap from:
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. 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. Weird.
The problems with other keys have slightly less clean solutions. As to the question of why shift behaves differently than other keys. 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. I.e. 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. However, auto-repeating the shift key in a similar manner is not (in most cases) useful to the user. Therefore, it makes sense that no repeated events for the shift key are generated. 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. 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.
A slightly better solution (IMO) can be achieved using KeyAdapter instead of Key Bindings. 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). 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. 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 InputMaps and ActionMaps, you put them in there. 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();. This allows you to add, remove, and change key bindings by performing the corresponding operation on the map.

Related

In java how do I check if a user is pressing a certain key?

I am trying to make a hotkey program that runs in the background, and I can not figure out how to make it check when a certain key is pressed (In this case the minus key). I need the program to wait for a key to be pressed and then the program checks if that key is the minus key.
An example of my program as is now in pseudo code is somewhere along these lines.
while(true)
{
int pressedKey = userInputedKey;
if(pressedKey == KeyEvent.VK_MINUS)
{
//action to be executed
}
}
I have found a lot of suggestions on other threads to use KeyListener but I might not be using it properly, so if your response is to use KeyListener please explain how to use it thoroughly (to be Exact I don't understand how when a KeyEvent is instantiated it already has the input of whatever key was pressed as the keyCode in that KeyEvent, so if I use it in the call to the KeyListener I will not get the proper results). Granted I do not know much about KeyEvent or KeyListener, so I might have not been attempting at the right way around my problem by using a KeyListener.
You can use the KeyEvent to get the key code, and then check it.
E.g:
someCompunent.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_MINUS) {
// do the stuff
}
}
});
If you want to use global hotkeys, when program catch keypress event in any application - try use JKeyMaster library (supports global hotkweys in Windows, Linux and MAC OS)

Java key bindings with multiple actions

So for reference, I am following Trystan's roguelike tutorial for java.
The tutorial above uses Key Listeners, which does not work for Mac OS X (and appears to be discouraged by the community), so I am trying to change the code to use Key Bindings instead.
Here's the question. In his "roguelike game", the game is divided into screens, where each screen has separate actions for a particular key. For instance, the "Start Screen" sends the player to the "Play screen" upon pressing the enter key:
public class StartScreen implements Screen {
public void displayOutput(AsciiPanel terminal) {
terminal.write("rl tutorial", 1, 1);
terminal.writeCenter("-- press [enter] to start --", 22);
}
public Screen respondToUserInput(KeyEvent key) {
return key.getKeyCode() == KeyEvent.VK_ENTER ? new PlayScreen() : this;
}
}
While the "Play Screen" sends the player to the "Win screen" upon pressing the enter key:
public class PlayScreen implements Screen {
public void displayOutput(AsciiPanel terminal) {
terminal.write("You are having fun.", 1, 1);
terminal.writeCenter("-- press [escape] to lose or [enter] to win --", 22);
}
public Screen respondToUserInput(KeyEvent key) {
switch (key.getKeyCode()){
case KeyEvent.VK_ESCAPE: return new LoseScreen();
case KeyEvent.VK_ENTER: return new WinScreen();
}
return this;
}
}
Now, from what I can see from Key Bindings examples such as this and this, all the bindings and actions seem to be "dumped" into one class. This is unsatisfactory, because:
One keystroke leads to only one action (I suppose I could have the action do a switch case, and check what screen I am in, and then execute some code specific to said screen, but perhaps there is a better way.)
It becomes more difficult to determine what action is from/for what screen.
Would there be a way to get Key Bindings to have one keystroke execute different actions depending on the "Screen"? Would I just use conditionals? Can I put Key Binding logic in other classes (Screens) and then call them in the main class, like what is happening with Key Listeners above?
Key bindings are not necessarily dumped into one class. They are associated with input maps, which in turn are associated with specific JComponents. If you register screen-specific bindings in the WHEN_ANCESTOR_OF_FOCUSED_COMPONENT input map of the JComponent representing that screen, then they will be recognized when any component in that screen has the keyboard focus (supposing a component in between has not overridden the binding with its own), but not when a component in a different screen has the focus.
Overall, the system is very flexible. You might consider reading the Java Tutorial section on this topic to bring yourself up to speed.

KeyTypedEvent KeyEvent's KeyCode is always 0?

I have a Java Swing application in the NetBeans IDE.
I made a form and attached a KeyListener to my various controls as such:
jButton1.addKeyListener(new java.awt.event.KeyAdapter() {
public void keyTyped(java.awt.event.KeyEvent evt) {
keyTypedEvent(evt);
}
});
and keyTypedEvent is defined as such:
private void keyTypedEvent(java.awt.event.KeyEvent evt)
{
System.out.println(evt);
appendDisplay(String.valueOf(evt.getKeyChar()));
}
I added a println to the evt to see what happens and to verify that my keylistener does work.
When I build and run my application, I realized that the output always seems to have a keycode = 0
To verify this, I had changed my println to be evt.getKeyCode() and it is always returning 0.
I could be completely misinterpreting what KeyCode does, but I thought that it would coorespond with the values in Oracle's documentation here:
http://docs.oracle.com/javase/7/docs/api/constant-values.html#java.awt.event.KeyEvent.VK_ESCAPE
For instance, VK_ESCAPE has a value of 27.
The keyTyped() event is only used for keys that produce character input. If you want to know when any key is pressed or released, you need to implement keyPressed() or keyReleased().
From the KeyEvent API:
"Key typed" events are higher-level and generally do not depend on the
platform or keyboard layout. They are generated when a Unicode
character is entered, and are the preferred way to find out about
character input....
For key pressed and key released events, the getKeyCode method returns
the event's keyCode. For key typed events, the getKeyCode method
always returns VK_UNDEFINED.
all suggestion about KeyListener for JButton are wrong, meaning Button1.addKeyListener(new java.awt.event.KeyAdapter() {
these events are implemented and correctly in JButtons API, use SwingAction or add ActionListener for listening Mouse and Key Event from/to JButton
basically everything is described in Oracle tutorial about How to Use Buttons, Check Boxes, and Radio Buttons
It very depends on key that has been pressed. Probably you need KeyListener with keyPressed method override, because keyTyped not triggered on non-printable characters.
Look at the difference between keyTyped and keyPressed here:
KeyListener, keyPressed versus keyTyped

Eliminating Initial keypress delay

When you type into a textbox and hold a key, you get (a.......aaaaaaaaaaaaaaa), depending on the initial key press delay.
addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
// Handle key press here
}
I'm creating a game in which the user's reflexes are very important. How can I eliminate this delay completely? The above code does not work. I have also tried overriding processKeyEvent with no luck.
These events are generated by the JVM / operating system, and unless you instruct the user to change the key-delay / key-repeat settings I'm afraid you'll have to do some more work.
I suggest you create a Timer which fires events in the correct rate, start and stop the timer upon keyPressed / keyReleased.

How do I handle simultaneous key presses in Java?

How do I handle simultaneous key presses in Java?
I'm trying to write a game and need to handle multiple keys presses at once.
When I'm holding a key (let's say to move forward) and then I hold another key (for example, to turn left), the new key is detected but the old pressed key isn't being detected anymore.
One way would be to keep track yourself of what keys are currently down.
When you get a keyPressed event, add the new key to the list; when you get a keyReleased event, remove the key from the list.
Then in your game loop, you can do actions based on what's in the list of keys.
Here is a code sample illustrating how to perform an action when CTRL+Z is pressed:
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
class p4 extends Frame implements KeyListener
{
int i=17;
int j=90;
boolean s1=false;
boolean s2=false;
public p4()
{
Frame f=new Frame("Pad");
f.setSize(400,400);
f.setLayout(null);
Label l=new Label();
l.setBounds(34,34,88,88);
f.add(l);
f.setVisible(true);
f.addKeyListener(this);
}
public static void main(String arg[]){
new p4();
}
public void keyReleased(KeyEvent e) {
//System.out.println("re"+e.getKeyChar());
if(i==e.getKeyCode())
{
s1=false;
}
if(j==e.getKeyCode())
{
s2=false;
}
}
public void keyTyped(KeyEvent e) {
//System.out.println("Ty");
}
/** Handle the key pressed event from the text field. */
public void keyPressed(KeyEvent e) {
System.out.println("pre"+e.getKeyCode());
if(i==e.getKeyCode())
{
s1=true;
}
if(j==e.getKeyCode())
{
s2=true;
}
if(s1==true && s2==true)
{
System.out.println("EXIT NOW");
System.exit(0);
}
}
/** Handle the key released event from the text field. */
}
Generally speaking, what you are describing can be achieved using bitmasks.
Oh, I had this problem too!
I tried making a simple Pong game following a tutorial I found.
But a lot of the time if I was holding one button and pressed another the new keypress would seem to register while the previous seemed to stop registering even if I was still holding the key pressed.
I thought that it was perhaps a limitation with the KeyListener feature so I tried rewriting my program to use Keybindings instead.
But if anything it made it all worse.
So I realized the problem was in the tutorial I was following.
The way it handled the logic after listening to the keys.
So if you are experiencing this type of issues I'd recommend you to troubleshoot the code that you are working with!
Also take note that the behaviour of keys apparently can be different depending on your Operative system.
So in Windows when a key is pressed it will repeat like you know it will in a text editor.
So when a key is pressed it will actually call repeatedly and then when you release it it will give you one release event.
So take note of that!
Also worth noting is that different keyboards will have different limitations on how many keys you can push simultaneously and that number will usually also be different depending on exactly the combination of keys that are being pressed.

Categories

Resources