# Snake in Java KeyListener Problem



## Leon92 (21. Nov 2011)

Hallo alle zusammen,

also ich bin gerade dabei Snake in Java zu programieren. aber ich komm einfach nich weiter.
Bis jetzt habe ich eine Klasse die das Programm starten und die das Fenster erzeugt und dort die Panels einfügen soll (=Window.class)

Dann habe ich eine Klasse die die ganz Grafik in ein Panel zeichnen soll, und nebenbei noch die FPS berechnet. (=MainGraphics.class). 

Die dritte und problembereitende  Klasse ist die die die Tastatureingaben abhören und die neuen Koordinaten berechnen soll. (=Snake.class)

Das Problem ist nun aber das Snake.class eigentlich unsichtbar ist und ich nicht weiß wie man da einen Keylistener einbaut.

Kann mir da vielleicht jmd helfen ?!

Danke ,Leo


----------



## SlaterB (21. Nov 2011)

den KeyListener kannst du dem JPanel zuordnen, dann aber requestFocus() nach setVisible(true) nicht vergessen,
robuster für alles andere als TextFelder & Co. sind vielleicht KeyBindings

How to Write a Key Listener (The Java™ Tutorials > Creating a GUI With JFC/Swing > Writing Event Listeners)


----------



## Leon92 (21. Nov 2011)

ich glaub du hast da was verkehrt verstanden. 

ich will bzw müsste den KeyListener eben der Klasse zuorden die gar kein Panel hat....


----------



## SlaterB (21. Nov 2011)

mit quasi einzeiligen Antworten kommen wir aber nicht weiter
überall auf der Welt werden Listener Komponenten 'zugeordnet' (was immer man darunter gerade verstehen mag), 
keinen anderen Klassen, so gehts, anders gehts nicht

das Tutorial zeigt Beispielcode, vielleicht hast du ja auch irgendeine Art Information/ Hinweis für andere?


----------



## Michael... (21. Nov 2011)

Leon92 hat gesagt.:


> ich glaub du hast da was verkehrt verstanden.
> 
> ich will bzw müsste den KeyListener eben der Klasse zuorden die gar kein Panel hat....


Sowohl bei KeyListenern als auch mit KeyBindings ist man an Komponenten gebunden. Mit einem Java Programm kann man nicht direkt die Tastatur abhören.

Ich würde hier ebenfalls mit KeyBindings arbeiten und diese an die Komponente welche das Spielfeld/die Schlange zeichnet hängen.


----------



## Leon92 (21. Nov 2011)

tut mir Leid meine hastige Antwort,

aber es wäre in meinen Augen Blöd die Tastatur in der einen Klasse abzuhören, das ganze in einer anderen zu verarbeiten und dann das ganze wieder zurückzugeben.
könnte man nicht einfach zB ein Panel einbinden und das unsichtbar machen, wenn ja wo muss ich diese Klasse dann einfügen?

In meiner Grafik Klasse oder in meiner Window Klasse ?

Danke für eure schnelle Hilfe


----------



## SlaterB (21. Nov 2011)

was ändert sich denn strukturell, wenn du ein unsichtbares Panel nimmst, worum geht es dir überhaupt?
du kannst deinen Code in die Klasse schreibe die du willst, du musst nur irgendwo eine einzelne Zeile
panel.addListener(listener); schreiben, was macht es für einen Unterschied ob du das gleich beim richtigen Panel machst oder einen neuen unsichtbaren?

du kannst auch bei verschiedenen Paneln/ Komponenten denselben Listener registieren oder verschiedene
und die eigentliche Aktion dann immer an eine zentrale Verarbeitungsklasse weiterleiten

> es wäre in meinen Augen Blöd die Tastatur in der einen Klasse abzuhören, das ganze in einer anderen zu verarbeiten und dann das ganze wieder zurückzugeben.

ja wie nun, willst du mehrere Klassen oder nicht? 
wenn alles in einer Klasse steht bei der Panel-Definition, dann ist es ja noch leichter, das ursprüngliche Panel zu verwenden,
das ist alles sehr schlecht zu verstehen, du hast deine Vorstellungen, ich andere,
da du die Frage stellst wäre es immer noch angebracht, dass du deine Ideen in langen Texten ausbreitest


----------



## Leon92 (21. Nov 2011)

Ja also jetzt noch mal ganz genau...

ich möchte Snake programmieren, mit jeweils einer Klasse:

... Zum starten der Programms und erzeugen des Fensters. (-> Window.class)
... zum erzeugen der Frames, bzw zum darstellen der Grafik (-> MainGraphics.class)
... und eine eine unsichtbare Klasse zum Bearbeiten/ Berechnen der Koordinaten(-> Snake)

... zur Not auch nocht eine Extra Klasse(unsichtbar) zum Abhören der Tastatur.

was mir aber auf jeden Fall wichtig ist: das die Grafikklasse wirklich eine reine Grafikklasse bleibt, deshalb möchte ich da den Keylistener nicht haben.

Ich hoffe das war verständlich.
Mit Code kann ich ehrlich gesagt nicht dienen weil das alles noch zu durcheinander ist, aber ich blick schon durch 

Leon


----------



## SlaterB (21. Nov 2011)

da ist noch nicht wirklich was neues dabei, z.B. wenig zur sehr konkreten Frage, was ein 'unsichtbares' Panel von anderen unterscheidet,

nur zur Wiedeholung:
evtl. reicht es, die Verknüpfung in der main-Methode zu bauen:

```
Window w = ..;
MainGraphics m = .. mit w;
Snake n = ..;
w.panel.addKeyListener(n);
```
oder irgendwas vergleichbares


----------



## Leon92 (22. Nov 2011)

Klasse MainGraphics, nur zur Darstellung der Frames... was auch so bleiben soll :

```
package graphics;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.text.DecimalFormat;
import java.util.Random;
import snake.Snake;


import javax.swing.JPanel;

@SuppressWarnings("serial")
public class MainGraphics extends JPanel implements Runnable{
	
	long nTime=1;
	long oTime=2;
	double timeDif;
	double fps = 0;
	
	double frames;
	
	int[]y;
	int[]x;
	int z;
	
	Color cl = Color.MAGENTA;
	
	Snake snake= new Snake();
	Random rand = new Random();
	
	int i=0;
	Thread animator;
	DecimalFormat df = new DecimalFormat("#0.00");
	
	public MainGraphics()
	{
		setBackground(Color.BLACK);
		setDoubleBuffered(true);
		isFocusable();
	}

    public void addNotify() {
        super.addNotify();
        animator = new Thread(this);
        animator.start();
    }
	
	public void paint(Graphics g){
		super.paint(g);
		
		Graphics2D g2d = (Graphics2D)g;
		g2d.setColor(Color.RED);
		g2d.drawRect(snake.getx(), snake.gety(),5, 5);
		g2d.setColor(cl);
		g2d.drawString("FPS: "+frames, 0,10 );
	}
	
	public void change(){
		
	}
	
	private double getFPS(){
		if(i>=12){
			i=0;
			fps=12.0/timeDif;
			fps=Math.round(fps*100);
			fps=fps/100;
			timeDif=0;
		}
		i++;
		oTime=nTime;
		nTime=System.nanoTime();
		timeDif+=(nTime-oTime)/1000000000.0;
		  
		
		return fps;
	}
	
	public void run() 
	{	
		while(true)
		{		
			frames=getFPS();
			
			try {
				Thread.sleep(6);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
							
			repaint();
		}
	}
}
```

Klasse Window, zum erstellen des Fensters, und zum starten des Programms, sollte auch eigentlich so bleiben 

```
package graphics;

import javax.swing.JFrame;

@SuppressWarnings("serial")
public class Window extends JFrame
{
	MainGraphics mg = new MainGraphics();
	
	public Window()
	{
		add(mg);
		setTitle("Snake");
		setDefaultCloseOperation(EXIT_ON_CLOSE);
		setVisible(true);
		setSize(300,310);
		setLocationRelativeTo(null);
		setResizable(false);
		
	}
	public static void main (String[] args){
		new Window();	
	}
		
}
```

Klasse Snake, war gedacht zum Verarbeiten der Eingaben und für alle anderen Berechnungen zur Schlange:

```
package graphics;

import javax.swing.JFrame;

@SuppressWarnings("serial")
public class Window extends JFrame
{
	MainGraphics mg = new MainGraphics();
	
	public Window()
	{
		add(mg);
		setTitle("Snake");
		setDefaultCloseOperation(EXIT_ON_CLOSE);
		setVisible(true);
		setSize(300,310);
		setLocationRelativeTo(null);
		setResizable(false);
		
	}
	public static void main (String[] args){
		new snake.Snake();
		new Window();	
	}
		
}
```

mehr kann ich dazu nicht sagen...
Leon


----------



## SlaterB (22. Nov 2011)

Klasse Snake erinnert mich stark an Klasse Window


----------



## Leon92 (22. Nov 2011)

Zu blöd für Copy und Paste 

Klass Snake:

```
package snake;

import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

import javax.swing.JPanel;

public class Snake extends JPanel implements Runnable,KeyListener{
	
	char input='R';
	int kCode;
	int x = 2;
	int y = 0;
	int i;
	public int[]xarr;
	public int[]yarr;
	
	
	public Snake()
	{
		Thread sn = new Thread(this);
		sn.start();
		setVisible(false);
		addKeyListener(this);
	}

	@Override
	public void paint(Graphics g){
		
	}
	
	@Override
	public void keyPressed(KeyEvent e)
	{
		kCode=e.getKeyCode();
		input='D';
		if(kCode==KeyEvent.VK_UP)	{input='U';}
		if(kCode==KeyEvent.VK_DOWN)	{input='D';}
		if(kCode==KeyEvent.VK_LEFT) {input='L';}
		if(kCode==KeyEvent.VK_RIGHT){input='R';}
	}
	public void keyReleased(KeyEvent e){}
	
	@Override
	public void keyTyped(KeyEvent e)
	{
		kCode=e.getKeyCode();
		
		if(kCode==KeyEvent.VK_UP)	{input='U';}
		if(kCode==KeyEvent.VK_DOWN)	{input='D';}
		if(kCode==KeyEvent.VK_LEFT) {input='L';}
		if(kCode==KeyEvent.VK_RIGHT){input='R';}
	}

	

	@Override
	public void run() 
	{
		while(true)
		{
			if(input=='U'){y-=1;}
			if(input=='D'){y+=1;}
			if(input=='L'){x-=1;}
			if(input=='R'){x+=1;}
			try {
				Thread.sleep(15);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	public int getx(){
		return x;
	}
	public int gety(){
		return y;
	}
}
```

wie gesagt, das wird dann alles noch um einiges komplexer besonders die Snake Klasse. Aber erstmal muss es ja im Ansatz funktionieren.


----------



## SlaterB (22. Nov 2011)

was ist jetzt eigentlich die Frage?
ich finde nach wie vor dass der Standard funktionierten könnte,
also Snake braucht nicht von JPanel zu erben, 
sondern MainGraphics führt irgendwann bei Gelegenheit
this.addKeyListener(snake);
aus,

schon fertig (*), schon hat das normale in der GUI sichtbare JPanel einen KeyLister, der hier eine separate Klasse ist,
das normalste der Welt

(*) von weiteren Problemen wie dem angesprochenen Focus abgesehen


----------



## Leon92 (22. Nov 2011)

Jetzt machts Klick.
Kann ich den Keylistener auch in der Klasse Window hinzufügen ?
Und wo muss ich dann die Methoden einfügen also KeyPressed etc. bstmt in die Klasse Snake oder ?

Ach und wegen dem Focus... da hatte ich warscheinlich irgendwo mal was verkehrt verstanden.

Vielen Dank schonmal


----------



## SlaterB (22. Nov 2011)

in Snake hast du doch schon KeyListener-Methoden,

vieles andere ist auch denkbar, ja, nur muss zwingend die beherrschende Komponente, also das JPanel, den KeyListener erhalten


----------



## Leon92 (23. Nov 2011)

alos heißt das im Klartext das ich den Keylistener eben nicht in der Klasse Window einbinden kann ?!


----------



## SlaterB (23. Nov 2011)

das kann man nicht so klar beantworten weil 'in einer Klasse X einbinden' kein fester Fachbegriff ist,
da muss man alles in genauen Details beschreiben und abwägen,
du verzichtestet darauf wie immer zugunsten ungenauer Fragen, also bleibt es bei mir hängen.. :

möglich ist, die bisherige Umsetzung auch von der Windowklasse anzustoßen, wie eigentlich immer jeder Code letzlich von überall erfolgen kann:
in Window [c]mg.addKeyListener(mg.snake);[/c] bzw. ähnlich

nicht möglich bzw. hier erfolgreich ist dagegen, dem JFrame-Objekt bzw. sonst irgendeiner Komponente != mg den KeyListener zuzuweisen

bisschen mehr möglich wäre mit KeyBindings, JFrame fällt dazu aber aus, dafür z.B. das ContentPane,
KeyBindings reagieren auch auf Eingaben in Unterkomponenten


----------



## Leon92 (23. Nov 2011)

Also ich habs mal versuch mit

```
this.addKeyListener(snake.Snake())
```
 in der Klasse Main Graphics aber da hat Eclipse schon wieder rumgemekert ich soll die Methode KEypressed etc einbinden.

Hab ich dann auch gemacht. Aber den eigentlich Effekt hats nicht erzielt dh die Steuerung ging nicht...

Jetzt hätte ich aber mal nochne Frage : was bedeutet eigentlich beherschende Komponente, und kann man irgendwie beeinflussen welche das ist. ??


----------



## SlaterB (23. Nov 2011)

wie von Anfang an gesagt solltest du dem Panel nach setVisible(true) noch den Focus geben

wenn du aber Fehlermeldungen hast kannst du aber eigentlich nicht gleichzeitig von 'Steuerung ging nicht' erzählen,
mit Fehlermeldungen läuft doch kein Programm..

[c]snake.Snake()[/c] klingt generell auch nach merkwürdigen Code, gehört noch ein [c]new[/c] dazu?
du müsstest irgendwann wohl einen aktuelles vollständigen Code-Stand posten, vorher sind nicht alle Details aufzufinden


----------



## Michael... (23. Nov 2011)

Ich habe zwar immer noch nicht verstanden, warum das unbedingt getrennt werden soll. Schließlich ist ein GUI dem Namen nach eine Benutzerschnittstelle die graphisch mit dem Benutzer kommuniziert - das gilt für beide Richtungen.
Wenn es unbedingt getrennt werden soll, könnte man eine JComponent nehmen und mit KeyBindings arbeiten. Diese Komponente könnte man beliebig im Fenster platzieren und es würde auf die Tastatur reagiert werden, egal ob die Komponente den Fokus (der für einen KeyListener notwendig ist) hat oder nicht.


----------



## Leon92 (23. Nov 2011)

also, der Fehler war ja nur, dass in der Methode MainGraphics nicht die Methoden keyPressed usw weiter waren.

Also habe ich diese dann eingefügt, dann hats gestartet und das Programm hat immer nochnicht auf die eingaben reagiert.
mit: 

```
Snake snake = new Snake();

und dann....

this.addKeyListener(snake);
```

in der Methode Maingraphics hatt eich das eingefügt. 
so jetzt hab ich mal noch versucht der Klasse MG den Focus mit "this.requestFocus()" zu geben aber ging immer noch nicht


----------



## SlaterB (23. Nov 2011)

was steht in erster Antwort?


SlaterB hat gesagt.:


> den KeyListener kannst du dem JPanel zuordnen, dann aber requestFocus() *nach setVisible(true)* nicht vergessen,



was du gemacht hast ist ohne kompletten Code natürlich nur zu erahnen,
der Konstruktor von MainGraphics wäre jedenfalls zu früh,
evtl. beginn der run-Methode, besser aber im Window-Konstruktor eben nach setVisible(true)


----------



## Michael... (23. Nov 2011)

Einmal versuch ich es noch: KeyBindings

Warum Dein Code nicht funktioniert, kann an vielem liegen. Dazu müsste man den gesamten Code möglichst als KSKB sehen.
Bei mir funktionierts:


```
JFrame frame = new JFrame();
		frame.setBounds(0, 0, 200, 200);
		JPanel panel = new JPanel();
		panel.addKeyListener(new KeyAdapter() {
			public void keyPressed(KeyEvent e) {
				System.out.println("pressed");
			}
			public void keyReleased(KeyEvent e) {
				System.out.println("released");
			}
		});
		frame.add(panel);
		frame.setVisible(true);
		panel.requestFocus();
```


----------



## Leon92 (23. Nov 2011)

```
package graphics;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.text.DecimalFormat;
import java.util.Random;
import snake.Snake;


import javax.swing.JPanel;

@SuppressWarnings("serial")
public class MainGraphics extends JPanel implements Runnable,KeyListener{
	
	long nTime=1;
	long oTime=2;
	double timeDif;
	double fps = 0;
	
	double frames;
	
	int[]y;
	int[]x;
	int z;
	
	Color cl = Color.MAGENTA;
	
	Snake snake= new Snake();
	Random rand = new Random();
	
	int i=0;
	Thread animator;
	DecimalFormat df = new DecimalFormat("#0.00");
	
	public MainGraphics()
	{
		setBackground(Color.BLACK);
		setVisible(true);
		this.requestFocus();
		setDoubleBuffered(true);
		this.addKeyListener(snake);
	}

    public void addNotify() {
        super.addNotify();
        animator = new Thread(this);
        animator.start();
    }
	
	public void paint(Graphics g){
		super.paint(g);
		
		Graphics2D g2d = (Graphics2D)g;
		g2d.setColor(Color.RED);
		g2d.drawRect(snake.getx(), snake.gety(),5, 5);
		g2d.setColor(cl);
		g2d.drawString("FPS: "+frames, 0,10 );
		g2d.drawString("FUUUUURTZ", 0, 20);
	}
	
	public void change(){
		
	}
	
	private double getFPS(){
		if(i>=12){
			i=0;
			fps=12.0/timeDif;
			fps=Math.round(fps*100);
			fps=fps/100;
			timeDif=0;
		}
		i++;
		oTime=nTime;
		nTime=System.nanoTime();
		timeDif+=(nTime-oTime)/1000000000.0;
		  
		
		return fps;
	}
	
	public void run() 
	{	
		while(true)
		{		
			frames=getFPS();
			
			try {
				Thread.sleep(6);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
							
			repaint();
		}
	}
}
```

so wie es jetzt ist bringt die Methode wie gesagt den Fehler das die Mehtoden Keypressed etc. fehlen, aber die hatte ich Probe halber mal eingefügt. Dann hats zwar gestartet aber die Steuerung ging nicht.

@Michael...

bitte nicht so verstehen als wäre ich zu faul oder will deine Hilfe nicht aber alles was ich bisher zu KeyBindings gesehen hab sah mir noch komplizierter aus als das hier...

ach und in der Window Klasse hatte ichs auch schon mit ....
	
	
	
	





```
MainGraphics mg = new MainGraphics();
snake.Snake snake = new snake.Snake();

mg.addKeyListener(snake);
```

... erfolglos probiert


----------



## Michael... (23. Nov 2011)

Dein Code - zumindest der hier gepostete - schaut etwas wirr aus.
Warum implementiert MainGraphics KeyListener? der soll/wird doch bereits von Snake implementiert.
MainGraphics enthält bereits ein Snake Objekt welches als KeyListener registriert wird. Warum das komische 
	
	
	
	





```
snake.Snake snake = new snake.Snake();
```
 in Window (schaut sehr komisch aus)
Warum überschreibst Du die Methode 
	
	
	
	





```
addNotify()
```
?


----------



## Leon92 (23. Nov 2011)

also das snake.Snake() in Window WAR da nur mal weil ich auch versucht hab den Keylistener von der KLASSe window einzubinden und weil ja SNake in einem anderen Package, nämlich im Package "snake" ist.

Und das addNotify muss so sein weil sonst die Methode run nicht startet wenn ich den ein neues Objekt von MG öffne.


----------



## SlaterB (23. Nov 2011)

setVisible(true);
am MainGraphics-Panel aufzurufen bringt nichts, es geht um das JFrame, die GUI muss auf dem Bildschirm sichtbar sein


----------



## Leon92 (23. Nov 2011)

also... 
in der Window Klasse habe ich setVisible(true); egal ich poste die jetzt einfach nochmal...


```
package graphics;

import javax.swing.JFrame;

@SuppressWarnings("serial")
public class Window extends JFrame 
{
	MainGraphics mg = new MainGraphics();
	
	public Window()
	{
		add(mg);
		setTitle("Snake");
		setDefaultCloseOperation(EXIT_ON_CLOSE);
		setVisible(true);
		setSize(300,310);
		setLocationRelativeTo(null);
		setResizable(false);
	}
	public static void main (String[] args){
		new Window();	
	}
		
}
```

soll ich da jetzt in der Klasse, nochmal setvisible true für MG machen oder reicht das so ??


----------



## SlaterB (23. Nov 2011)

wie kommst du auf die Idee setVisible(true) für ein JPanel aufzurufen?
bringe ich ich dazu? 

jedes Swing-Programm hat standardmäßig genau einen setVisible(true)-Aufruf, aufs JFrame,
ein ziemlich entscheidender Moment offensichtlich, ab dann ist die GUI sichtbar,

von Anfang an sage ich dir, dass NACH diesem Moment der Focus zu setzen ist,
wenn du mir dahingehend vertraust, dann bringt es offensichtlich nichts, im Konstruktor von MainGraphics was zum Focus zu tun,
denn das Objekt wird ja früher erstellt, die zeitliche Reihenfolge allen Codes sollte man schon einigermaßen im Blick haben,

schreibe nun in Zeile 16 von Window
mg.requestFocus();
..
(und nein, dann muss es nicht zwingend funktionieren, vielleicht klappt es, 
ansonsten muss der Code weiter angeschaut werden, ich kann es erst morgen testen)


----------



## Leon92 (23. Nov 2011)

also ich habe jetzt aus MG alles mit setVisible und requestFocus gelöscht und in Window mg.requestFocus geschrieben und siehe da : ES FUNKTIONIERT !!!!!!!!!!!!!

also tausendmal Dankeschön.

Aber nochmal eine Frage zum Verständnis. setVisible und requF() wird in einer GUI immer nur einmal aufgerufen ?!?!?

ich dachte nämlcih das muss ich für jedes Swing element machen....


----------



## SlaterB (23. Nov 2011)

deine GUI funktionierte doch vorher schon mit nur einem setVisible(true),
requestFocus() ist bisschen ne andere Frage, Focus kann man auch verlieren, aber sofern du nirgendwo rumklickst/ das Fenster wechselst wird es schon laufen,
langfristig ist das eh nicht schön


----------

