Is this cast okay? - java

I have an EventHandler that I set as an event filter on TextFields. When I write the class, I get the source TextField by calling getSource() on the event and casting it to a TextField.
The code for the EventHandler:
public class NumberFilter implements EventHandler<KeyEvent> {
public final int maxLength;
public NumberFilter(int maxLength) {
this.maxLength = maxLength;
}
#Override
public void handle(KeyEvent event) {
TextField textField = (TextField) event.getSource(); //<-- is this cast okay?
//consume event if there is too much text or the input isn't a number.
if (textField.getText().length() >= maxLength || !event.getCharacter().matches("[0-9]")) {
event.consume();
}
}
}
Is this cast okay by standard java conventions? How can I write the class so that it can't be used anywhere except as an event filter for a TextField?

Andy Turner's answer provides a robust general approach to allowing event handlers to be added to only one type of Node. However, for the specific case of vetoing changes to the text in a TextField (or other text input control), the approach of using key event handlers is not a good one for the following reasons:
The user can bring up a context menu with the mouse and paste text in. This doesn't involve any key presses at all, so your handler won't be invoked.
You have no control over which type of key events the text field uses internally. Are you registering this filter with KEY_PRESSED, KEY_RELEASED, or KEY_TYPED events? Are you sure the events used internally by the text field will remain the same from one JavaFX release to the next?
You will likely inadvertently veto keyboard shortcuts such as Ctrl-C (for copy) or Ctrl-V (for paste), and similar. (If you don't veto shortcuts for "paste", you allow another loophole for the user to paste invalid text...). Again, it's possible a future release of JavaFX may introduce additional shortcuts, which it's virtually impossible to proof your functionality against.
For completeness, the preferred approach for this particular use case is as follows:
Use a TextFormatter, which is the supported mechanism for vetoing or modifying text entry to a text input control (as well as providing mechanisms to format or parse text in the control). You can make this reusable by implementing the filter in a standalone class:
public class NumberFilter implements UnaryOperator<TextFormatter.Change> {
private final Pattern pattern ;
public NumberFilter(int maxLength) {
pattern = Pattern.compile("[0-9]{0,"+maxLength+"}");
}
#Override
public TextFormatter.Change apply(TextFormatter.Change c) {
String newText = c.getControlNewText() ;
if (pattern.matcher(newText).matches()) {
return c ;
} else {
return null ;
}
}
}
And now you can do
TextField textField = new TextField();
textField.setTextFormatter(new TextFormatter<String>(new NumberFilter(5)));

Just to expand on my comment on #MaxPower's answer:
Don't use inheritance to do something which you can more cleanly do with composition.
I think that #James_D's approach is better in this case; but if in general you want an EventHandler which can only be added to a certain type of field, enforce this through your API:
public class NumberFilter implements EventHandler<KeyEvent> {
public static void addTo(int maxLength, TextField textField) {
textField.addEventHandler(new NumberFilter(maxLength));
}
private NumberFilter(int maxLength) {
// Private ctor means that you can't just create one of these
// however you like: you have to create it via the addTo method.
}
// Now casting in the handle() method is safe.
}
In this way, the only means of creating the NumberFilter is via the addTo method; and that requires that you're adding it to a TextField.

Casts are a way of you telling the compiler that you know more then it does.
If you know that every time this piece of code gets called it will be from a TextField than it is okay. Otherwise, I would do
try {
TextField textField = (TextField) event.getSource();
//Do Stuff
}
catch(ClassCastException e) {
//handle the error
}
or if you want a little more type safety
if(event.getSource() instanceof TextField) {
TextField textField = (TextField) event.getSource();
}
Or better yet
public class MyTextField extends TextField implements EventHandler<KeyEvent> {
}
then place use this instead of TextField and add your method, then it's type safe.

Related

ImageJ actionmap [duplicate]

I'm using KeyListeners in my code (game or otherwise) as the way for my on-screen objects to react to user key input. Here is my code:
public class MyGame extends JFrame {
static int up = KeyEvent.VK_UP;
static int right = KeyEvent.VK_RIGHT;
static int down = KeyEvent.VK_DOWN;
static int left = KeyEvent.VK_LEFT;
static int fire = KeyEvent.VK_Q;
public MyGame() {
// Do all the layout management and what not...
JLabel obj1 = new JLabel();
JLabel obj2 = new JLabel();
obj1.addKeyListener(new MyKeyListener());
obj2.addKeyListener(new MyKeyListener());
add(obj1);
add(obj2);
// Do other GUI things...
}
static void move(int direction, Object source) {
// do something
}
static void fire(Object source) {
// do something
}
static void rebindKey(int newKey, String oldKey) {
// Depends on your GUI implementation.
// Detecting the new key by a KeyListener is the way to go this time.
if (oldKey.equals("up"))
up = newKey;
if (oldKey.equals("down"))
down = newKey;
// ...
}
public static void main(String[] args) {
new MyGame();
}
private static class MyKeyListener extends KeyAdapter {
#Override
public void keyPressed(KeyEvent e) {
Object source = e.getSource();
int action = e.getExtendedKeyCode();
/* Will not work if you want to allow rebinding keys since case variables must be constants.
switch (action) {
case up:
move(1, source);
case right:
move(2, source);
case down:
move(3, source);
case left:
move(4, source);
case fire:
fire(source);
...
}
*/
if (action == up)
move(1, source);
else if (action == right)
move(2, source);
else if (action == down)
move(3, source);
else if (action == left)
move(4, source);
else if (action == fire)
fire(source);
}
}
}
I have problems with the responsiveness:
I need to click on the object for it to work.
The response I get for pressing one of the keys is not how I wanted it to work - too responsive or too unresponsive.
Why does this happen and how do I fix this?
This answer explains and demonstrates how to use key bindings instead of key listeners for educational purpose. It is not
How to write a game in Java.
How good code writing should look like (e.g. visibility).
The most efficient (performance- or code-wise) way to implement key bindings.
It is
What I would post as an answer to anyone who is having trouble with key listeners.
Answer; Read the Swing tutorial on key bindings.
I don't want to read manuals, tell me why I would want to use key bindings instead of the beautiful code I have already!
Well, the Swing tutorial explains that
Key bindings don't require you to click the component (to give it focus):
Removes unexpected behavior from the user's point of view.
If you have 2 objects, they can't move simultaneously as only 1 of the objects can have the focus at a given time (even if you bind them to different keys).
Key bindings are easier to maintain and manipulate:
Disabling, rebinding, re-assigning user actions is much easier.
The code is easier to read.
OK, you convinced me to try it out. How does it work?
The tutorial has a good section about it. Key bindings involve 2 objects InputMap and ActionMap. InputMap maps a user input to an action name, ActionMap maps an action name to an Action. When the user presses a key, the input map is searched for the key and finds an action name, then the action map is searched for the action name and executes the action.
Looks cumbersome. Why not bind the user input to directly to the action and get rid of the action name? Then you need only one map and not two.
Good question! You will see that this is one of the things that make key bindings more manageable (disable, rebind etc.).
I want you to give me a full working code of this.
No (the Swing tutorial has working examples).
You suck! I hate you!
Here is how to make a single key binding:
myComponent.getInputMap().put("userInput", "myAction");
myComponent.getActionMap().put("myAction", action);
Note that there are 3 InputMaps reacting to different focus states:
myComponent.getInputMap(JComponent.WHEN_FOCUSED);
myComponent.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
myComponent.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
WHEN_FOCUSED, which is also the one used when no argument is supplied, is used when the component has focus. This is similar to the key listener case.
WHEN_ANCESTOR_OF_FOCUSED_COMPONENT is used when a focused component is inside a component which is registered to receive the action. If you have many crew members inside a spaceship and you want the spaceship to continue receiving input while any of the crew members has focus, use this.
WHEN_IN_FOCUSED_WINDOW is used when a component which is registered to receive the action is inside a focused component. If you have many tanks in a focused window and you want all of them to receive input at the same time, use this.
The code presented in the question will look something like this assuming both objects are to be controlled at the same time:
public class MyGame extends JFrame {
private static final int IFW = JComponent.WHEN_IN_FOCUSED_WINDOW;
private static final String MOVE_UP = "move up";
private static final String MOVE_DOWN = "move down";
private static final String FIRE = "move fire";
static JLabel obj1 = new JLabel();
static JLabel obj2 = new JLabel();
public MyGame() {
// Do all the layout management and what not...
obj1.getInputMap(IFW).put(KeyStroke.getKeyStroke("UP"), MOVE_UP);
obj1.getInputMap(IFW).put(KeyStroke.getKeyStroke("DOWN"), MOVE_DOWN);
// ...
obj1.getInputMap(IFW).put(KeyStroke.getKeyStroke("control CONTROL"), FIRE);
obj2.getInputMap(IFW).put(KeyStroke.getKeyStroke("W"), MOVE_UP);
obj2.getInputMap(IFW).put(KeyStroke.getKeyStroke("S"), MOVE_DOWN);
// ...
obj2.getInputMap(IFW).put(KeyStroke.getKeyStroke("T"), FIRE);
obj1.getActionMap().put(MOVE_UP, new MoveAction(1, 1));
obj1.getActionMap().put(MOVE_DOWN, new MoveAction(2, 1));
// ...
obj1.getActionMap().put(FIRE, new FireAction(1));
obj2.getActionMap().put(MOVE_UP, new MoveAction(1, 2));
obj2.getActionMap().put(MOVE_DOWN, new MoveAction(2, 2));
// ...
obj2.getActionMap().put(FIRE, new FireAction(2));
// In practice you would probably create your own objects instead of the JLabels.
// Then you can create a convenience method obj.inputMapPut(String ks, String a)
// equivalent to obj.getInputMap(IFW).put(KeyStroke.getKeyStroke(ks), a);
// and something similar for the action map.
add(obj1);
add(obj2);
// Do other GUI things...
}
static void rebindKey(KeyEvent ke, String oldKey) {
// Depends on your GUI implementation.
// Detecting the new key by a KeyListener is the way to go this time.
obj1.getInputMap(IFW).remove(KeyStroke.getKeyStroke(oldKey));
// Removing can also be done by assigning the action name "none".
obj1.getInputMap(IFW).put(KeyStroke.getKeyStrokeForEvent(ke),
obj1.getInputMap(IFW).get(KeyStroke.getKeyStroke(oldKey)));
// You can drop the remove action if you want a secondary key for the action.
}
public static void main(String[] args) {
new MyGame();
}
private class MoveAction extends AbstractAction {
int direction;
int player;
MoveAction(int direction, int player) {
this.direction = direction;
this.player = player;
}
#Override
public void actionPerformed(ActionEvent e) {
// Same as the move method in the question code.
// Player can be detected by e.getSource() instead and call its own move method.
}
}
private class FireAction extends AbstractAction {
int player;
FireAction(int player) {
this.player = player;
}
#Override
public void actionPerformed(ActionEvent e) {
// Same as the fire method in the question code.
// Player can be detected by e.getSource() instead, and call its own fire method.
// If so then remove the constructor.
}
}
}
You can see that separating the input map from the action map allow reusable code and better control of bindings. In addition, you can also control an Action directly if you need the functionality. For example:
FireAction p1Fire = new FireAction(1);
p1Fire.setEnabled(false); // Disable the action (for both players in this case).
See the Action tutorial for more information.
I see that you used 1 action, move, for 4 keys (directions) and 1 action, fire, for 1 key. Why not give each key its own action, or give all keys the same action and sort out what to do inside the action (like in the move case)?
Good point. Technically you can do both, but you have to think what makes sense and what allows for easy management and reusable code. Here I assumed moving is similar for all directions and firing is different, so I chose this approach.
I see a lot of KeyStrokes used, what are those? Are they like a KeyEvent?
Yes, they have a similar function, but are more appropriate for use here. See their API for info and on how to create them.
Questions? Improvements? Suggestions? Leave a comment.
Have a better answer? Post it.
Note: this is not an answer, just a comment with too much code :-)
Getting keyStrokes via getKeyStroke(String) is the correct way - but needs careful reading of the api doc:
modifiers := shift | control | ctrl | meta | alt | altGraph
typedID := typed <typedKey>
typedKey := string of length 1 giving Unicode character.
pressedReleasedID := (pressed | released) key
key := KeyEvent key code name, i.e. the name following "VK_".
The last line should better be exact name, that is case matters: for the down key the exact key code name is VK_DOWN, so the parameter must be "DOWN" (not "Down" or any other variation of upper/lower case letters)
Not entirely intuitive (read: had to dig a bit myself) is getting a KeyStroke to a modifier key. Even with proper spelling, the following will not work:
KeyStroke control = getKeyStroke("CONTROL");
Deeper down in the awt event queue, a keyEvent for a single modifier key is created with itself as modifier. To bind to the control key, you need the stroke:
KeyStroke control = getKeyStroke("ctrl CONTROL");
Here is an easyway that would not require you to read hundreds of lines of code just learn a few lines long trick.
declare a new JLabel and add it to your JFrame (I didn't test it in other components)
private static JLabel listener= new JLabel();
The focus needs to stay on this for the keys to work though.
In constructor :
add(listener);
Use this method:
OLD METHOD:
private void setKeyBinding(String keyString, AbstractAction action) {
listener.getInputMap().put(KeyStroke.getKeyStroke(keyString), keyString);
listener.getActionMap().put(keyString, action);
}
KeyString must be written properly. It is not typesafe and you must consult the official list to learn what is the keyString(it is not an official term) for each button.
NEW METHOD
private void setKeyBinding(int keyCode, AbstractAction action) {
int modifier = 0;
switch (keyCode) {
case KeyEvent.VK_CONTROL:
modifier = InputEvent.CTRL_DOWN_MASK;
break;
case KeyEvent.VK_SHIFT:
modifier = InputEvent.SHIFT_DOWN_MASK;
break;
case KeyEvent.VK_ALT:
modifier = InputEvent.ALT_DOWN_MASK;
break;
}
listener.getInputMap().put(KeyStroke.getKeyStroke(keyCode, modifier), keyCode);
listener.getActionMap().put(keyCode, action);
}
In this new method you can simply set it using KeyEvent.VK_WHATEVER
EXAMPLE CALL:
setKeyBinding(KeyEvent.VK_CONTROL, new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("ctrl pressed");
}
});
Send an anonymous class (or use subclass) of AbstractAction. Override its public void actionPerformed(ActionEvent e) and make it do whatever you want the key to do.
PROBLEM:
I couldn't get it running for VK_ALT_GRAPH.
case KeyEvent.VK_ALT_GRAPH:
modifier = InputEvent.ALT_GRAPH_DOWN_MASK;
break;
does not make it work for me for some reason.
Here is an example of how to get key bindings working.
(Inside JFrame subclass using extends, which is called by the constructor)
// Create key bindings for controls
private void createKeyBindings(JPanel p) {
InputMap im = p.getInputMap(JPanel.WHEN_IN_FOCUSED_WINDOW);
ActionMap am = p.getActionMap();
im.put(KeyStroke.getKeyStroke("W"), MoveAction.Action.MOVE_UP);
im.put(KeyStroke.getKeyStroke("S"), MoveAction.Action.MOVE_DOWN);
im.put(KeyStroke.getKeyStroke("A"), MoveAction.Action.MOVE_LEFT);
im.put(KeyStroke.getKeyStroke("D"), MoveAction.Action.MOVE_RIGHT);
am.put(MoveAction.Action.MOVE_UP, new MoveAction(this, MoveAction.Action.MOVE_UP));
am.put(MoveAction.Action.MOVE_DOWN, new MoveAction(this, MoveAction.Action.MOVE_DOWN));
am.put(MoveAction.Action.MOVE_LEFT, new MoveAction(this, MoveAction.Action.MOVE_LEFT));
am.put(MoveAction.Action.MOVE_RIGHT, new MoveAction(this, MoveAction.Action.MOVE_RIGHT));
}
Separate class to handle those key bindings created above (where Window is the class that extends from JFrame)
// Handles the key bindings
class MoveAction extends AbstractAction {
enum Action {
MOVE_UP, MOVE_DOWN, MOVE_LEFT, MOVE_RIGHT;
}
private static final long serialVersionUID = /* Some ID */;
Window window;
Action action;
public MoveAction(Window window, Action action) {
this.window = window;
this.action = action;
}
#Override
public void actionPerformed(ActionEvent e) {
switch (action) {
case MOVE_UP:
/* ... */
break;
case MOVE_DOWN:
/* ... */
break;
case MOVE_LEFT:
/* ... */
break;
case MOVE_RIGHT:
/* ... */
break;
}
}
}

Using multiple JButtons with the same label in Java

I have two buttons in my project that both have a "+" label. When the actionPerformed() method is called, it calls a specific method based on the label. How can I distiguish between two JButtons with the same label? Is there a better way to do this then how I've done it?
This is the definition of the buttons:
JButton keypadPlus1 = new JButton(" + ");
JButton keypadMinus1 = new JButton(" - ");
JButton keypadPlus2 = new JButton("+");
JButton keypadMinus2 = new JButton("-");
Adding the ActionListeners for the buttons:
keypadPlus1.addActionListener(backEnd);
keypadPlus2.addActionListener(backEnd);
keypadMinus1.addActionListener(backEnd);
keypadMinus2.addActionListener(backEnd);
The actionPerformed #Override in the backEnd:
public void actionPerformed (ActionEvent event) {
String command = event.getActionCommand();
if (command.equals("+")) {
calcLifePoints(command);
}
if (command.equals("-")) {
calcLifePoints(command);
}
if (command.equals(" + ")) {
calcLifePoints(command);
}
if (command.equals(" - ")) {
calcLifePoints(command);
}
}
You could...
Use ActionEvent#getSource
You could...
Set the actionCommand property of each button to something unique and use ActionEvent#getActionCommand
You could...
Use separate listeners, either anonymously or as inner or outer classes depending on your needs
You could...
Make use of the Action API, which would allow you to define a common/abstract Action which defined the common properties (like the + text) and then extend this to make unique actions for each button
See How to Use Actions for more details
You could...
Use JButton#putClientProperty to set some unique flag on each button and cast the ActionEvent to a JComponent and use getClientProperty to retrieve the flag ... but given the previous suggestions, I'm not sure why you'd bother
You shouldn't have a single listener handle the behavior for different responsibilities. If the two + buttons do not do the same thing, give the buttons separate listeners.
This will allow your code to be a lot more cohesive. By reducing your listeners to 1 responsibility each, you'll be able to re-use those responsibilities. It also make testing easier, allowing you to test each behavior in complete isolation.
Although if you must, ActionEvent#getSource() returns which ever component triggered the event. Doing a reference comparison will allow you to determine which object triggered the event.
The best way to handle this would to separate the responsibilities your current listener has into separate classes:
class FirstListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
}
}
Lets assume FirstListener represents your first + button behavior. If that behavior requires any external objects (such as a field in a different class), simply pass it through the constructor:
class FirstListener implements ActionListener {
private Object dependency;
public FirstListener(Object dependency) {
this.dependency = dependency;
}
//actionPerformed declaration
}
You can do the same for the other buttons (for example, the second + button).
If you feel this is a bit excessive, feel free to use lambda expressions to declare the listeners:
//Java 8+
button.addActionListener(event -> {
});
This doesn't give you the same modularity as the previous example, as the behavior is no longer separated from the actual class: you will be forced to change the implementation to change the behavior, rather than using dependency inversion to simply pass a different object which also implements ActionListener.
Instead of this,
public void actionPerformed (ActionEvent event) {
String command = event.getActionCommand();
if (command.equals("+")) {
calcLifePoints(command);
}
if (command.equals("-")) {
calcLifePoints(command);
}
if (command.equals(" + ")) {
calcLifePoints(command);
}
if (command.equals(" - ")) {
calcLifePoints(command);
}
}
Use like this,
public void actionPerformed (ActionEvent event) {
Object command = event.getSource();
if (command.equals(keypadPlus1)) {
calcLifePoints(event.getActionCommand());
}
if (command.equals(keypadMinus1)) {
calcLifePoints(event.getActionCommand());
}
if (command.equals(keypadPlus2)) {
calcLifePoints(event.getActionCommand());
}
if (command.equals(keypadMinus2)) {
calcLifePoints(event.getActionCommand());
}
}

How to deal with Property<T>, a change listener and initialization of the property?

I am using JavaFX's Property<T> class and I am quite happy with the result, minified example code:
public CircularListCursor<E> {
private final Property<E> elementProperty;
public CircularListCursor() {
this.elementProperty = new SimpleObjectProperty(/*some value*/);
}
//various methods that call elementProperty.setValue(/*some value*/);
}
Usage:
private final CircularListCursor<SelectionData> selectionDataCursor;
...
selectionDataCursor.elementProperty().addListener((observableValue, oldValue, newValue) -> {
oldValue.getLabel().setStyle("-fx-text-fill: black");
newValue.getLabel().setStyle("-fx-text-fill: red");
});
Now this works almost perfectly, but it doesn't trigger on the construction of the object. It is logical that it works that way, because the property is not bound to yet during construction, so no change event can be fired either.
But I do want to be notified of the initial value during construction to allow for clean code, is there a way to do so?
There is no direct solution for that in JavaFX.
Nevertheless, you can make things a little bit easier/cleaner by moving the listener code into a private event handler method. This method can then be called once at the end of construction to initialize your object state. Thanks to Java 8 lambda expressions, you can use the method reference to the event handler method directly as listener:
// register event handler method
selectionDataCursor.elementProperty().addListener(this::onElementChanged);
// call listener once for initialization:
onElementChanged(selectionDataCursor.elementProperty(), null, selectionDataCursor.getElement());
...
// event handler method
private void onElementChanged(ObservableValue<? extends E> observableValue, E oldValue, E newValue) {
if (oldValue != null) oldValue.getLabel().setStyle("-fx-text-fill: black");
if (newValue != null) newValue.getLabel().setStyle("-fx-text-fill: red");
}
Side note: Listeners built via method references can't be removed any more. More specific, the following code will NOT remove the listener, as this::onElementChanged will create a new listener every time that is not equal to the one that is already registered:
selectionDataCursor.elementProperty().removeListener(this::onElementChanged);
Using EasyBind, you can
Select the nested styleProperty from elementProperty.
Bind the nested styleProperty to some observable string (in your case, a constant string for red text fill).
Provide an additional string argument to the bind method that is used to reset the style property of the old element when the element changes.
Here is the code:
ObservableValue<String> constRed = new SimpleStringProperty("-fx-text-fill: red");
EasyBind.monadic(selectionDataCursor.elementProperty())
.selectProperty(e -> e.getLabel().styleProperty())
.bind(constRed, "-fx-text-fill: black");
Notice how you don't need to register any listeners—one binding does it all. A binding is more declarative, while a listener is more imperative (side-effectful).

How to use Key Bindings instead of Key Listeners

I'm using KeyListeners in my code (game or otherwise) as the way for my on-screen objects to react to user key input. Here is my code:
public class MyGame extends JFrame {
static int up = KeyEvent.VK_UP;
static int right = KeyEvent.VK_RIGHT;
static int down = KeyEvent.VK_DOWN;
static int left = KeyEvent.VK_LEFT;
static int fire = KeyEvent.VK_Q;
public MyGame() {
// Do all the layout management and what not...
JLabel obj1 = new JLabel();
JLabel obj2 = new JLabel();
obj1.addKeyListener(new MyKeyListener());
obj2.addKeyListener(new MyKeyListener());
add(obj1);
add(obj2);
// Do other GUI things...
}
static void move(int direction, Object source) {
// do something
}
static void fire(Object source) {
// do something
}
static void rebindKey(int newKey, String oldKey) {
// Depends on your GUI implementation.
// Detecting the new key by a KeyListener is the way to go this time.
if (oldKey.equals("up"))
up = newKey;
if (oldKey.equals("down"))
down = newKey;
// ...
}
public static void main(String[] args) {
new MyGame();
}
private static class MyKeyListener extends KeyAdapter {
#Override
public void keyPressed(KeyEvent e) {
Object source = e.getSource();
int action = e.getExtendedKeyCode();
/* Will not work if you want to allow rebinding keys since case variables must be constants.
switch (action) {
case up:
move(1, source);
case right:
move(2, source);
case down:
move(3, source);
case left:
move(4, source);
case fire:
fire(source);
...
}
*/
if (action == up)
move(1, source);
else if (action == right)
move(2, source);
else if (action == down)
move(3, source);
else if (action == left)
move(4, source);
else if (action == fire)
fire(source);
}
}
}
I have problems with the responsiveness:
I need to click on the object for it to work.
The response I get for pressing one of the keys is not how I wanted it to work - too responsive or too unresponsive.
Why does this happen and how do I fix this?
This answer explains and demonstrates how to use key bindings instead of key listeners for educational purpose. It is not
How to write a game in Java.
How good code writing should look like (e.g. visibility).
The most efficient (performance- or code-wise) way to implement key bindings.
It is
What I would post as an answer to anyone who is having trouble with key listeners.
Answer; Read the Swing tutorial on key bindings.
I don't want to read manuals, tell me why I would want to use key bindings instead of the beautiful code I have already!
Well, the Swing tutorial explains that
Key bindings don't require you to click the component (to give it focus):
Removes unexpected behavior from the user's point of view.
If you have 2 objects, they can't move simultaneously as only 1 of the objects can have the focus at a given time (even if you bind them to different keys).
Key bindings are easier to maintain and manipulate:
Disabling, rebinding, re-assigning user actions is much easier.
The code is easier to read.
OK, you convinced me to try it out. How does it work?
The tutorial has a good section about it. Key bindings involve 2 objects InputMap and ActionMap. InputMap maps a user input to an action name, ActionMap maps an action name to an Action. When the user presses a key, the input map is searched for the key and finds an action name, then the action map is searched for the action name and executes the action.
Looks cumbersome. Why not bind the user input to directly to the action and get rid of the action name? Then you need only one map and not two.
Good question! You will see that this is one of the things that make key bindings more manageable (disable, rebind etc.).
I want you to give me a full working code of this.
No (the Swing tutorial has working examples).
You suck! I hate you!
Here is how to make a single key binding:
myComponent.getInputMap().put("userInput", "myAction");
myComponent.getActionMap().put("myAction", action);
Note that there are 3 InputMaps reacting to different focus states:
myComponent.getInputMap(JComponent.WHEN_FOCUSED);
myComponent.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
myComponent.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
WHEN_FOCUSED, which is also the one used when no argument is supplied, is used when the component has focus. This is similar to the key listener case.
WHEN_ANCESTOR_OF_FOCUSED_COMPONENT is used when a focused component is inside a component which is registered to receive the action. If you have many crew members inside a spaceship and you want the spaceship to continue receiving input while any of the crew members has focus, use this.
WHEN_IN_FOCUSED_WINDOW is used when a component which is registered to receive the action is inside a focused component. If you have many tanks in a focused window and you want all of them to receive input at the same time, use this.
The code presented in the question will look something like this assuming both objects are to be controlled at the same time:
public class MyGame extends JFrame {
private static final int IFW = JComponent.WHEN_IN_FOCUSED_WINDOW;
private static final String MOVE_UP = "move up";
private static final String MOVE_DOWN = "move down";
private static final String FIRE = "move fire";
static JLabel obj1 = new JLabel();
static JLabel obj2 = new JLabel();
public MyGame() {
// Do all the layout management and what not...
obj1.getInputMap(IFW).put(KeyStroke.getKeyStroke("UP"), MOVE_UP);
obj1.getInputMap(IFW).put(KeyStroke.getKeyStroke("DOWN"), MOVE_DOWN);
// ...
obj1.getInputMap(IFW).put(KeyStroke.getKeyStroke("control CONTROL"), FIRE);
obj2.getInputMap(IFW).put(KeyStroke.getKeyStroke("W"), MOVE_UP);
obj2.getInputMap(IFW).put(KeyStroke.getKeyStroke("S"), MOVE_DOWN);
// ...
obj2.getInputMap(IFW).put(KeyStroke.getKeyStroke("T"), FIRE);
obj1.getActionMap().put(MOVE_UP, new MoveAction(1, 1));
obj1.getActionMap().put(MOVE_DOWN, new MoveAction(2, 1));
// ...
obj1.getActionMap().put(FIRE, new FireAction(1));
obj2.getActionMap().put(MOVE_UP, new MoveAction(1, 2));
obj2.getActionMap().put(MOVE_DOWN, new MoveAction(2, 2));
// ...
obj2.getActionMap().put(FIRE, new FireAction(2));
// In practice you would probably create your own objects instead of the JLabels.
// Then you can create a convenience method obj.inputMapPut(String ks, String a)
// equivalent to obj.getInputMap(IFW).put(KeyStroke.getKeyStroke(ks), a);
// and something similar for the action map.
add(obj1);
add(obj2);
// Do other GUI things...
}
static void rebindKey(KeyEvent ke, String oldKey) {
// Depends on your GUI implementation.
// Detecting the new key by a KeyListener is the way to go this time.
obj1.getInputMap(IFW).remove(KeyStroke.getKeyStroke(oldKey));
// Removing can also be done by assigning the action name "none".
obj1.getInputMap(IFW).put(KeyStroke.getKeyStrokeForEvent(ke),
obj1.getInputMap(IFW).get(KeyStroke.getKeyStroke(oldKey)));
// You can drop the remove action if you want a secondary key for the action.
}
public static void main(String[] args) {
new MyGame();
}
private class MoveAction extends AbstractAction {
int direction;
int player;
MoveAction(int direction, int player) {
this.direction = direction;
this.player = player;
}
#Override
public void actionPerformed(ActionEvent e) {
// Same as the move method in the question code.
// Player can be detected by e.getSource() instead and call its own move method.
}
}
private class FireAction extends AbstractAction {
int player;
FireAction(int player) {
this.player = player;
}
#Override
public void actionPerformed(ActionEvent e) {
// Same as the fire method in the question code.
// Player can be detected by e.getSource() instead, and call its own fire method.
// If so then remove the constructor.
}
}
}
You can see that separating the input map from the action map allow reusable code and better control of bindings. In addition, you can also control an Action directly if you need the functionality. For example:
FireAction p1Fire = new FireAction(1);
p1Fire.setEnabled(false); // Disable the action (for both players in this case).
See the Action tutorial for more information.
I see that you used 1 action, move, for 4 keys (directions) and 1 action, fire, for 1 key. Why not give each key its own action, or give all keys the same action and sort out what to do inside the action (like in the move case)?
Good point. Technically you can do both, but you have to think what makes sense and what allows for easy management and reusable code. Here I assumed moving is similar for all directions and firing is different, so I chose this approach.
I see a lot of KeyStrokes used, what are those? Are they like a KeyEvent?
Yes, they have a similar function, but are more appropriate for use here. See their API for info and on how to create them.
Questions? Improvements? Suggestions? Leave a comment.
Have a better answer? Post it.
Note: this is not an answer, just a comment with too much code :-)
Getting keyStrokes via getKeyStroke(String) is the correct way - but needs careful reading of the api doc:
modifiers := shift | control | ctrl | meta | alt | altGraph
typedID := typed <typedKey>
typedKey := string of length 1 giving Unicode character.
pressedReleasedID := (pressed | released) key
key := KeyEvent key code name, i.e. the name following "VK_".
The last line should better be exact name, that is case matters: for the down key the exact key code name is VK_DOWN, so the parameter must be "DOWN" (not "Down" or any other variation of upper/lower case letters)
Not entirely intuitive (read: had to dig a bit myself) is getting a KeyStroke to a modifier key. Even with proper spelling, the following will not work:
KeyStroke control = getKeyStroke("CONTROL");
Deeper down in the awt event queue, a keyEvent for a single modifier key is created with itself as modifier. To bind to the control key, you need the stroke:
KeyStroke control = getKeyStroke("ctrl CONTROL");
Here is an easyway that would not require you to read hundreds of lines of code just learn a few lines long trick.
declare a new JLabel and add it to your JFrame (I didn't test it in other components)
private static JLabel listener= new JLabel();
The focus needs to stay on this for the keys to work though.
In constructor :
add(listener);
Use this method:
OLD METHOD:
private void setKeyBinding(String keyString, AbstractAction action) {
listener.getInputMap().put(KeyStroke.getKeyStroke(keyString), keyString);
listener.getActionMap().put(keyString, action);
}
KeyString must be written properly. It is not typesafe and you must consult the official list to learn what is the keyString(it is not an official term) for each button.
NEW METHOD
private void setKeyBinding(int keyCode, AbstractAction action) {
int modifier = 0;
switch (keyCode) {
case KeyEvent.VK_CONTROL:
modifier = InputEvent.CTRL_DOWN_MASK;
break;
case KeyEvent.VK_SHIFT:
modifier = InputEvent.SHIFT_DOWN_MASK;
break;
case KeyEvent.VK_ALT:
modifier = InputEvent.ALT_DOWN_MASK;
break;
}
listener.getInputMap().put(KeyStroke.getKeyStroke(keyCode, modifier), keyCode);
listener.getActionMap().put(keyCode, action);
}
In this new method you can simply set it using KeyEvent.VK_WHATEVER
EXAMPLE CALL:
setKeyBinding(KeyEvent.VK_CONTROL, new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("ctrl pressed");
}
});
Send an anonymous class (or use subclass) of AbstractAction. Override its public void actionPerformed(ActionEvent e) and make it do whatever you want the key to do.
PROBLEM:
I couldn't get it running for VK_ALT_GRAPH.
case KeyEvent.VK_ALT_GRAPH:
modifier = InputEvent.ALT_GRAPH_DOWN_MASK;
break;
does not make it work for me for some reason.
Here is an example of how to get key bindings working.
(Inside JFrame subclass using extends, which is called by the constructor)
// Create key bindings for controls
private void createKeyBindings(JPanel p) {
InputMap im = p.getInputMap(JPanel.WHEN_IN_FOCUSED_WINDOW);
ActionMap am = p.getActionMap();
im.put(KeyStroke.getKeyStroke("W"), MoveAction.Action.MOVE_UP);
im.put(KeyStroke.getKeyStroke("S"), MoveAction.Action.MOVE_DOWN);
im.put(KeyStroke.getKeyStroke("A"), MoveAction.Action.MOVE_LEFT);
im.put(KeyStroke.getKeyStroke("D"), MoveAction.Action.MOVE_RIGHT);
am.put(MoveAction.Action.MOVE_UP, new MoveAction(this, MoveAction.Action.MOVE_UP));
am.put(MoveAction.Action.MOVE_DOWN, new MoveAction(this, MoveAction.Action.MOVE_DOWN));
am.put(MoveAction.Action.MOVE_LEFT, new MoveAction(this, MoveAction.Action.MOVE_LEFT));
am.put(MoveAction.Action.MOVE_RIGHT, new MoveAction(this, MoveAction.Action.MOVE_RIGHT));
}
Separate class to handle those key bindings created above (where Window is the class that extends from JFrame)
// Handles the key bindings
class MoveAction extends AbstractAction {
enum Action {
MOVE_UP, MOVE_DOWN, MOVE_LEFT, MOVE_RIGHT;
}
private static final long serialVersionUID = /* Some ID */;
Window window;
Action action;
public MoveAction(Window window, Action action) {
this.window = window;
this.action = action;
}
#Override
public void actionPerformed(ActionEvent e) {
switch (action) {
case MOVE_UP:
/* ... */
break;
case MOVE_DOWN:
/* ... */
break;
case MOVE_LEFT:
/* ... */
break;
case MOVE_RIGHT:
/* ... */
break;
}
}
}

How do I access the source of an ActionEvent when the ActionListener is located in a different class?

I can't get my head round this one. I've tried to adhere to the MVC pattern for the first time and now have difficulties accessing the source of an ActionEvent because the ActionListener is located in a different class. But let the code do the talking...
In the "view":
// ControlForms.java
...
private JPanel createSearchPanel() throws SQLException {
...
comboBoxCode = new JComboBox(); // Field comboBoxCode -> JComboBox()
SwingUtilities.invokeLater(new Runnable() {
public void run() {
AutoCompleteSupport<Object> support = AutoCompleteSupport.install(
comboBoxCode, GlazedLists.eventListOf(jnlCodeArray));
}
}); // Auto-Complete comboBox from GlazedLists
...
public void setComboListener(ComboListener comboListener) {
comboBoxCode.addActionListener(comboListener);
}
...
}
Then, in what I term the controller, I have two different classes:
// Controller.java
public MyController() throws SQLException {
...
addListeners();
}
...
private void addListeners(){
View view = getView();
getView().getControlForm().setComboListener(new ComboListener());
}
and
public class ComboListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
System.out.println("ComboBox listened to! e = " + e.toString());
}
}
Now, e obviously doesn't give the name of the variable (which at the moment I wish it would), so I cannot if test for e.getSource().
My question is thus: is there either a) a way to query (via if for example) the source of e, or b) a less complicated way to get to the variable name?
Many, many thanks in advance for your insights and tips!
Why do you need the name of the variable? Why can't you do the event handling like this
public class ComboListener implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
JComboBox source = (JComboBox)e.getSource();
//do processing here
}
}
I'd think that if you need to do processing according the variable name, obviously you need different listeners for different combo boxes.
Generally, there are only two situations in which you should use a listener like that: a) you're going to handle a certain event the same way for a bunch of objects, or b) you're only going to use the listener for one object. In the latter case, I'd prefer handling the event locally anyway.
That said, the direct answer to your question is: you shouldn't have to check inside your ActionListener implementation to see whether the appropriate object is the source of the event; you should simply only add the ActionListener to that one object.
One final note: without knowing the specifics of your architecture... generally, MVC will treat all event handling as part of the View (it reduces coupling) and the View will pass commands or method calls or your own events (i.e., not Swing's) to the Controller.

Categories