Ich verwende KeyListener
s in meinem Code (Spiel oder auf andere Weise), um meine Objekte auf dem Bildschirm auf Benutzereingaben zu reagieren. Hier ist mein 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);
}
}
}
Ich habe Probleme mit der Reaktionsfähigkeit:
Warum passiert das und wie kann ich das beheben?
In dieser Antwort wird erläutert und veranschaulicht, wie Tastenzuweisungen anstelle von Schlüsselhörern für Bildungszwecke verwendet werden. Es ist nicht
Es ist
Antworten; Lesen Sie das Swing-Tutorial zu Tastenkombinationen .
Ich möchte keine Handbücher lesen, sagen Sie mir, warum ich anstelle des schönen Codes, den ich bereits habe, Schlüsselbindungen verwenden möchte!
Nun, das Swing-Tutorial erklärt das
OK, Sie haben mich davon überzeugt, es auszuprobieren. Wie funktioniert es?
Das Tutorial hat einen guten Abschnitt darüber. Schlüsselbindungen umfassen 2 Objekte InputMap
und ActionMap
. InputMap
ordnet eine Benutzereingabe einem Aktionsnamen zu, ActionMap
ordnet einen Aktionsnamen einem Action
zu. Wenn der Benutzer eine Taste drückt, wird die Eingabezuordnung nach der Taste durchsucht und ein Aktionsname gefunden. Dann wird die Aktionszuordnung nach dem Aktionsnamen gesucht und führt die Aktion aus.
Sieht umständlich aus. Warum binden Sie die Benutzereingabe nicht direkt an die Aktion und entfernen den Aktionsnamen? Dann brauchen Sie nur eine Karte und nicht zwei.
Gute Frage! Sie werden sehen, dass dies eine der Faktoren ist, die die Tastenkombinationen leichter handhabbar machen (deaktivieren, neu binden usw.).
Ich möchte, dass Sie mir einen vollständigen Code dazu geben.
Nein (das Swing-Tutorial enthält Arbeitsbeispiele ).
Du gehtst mir auf die Nerven!Ich hasse dich!
So stellen Sie eine einzelne Schlüsselbindung her:
myComponent.getInputMap().put("userInput", "myAction");
myComponent.getActionMap().put("myAction", action);
Beachten Sie, dass es 3 InputMap
s gibt, die auf verschiedene Fokuszustände reagieren:
myComponent.getInputMap(JComponent.WHEN_FOCUSED);
myComponent.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
myComponent.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
WHEN_FOCUSED
, der auch verwendet wird, wenn kein Argument angegeben wird, wird verwendet, wenn die Komponente den Fokus hat. Dies ist ähnlich wie bei dem Key Listener.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT
wird verwendet, wenn sich eine fokussierte Komponente in einer Komponente befindet, die zum Empfangen der Aktion registriert ist. Wenn sich viele Besatzungsmitglieder in einem Raumschiff befinden und Sie möchten, dass das Raumschiff weiterhin Eingaben erhält, während eines der Besatzungsmitglieder den Fokus hat, verwenden Sie diese Option.WHEN_IN_FOCUSED_WINDOW
wird verwendet, wenn sich eine Komponente, die zum Empfangen der Aktion registriert ist, in einer fokussierten Komponente befindet. Wenn sich viele Tanks in einem fokussierten Fenster befinden und alle gleichzeitig Eingaben erhalten sollen, verwenden Sie diese Option.Der in der Frage dargestellte Code sieht in etwa so aus, wenn beide Objekte gleichzeitig gesteuert werden:
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.
}
}
}
Sie sehen, dass das Trennen der Eingabezuordnung von der Aktionszuordnung wiederverwendbaren Code und eine bessere Kontrolle der Bindungen ermöglicht. Darüber hinaus können Sie eine Aktion auch direkt steuern, wenn Sie die Funktionalität benötigen. Zum Beispiel:
FireAction p1Fire = new FireAction(1);
p1Fire.setEnabled(false); // Disable the action (for both players in this case).
Weitere Informationen finden Sie im Action-Tutorial .
Ich sehe, dass Sie 1 Aktion, Bewegung für 4 Tasten (Richtungen) und 1 Aktion, Feuer, für 1 Taste verwendet haben. Warum geben Sie jeder Taste keine eigene Aktion oder geben Sie allen Tasten dieselbe Aktion und sortieren Sie, was zu tun ist innerhalb der Aktion ausführen (wie in dem Verschiebungsfall)?
Guter Punkt. Technisch können Sie beides tun, aber Sie müssen sich überlegen, was sinnvoll ist und was eine einfache Verwaltung und wiederverwendbaren Code ermöglicht. Ich nahm an, dass das Bewegen für alle Richtungen ähnlich ist und das Schießen anders ist, also habe ich diesen Ansatz gewählt.
Ich sehe viele
KeyStroke
s, was sind die? Sind sie wie einKeyEvent
?
Ja, sie haben eine ähnliche Funktion, sind aber für die Verwendung hier besser geeignet. Sehen Sie sich ihre API für Informationen und wie Sie sie erstellen können.
Fragen? Verbesserungen? Vorschläge? Hinterlasse einen Kommentar . Hast du eine bessere Antwort? Veröffentliche es.
Hinweis: Dies ist keine Antwort, nur ein Kommentar mit zu viel Code :-)
KeyStrokes über getKeyStroke (String) zu beziehen, ist der richtige Weg - aber das api doc muss sorgfältig gelesen werden:
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_".
Die letzte Zeile sollte besser genauer Name sein, das ist der Fall: Für die Abwärts-Taste lautet der genaue Tastencode VK_DOWN
, daher muss der Parameter "DOWN" sein (nicht "Down" oder eine andere Variation von Kleinbuchstaben)
Nicht ganz intuitiv (gelesen: musste selbst ein bisschen Dig graben) bringt einen KeyStroke auf einen Modifier-Key. Selbst mit der richtigen Schreibweise funktioniert Folgendes nicht:
KeyStroke control = getKeyStroke("CONTROL");
Weiter unten in der awt-Ereigniswarteschlange wird ein keyEvent für einen einzelnen Modifiziererschlüssel mit sich selbst als Modifizierer erstellt. Um an die Steuertaste zu binden, benötigen Sie den Strich:
KeyStroke control = getKeyStroke("ctrl CONTROL");
Hier ist eine einfache Methode, bei der Sie nicht hunderte von Codezeilen lesen müssen, sondern nur ein paar Zeilen lang lernen.
deklarieren Sie ein neues JLabel und fügen Sie es Ihrem JFrame hinzu (ich habe es nicht in anderen Komponenten getestet)
private static JLabel listener= new JLabel();
Der Fokus muss auf diesem Punkt bleiben, damit die Tasten funktionieren.
Im Konstruktor:
add(listener);
Verwenden Sie diese Methode:
ALTE METHODE:
private void setKeyBinding(String keyString, AbstractAction action) {
listener.getInputMap().put(KeyStroke.getKeyStroke(keyString), keyString);
listener.getActionMap().put(keyString, action);
}
KeyString muss richtig geschrieben werden. Es ist nicht typensicher und Sie müssen die offizielle list konsultieren, um zu erfahren, was der keyString (es ist kein offizieller Begriff) für jede Schaltfläche ist.
NEUE METHODE
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 dieser neuen Methode können Sie es einfach mit KeyEvent.VK_WHATEVER
einstellen.
BEISPIEL ANRUFE:
setKeyBinding(KeyEvent.VK_CONTROL, new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("ctrl pressed");
}
});
Senden Sie eine anonyme Klasse (oder verwenden Sie eine Unterklasse) von AbstractAction. Überschreiben Sie public void actionPerformed(ActionEvent e)
und machen Sie, was immer Sie wollen, damit der Schlüssel funktioniert.
PROBLEM:
Ich konnte es nicht für VK_ALT_GRAPH zum Laufen bringen.
case KeyEvent.VK_ALT_GRAPH:
modifier = InputEvent.ALT_GRAPH_DOWN_MASK;
break;
macht es aus irgendeinem Grund nicht für mich.