View Full Version : [JAVA] keyListener su un jpanel
alessia86
12-09-2009, 08:52
Salve a tutti..allora io ho un jpanel e su di esso voglio mettere un ascoltare di eventi da tastiera..e faccio:
this.addKeyListener(new KeyListener());
//this indica il pannello
setFocusable(true);
metto setFocusable per dare il focus al pannello..però la tastiera non funziona lo stesso se prima non faccio un clicco sull'icona della finestra che contiene il pannello..C'è un altro modo per far ricevere gli eventi su un pannello? oppure il pannello devo farlo diventare frame?
Grazie a tutti!! :cry:
Invoca requestFocusInWindow sul pannello dopo l'apertura della finestra di cui fa parte.
alessia86
12-09-2009, 11:20
Allora ho scritto nel pannello:
this.requestFocusInWindow(true);
Però non li rileva lo stesso.. :muro:
Funziona solo dopo che il pannello è apparso sullo schermo.
alessia86
12-09-2009, 13:06
Anche dopo che appare il pannello non succede niente..non so forse sbaglio a scrivere io qualcosa..Non capisco :doh:
Esempio:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Main {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
start();
}
});
}
private static void start() {
final JPanel PANEL = new JPanel();
PANEL.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
System.out.println("key pressed...");
}
});
JFrame window = new JFrame("Test");
window.addWindowListener(new WindowAdapter() {
public void windowOpened(WindowEvent e) {
PANEL.requestFocusInWindow();
}
public void windowClosing(WindowEvent e) {
e.getWindow().dispose();
}
});
window.add(PANEL);
window.setSize(400, 400);
window.setVisible(true);
}
}
alessia86
12-09-2009, 15:04
Ok..adesso mi da..anche se io avevo già provato prima che mi rispondessi..dando il focus al frame che contiene il pannello..facendo sempre requestFocusInWindow e tutto ciò l'ho messo sempre nel pannello..
Ora è sorto un altro problema che non so se dipende dal fatto che i miei componenti li disegno su un jpanel..In pratica ho un personaggio che deve muoversi a sinistra e lo faccio muovere cosi:
p.sel=1; //p indica la classe personaggio che selezionando l'indice 1 andrà a prendere l'immagine sinistra del personaggio
if(p.getX()>5)
for(int i=1;i<=6;i++)
p.vaiSinistra(); // dopodichè si muove
try {
Thread.sleep(40);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
p.sel=0; // adesso prende l'immagine indice 0 di personaggio che sarà l'immagine del pers frontale
}
Ora il personaggio si muove però non ho l'effetto che ad ogni passo il personaggio cambia da figura sinistra a frontale..Non so se mi sono spiegata bene!! Cmq se non va bene metterlo qui farò un nuovo post
E' possibile che incollando il pezzo di codice si siano perse un po' di parentesi? Altrimenti è un algoritmo un po' strano quello che vedo.
p.sel = (0 oppure 1) è quello che "scambia" l'immagine, va benissimo.
p.sel = 1, cioè usiamo la figura dell'omino che va sinistra.
Poi abbiamo un:
se la posizione è maggiore di 5
-vai cinque volte a sinistra: l'immagine è sempre 1.
Fatto questo, c'è una pausa di 40ms seguita da un:
p.sel = 0, cioè l'immagine corrente dell'omino diventa quella frontale.
E' corretto, manca qualche pezzo oppure io non ho capito una mazza?
alessia86
12-09-2009, 15:23
si..è proprio cosi'..il codice lo hai interpretato benissimo..Mi scuso per l'ultima parantesi...era la chiusura dell'evento che si rilevava premendo un tasto ..Non l'ho messo io ;)
alessia86
12-09-2009, 15:32
In pratica..per spiegarmi meglio..L'immagine 1 del personaggio versione sinistra non la visualizza..ma visualizza solo p.sel=0 e cioè l'immagine frontale del personaggio..
Non dovrebbe invece essere una cosa tipo (pseudo codice):
p.sel = 1; //cioè partiamo da sinistra
se p.x > 5 {
per cinque volte {
p.vai a sinistra
se p.sel == 0 p.sel = 1 altrimenti p.sel = 0
pausa di 40 millisecondi
}
}
?
alessia86
12-09-2009, 16:09
Ho provato..ma niente ancora non da..Ti rimando il codice:
if(e.getKeyCode()==KeyEvent.VK_LEFT){
p.sel=1;
if(p.getX()>5){
for(int i=1;i<6;i++){
p.vaiSinistra();
if(p.sel==0)
p.sel=1;
else
p.sel=0;
try {
Thread.sleep(40);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
}
il metodo vaiASinistra causa l'aggiornamento dell'aspetto dello sprite "p"? Cioè una cosa tipo:
public void vaiASinistra() {
x--;
schermo.repaint();
}
?
alessia86
12-09-2009, 16:15
La cosa strana è che io disegno il mio videogioco e quindi il personaggio in un jpanel..se lo disegno in un jframe il personaggio si muove con le diverse facciate..
alessia86
12-09-2009, 16:15
si..esattamente setta la posizione e dopo invoca repaint..
Giusto un esperimento (da cancellare subito dopo aver tentato).
if(e.getKeyCode() == eccetera) {
new Thread() {
public void run() {
//qui metti tutto quello da p.sel = 1 al catch.
}
}.start();
}
Se sei in un metodo keyPressed allora sei nell'EDT. Se sei nell'edt quando invochi repaint() quel repaint() è eseguito dopo il ciclo for.
Dovresti però notare altre stranezze rispetto al solo mancato aggiornamento dell'immagine (in particolare un movimento a scatti e uina scarsa reattività della GUI).
alessia86
12-09-2009, 16:35
Ho provato..e adesso funziona :D ...forse faceva interferenza con l'edt..
Occhio che così generi un thread per ogni keyPressed. Se l'utente tiene premuto un tasto per troppo tempo ti ritrovi con uno tsunami di Thread in esecuzione.
alessia86
12-09-2009, 16:51
io uso qst solo per 2 tasti..la freccia destra e sinistra che mi fa muovere il personaggio..Va bene solo per 2 oppure mi creerebbe lo stesso problemi?
Basta anche un tasto. Se tieni premuto un pulsante - ed è attiva la ripetizione, come normalmente è - il metodo keyPressed viene invocato in rapida successione.
Francamente non saprei dirti se ti convenga o no affrontare il problema. Potresti ad esempio introdurre un campo volatile di tipo boolean da controllare prima di avviare il thread. Tipo:
private volatile boolean handlingInput = false;
public void keyPressed(KeyEvent e) {
if(e.getKeyCode() == eccetera) {
if(!handlingInput) {
handlingInput = true;
new Thread() {
public void run() {
//p.set = 1
//eccetera eccetera
handlingInput = false;
}
}.start();
}
}
}
Questo esclude la possibilità che venga lanciato il Thread se lo stesso non ha ancora terminato di masticare l'input.
Poi però ti trovi a dover gestire il caso "è possibile che l'utente debba premere due tasti conteporaneamente?"
Se sì allora dovrai avere due booleani handlingInput, uno per tasto.
E' fattibile ma aggiunge un certo grado di complessità al programma.
Così come aggiunge complessità il fatto che essendo "p" maneggiato da Thread diversi esso dovrebbe essere Thread-Safe. Per il codice che appare è sufficiente che il suo campo set sia dichiarato volatile. Bisogna poi vedere se nel resto del programma ci siano altri interventi concorrenti.
Insomma, potrebbero esserci un tot di cosine da sistemare.
alessia86
12-09-2009, 17:12
Altrimenti come posso risolvere il problema senza mettere un thread per il tasto?
Puoi usare un Thread a parte che esegue un ciclo continuo di consumazione su una coda degli eventi da te creata. Ad ogni keyPressed aggiugeresti a quella coda un marcatore, tipo un intero MOVE_LEFT, che causerebbe l'invocazione da parte del Thread della sequenza di operazioni che prima avevi nel keyPressed.
In questo non avresti più il problema dell'accumulo di Thread.
Bisognerebbe comunque vedere l'architettura generale del tuo motore di gioco per capire quanto sarebbe problematica una soluzione del genere.
A me sembra che tu abbia scelto un'architettura parallela che va benissimo ma richiede una certa cura negli interventi altrimenti si accumulano dei comportamenti bizzarri.
alessia86
12-09-2009, 17:35
Forse non ho capito bene..dovrei fare l'intera classe keypres(è la classe che gestisce gli eventi da tastiera) di tipo thread? però gli altri tipi di tasti che ho messo tipo escape oppure il tasto a funzionano senza bisogno di niente..
alessia86
12-09-2009, 17:36
Vorrei dire non è che magari mettendo l'intera classe di tipo thread mi crea problemi pure con gli altri tasti?
Il problema che avevi era che il thread che esegue il metodo keyPressed è lo stesso che deve disegnare. Il disegno viene fatto con repaint() ma repaint non disegna subito: aspetta che l'EDT termini di eseguire il compito attuale poi disegna.
Così se nel tuo keyPressed scrivi:
per cinque volte scambia l'immagine, repaint(), sleep
l'EDT per cinque volte scambia l'immagine, per cinque volte va in pausa POI ridisegna per cinque volte.
Se usi un Thread diverso ottieni invece l'interposizione tra il disegno e lo scambio delle immagini con pausa successiva.
Non è priva di problemi neppure questa soluzione ma non causa errori fatali del programma, semmai può capitare che la sostituzione delle immagini non risulti esattamente sequenziale.
E' critico invece il problema che deriva dalla creazione di un Thread ad ogni keyPressed. Relativamente, perchè puoi tranquillamente creare diverse centinaia di Thread prima che la JVM entri in crisi, il tuo utente dovrebbe star lì col dito spiaccicato sul tasto per parecchi secondi di fila prima che tutto vada a schifìo.
Il Thread ad hoc per la gestione dell'input sarebbe una cosa di questo genere:
public class LaMiaClasse extends KeyAdapter {
private static final Integer MOVE_LEFT = 0;
private static final Integer FIRE = 1;
private final ConcurrentBlockingQueue<Integer> INPUT_QUEUE =
new ConcurrentBlockingQueue<Integer>();
private final Thread INPUT_PARSER = new Thread() {
public void run() {
//ciclo di gestione dell'input
while(true) {
try {
Integer action = INPUT_QUEUE.take();
if(action == MOVE_LEFT) {
p.sec = 1;
for(int i = 1; i<=6;i++) {
p.muoviASinistra();
try {
Thread.sleep(40);
} catch(InterruptedException ex) {
return;
}
}
} else if(action == FIRE) {
}
} catch(InterruptedException ex) {
return;
}
}
}
};
public LaMiaClasse() {
INPUT_PARSER.setDaemon(true);
}
public void keyPressed(KeyEvent e) {
if(!INPUT_PARSER.isAlive()) {
INPUT_PARSER.start();
}
if(e.getKeyCode() == KeyEvent.VK_LEFT) {
INPUT_QUEUE.offer(MOVE_LEFT);
}
}
}
Tieni conto che l'ho scritto al volo ma l'idea è quella.
alessia86
13-09-2009, 13:55
Grazie mille..ho creato un nuovo thread e funziona..in effetti almeno cosi..non causa errori fatali..qst videogioco lo sto preparando per un esame ;) Grazie davv
alessia86
13-09-2009, 14:32
E se voglio inserire nella mia classe Keypres..qst codice:
if(e.getKeyCode()==KeyEvent.VK_A)
{
if(!Fu.stato){
Fu.Ricevi(pal,pal2,pal3);
Thread t=new Thread(Fu);
t.start();
SoundT player = new SoundT("Suoni/pang3-079.wav");
player.start();
}
dove fa si che premendo il tasto A la classe Fuoco(Fu) riceva gli oggetti che devono essere colpiti..poi avvia il thread che fa partire la classe Fuoco..e SoundT è un'altra classe (sempre un thread) che mi genera il suono dello sparo.
La classe Fuoco è un thread che al suo avvio fa partire un metodo che verifica se lo sparo colpisce gli oggetti..
Ora se io in KeyPres aggiungo un metodo del genere..non genera interferenze con il Thread creato per la pressione dei tasti in keypres?
pur volendo aggiungerlo nella coda bloccante come faccio poi non posso creare un altro thread ,che mi avvi la classe fuoco, all'interno di un altro..
vBulletin® v3.6.4, Copyright ©2000-2025, Jelsoft Enterprises Ltd.