Java key bindings with multiple actions - java

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.

Related

Java KeyAdapter vs. Swing Key Bindings?

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.

Java can't get KeyListener to work in my GUI

I am making a sort of remote control program for Lego EV3 robot. that part is irrelevant. So i made a GUI and i want to control the robot when i press keys. I understand that i have to use something called KeyListener and i even saw a tutorial which is supposed to work.
The GUI class code is right here. Kinda long but it has KeyPressed event at the end.
http://pastebin.com/QK639BDs
I am not sure what i am doing wrong but the program doesn't detect if any key is pressed at all. Any.
I would really appreciate any help on how to make that work.
EDIT:
keyManager=KeyboardFocusManager.getCurrentKeyboardFocusManager();
keyManager.addKeyEventDispatcher(new KeyEventDispatcher() {
// UP:38 DOWN:40 LEFT:37 RIGHT:39
public boolean dispatchKeyEvent(KeyEvent e) {
if(e.getID()==KeyEvent.KEY_PRESSED && e.getKeyCode()==38){
System.out.println("UP");
return true;
}
if(e.getID()==KeyEvent.KEY_RELEASED&& e.getKeyCode()==38){
System.out.println("RELEASED");
return true;
}
return false;
}
});
So i browsed around and i found KeyboardFocusManger which is sort of working for me. I am testing it with a println. I am having only one problem. While i hold down the UP key i want it to only print UP once. Because the UP key will basically start the motor and it will keep moving until the key release will stop it.
Any ideas on how to do that?
KeyListener has issues, particularly with focus, so when any of your text fields are focused, your KeyListener won't respond, for example.
A better solution is to make use of the key bindings API, which allows you to control the level of focus required in order to trigger the key event. In combination with the Action API, you can define common actions for both your keys and buttons, for example.
Take a look at How to Use Key Bindings and How to Use Actions for more details.
Ps- I'm jealous and I wish you luck ;)
Do this:
frame.getContentPane().addKeyListener(this);
You may have to do it with different components depending on which one you want to have a key listener on.

gridworld help-moving critters to open spot

Alright so I have a project due for computer science, and I need help with grid world. I have the code written so I can click on a critter, but I want to click on the critter once, then click on an open spot on the grid to move the critter to. But it doesn't work. I need to get this working, and I don't know whats wrong with my code. Take a look. thanks for all your help
Actor t;
public boolean locationClicked(Location loc)
{
Grid<Actor> gr = getGrid();
t = gr.get(loc);
Actor j;
//Location second;
if (t != null)
{
setMessage("Click on " + t);
numOfClicks++;
if(t instanceof BlackCheckers || t instanceof RedCheckers)
{
if(numOfClicks==0)
{
secondClick(second);
}
}
}
else
{
setMessage("Click on nothing");
}
return true;
}
Location second;
public void secondClick(Location second)
{
this.second=second;
Grid<Actor> op=getGrid();
Actor te=op.get(second);
if(te==null)
{
t.moveTo(second);
}
}
This is beyond what I know about Gridworld, but I found something that might help.
How You Can Intercept Mouse Clicks and Keystrokes
The World class has student-friendly mechanisms for intercepting mouse clicks and keystrokes. No knowledge of AWT events is required.
When the user clicks on a grid location, the locationClicked method of the World is called. By default, that method returns false, which tells the framework to initiate the default action, namely to move the selection square and to show the constructor or method menu.
To intercept the mouse click, override the locationClicked method. Carry out any desired action and return true. The grid location on which the user clicked is passed as a parameter. Typical actions include flipping tiles, populating empty locations, and so on.
Sometimes, you need to ask the user for additional information after the mouse click. The easiest method is to use a JOptionPane.
Let’s consider a common situation. In a game, a user selects a piece. You want to ask where the user wants to move the piece. You can wait for another mouse click. That means,
your locationClicked method needs to keep track of the click state (piece selection vs. target selection). Or you can enumerate all legal targets and call JOptionPane.showOptionDialog.
When the user hits a key, the keyPressed method of the World is called. By default, that method returns false, which tells the framework to initiate the default key action. If the user hit a cursor key, the selection square is moved. If the user hit the Enter key, the constructor or method menu is shown. All other keys are ignored.
To intercept the keystroke, override the keyPressed method. The method receives the current location and the keystroke string, encoded in the same format that is used by the java.awt.KeyStroke class. Example keystroke strings are "INSERT" or "alt shift X". Your keyPressed method should check the keystroke string. If the string matches a keystroke that you want to intercept, carry out any desired action and return true. Return false for all other keystrokes. It is a good idea to return false for the cursor keys and the Enter key. Otherwise, the standard actions are disabled for your world.
Source (pg 16)

Using KeyListener for a Calculator in NetBeans

I have written a calculator in NetBeans and it functions perfectly. However, I have to actually click the buttons to insert numbers and am trying to remedy that with a KeyListener. I have all my numbers and function buttons set inside a JPanel named buttons. I have my display label in a JPanel named display.
I set my class to implement KeyListener and it inserted the KeyPressed, -Typed, and -Released methods; however I stuck from there. I'm not sure how to make my buttons actually listen for the KeyPressed event, and when it hears the event - activate the button. Also, my buttons are named by their number (e.g. the Zero Button is named zero, One button is one, etc.).
I've read that you actually have to implement a KeyListener somewhere by using: something.addKeyListener(something);
but I cannot seem to figure this out.
Can I get some help here? I'm new to Java and this is my first solo project. And let me know if I didn't provide enough information.
EDIT: Most of my code is NetBeans Generated and I cannot edit the initialization of the components which seems to be my problem I think?
My class declaration:
public class Calculator extends javax.swing.JFrame implements KeyListener {
//Creates new form Calculator
public Calculator() {
initComponents();
}
One of my buttonPressed actions (all identical with changes for actual number):
private void zeroActionPerformed(java.awt.event.ActionEvent evt) {
if (display.getText().length() >= 16)
{
JOptionPane.showMessageDialog(null, "Cannot Handle > 16 digits");
return;
}
else if (display.getText().equals("0"))
{
return;
}
display.setText(display.getText().concat("0"));
Main method supplied by NetBeans:
public static void main(String args[]) {
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new Calculator().setVisible(true);
}
});
}
The initComponents() netbeans generated is absolutely massive (about 500 lines of code) and I cannot edit any of it. Let me know if I can supply any more helpful information.
Could there be an issue of Focus, and if so how can I resolve the issue?
Yes there is probably an issue with focus. That is why you should NOT be using a KeyListener.
Swing was designed to be used with Key Bindings. That is you create an Action that does what you want. Then this Action can be added to your JButton. It can also be bound to a KeyStroke. So you have nice reusable code.
Read the Swing tutorial on How to Use Key Bindings for more information. Key Bindings don't have the focus issue that you currently have.
I'm not sure I completely understand your question, and some code would help, but I'll take a crack, since it sounds like a problem I used to have a lot.
It sounds like the reason that your key presses aren't being recognized is that the focus is on one of the buttons. If you add keylisteners to the buttons, then you shouldn't have any problem.
In netbeans you can add keylisteners through the design screen really easily.
Here's a picture showing you how to add a keyPressed listener to a button in a jPanel.
private void jButton1KeyPressed(java.awt.event.KeyEvent evt) {
//Check which key is pressed
//do whatever you need to do with the keypressed information
}
It is nice to be able to write out the listeners yourself, but if you are just learning, then it is also nice to get as much help as possible.
This might not be the best solution, since you would have to add the listener for each of your buttons.

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