# Graphics Objekt -  aus anderer Klasse aufrufen / übergeben



## lal000r (25. Okt 2011)

Hallo zusammen,

Ich ecke momentan mit der Swing-paintComponent-Methode immer wieder an. 
Zunächst habe ich eine Klasse, die von JPanel abgeleitet ist und die paintComponent-Methode überschreibt.
Innerhalb dieser paintComponent-Methode wird das Graphics-Objekt an diverse Methoden übergeben, damit diese ebenfalls zeichnen können. (Alles innerhalb dieser Klasse)

Nun habe ich allerdings eine weitere Klasse (Animator), die von Thread abgeleitet ist und das genannte Panel übergeben bekommt. Innerhalb dieser Klasse sollen Spielfiguren gezeichnet werden, die über das Panel "wandern" sollen. Dazu lasse ich die Zeichenvorgänge innerhalb des Animationsthreads immer mal wieder für einige Millisekunden schlafen, damit eine Animation entsteht.
Das Problem ist jetzt nur folgendes:

Zunächst erzeuge ich in der Panel-Klasse innerhalb der paintComponent einen neuen Animator-Thread, der dann die Animationsmethoden in der run()-Methode ausführt. Das funktioniert auch soweit.
ABER.. die Methode in der run() braucht zum Zeichnen das Graphics-Object. 
Nur wie komme ich an dieses Objekt außerhalb der Panelklasse? Zum Testen habe ich im Konstruktor des Animators das Panel mit übergeben und ziehe mir das Graphics-Objekt mit panel.getGraphics(). 

Funktionieren tut dies, allerdings ist mir bekannt, dass man das nicht so machen sollte. Außerdem führt es zum Flackern bei der Animation. 
Wie komme ich also von außerhalb an das Graphics-Objekt, bzw. wie initiiere ich eine Methode von außerhalb die das Graphics-Objekt benötigt?

Wenn es gewünscht ist, kann ich auch gern etwas Code posten. Vielen Dank schonmal!


----------



## SlaterB (25. Okt 2011)

grundsätzlich (später das große Aber)
paintComponent startet keinen Thread, das können andere machen,

der Thread muss sich für Graphics nicht interessieren, seine Berechnungen können unabhängig vom Zeichnen sein,
er bestimmt den aktuellen Zeitpunkt, berechnet meinetwegen Positionen usw., und dann folgt nur ein Befehl: repaint()
-> schon ist auf sauberen Wege irgendwann die paint-Methode dran, die nimmt sich was an Informationen nötig ist und malt exakt den aktuellen Zustand

dabei ist evtl. an Synchronisation oder andere Absprache zu denken, damit nicht doch der Thread gleichzeitig weiterarbeitet und bisschen was während des Zeichnens verändert

-----

das große Aber: Flackern bei Hochleistungs-Zeichnen ist mit diesem Verfahren glaube ich nicht unbedingt zu vermeiden,
daher sind auch andere Varianten nicht ganz auszuschließen

eine einfache, auch relativ saubere möchte ich noch nennen: der Thread malt auf ein Image-Objekt, dessen getGraphics() kann gerne herumgereicht werden,
paintComponent malt nur jederzeit das aktuellste Bild, gegen Flackern hilft das aber im Extremfall glaube ich auch nicht


ein Link, allerdings auch auf einfachem Niveau, unter 'Thread rechnet, paint malt'-Verfahren ohne dass Flacker-Probleme eine Rolle spielen, nehme ich an:
http://www.java-forum.org/spiele-multimedia-programmierung/54795-quaxli-2d-spiele-tutorial.html


----------



## GUI-Programmer (25. Okt 2011)

SlaterB hat gesagt.:


> der Thread malt auf ein Image-Objekt, dessen getGraphics() kann gerne herumgereicht werden



Das wollte ich auch gerade sagen. Hier nen kleines Beispiel:

```
public class Xy extends JPanel {
	protected BufferedImage drawingImage;
	
	public Xy() {
		setPreferredSize(new Dimension(800, 600));
		drawingImage = new BufferedImage(getWidth(), getHeight(), BufferedImage.TRANSLUCENT);
		Graphics2D g2 = drawingImage.createGraphics();
		g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f));
		g2.fillRect(10, 10, 100, 100);
		// Du solltest bei deiner Thread-Klasse sonen Konstruktor erstellen, um eben
		// das Bild übergeben zu können, dass dann in der run() verändert werden kann
		DeinThread t = new DeinThread(drawingImage);
		t.start();
	}

	protected void paintComponent(Graphics g) {
        if (g instanceof Graphics2D) {
        	Graphics2D g2 = (Graphics2D)g;
        	g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f));
        	g2.drawImage(drawingImage, 0, 0, this);
        }
    }
}
```

Ach und ich glaube, dass es sinnvoller wäre, wenn deine Thread-Klasse nicht von Thread sondern von SwingWorker erbt.


----------



## lal000r (25. Okt 2011)

Danke für Eure Vorschläge, vielleicht muss ich dazu noch sagen, dass ich noch nicht sonderlich erfahren auf dem Gebiet der Anwendungsentwicklung bin, vor allem was die Umsetzung von Threads angeht, ist das meine Premiere 



SlaterB hat gesagt.:


> der Thread muss sich für Graphics nicht interessieren, seine Berechnungen können unabhängig vom Zeichnen sein,
> er bestimmt den aktuellen Zeitpunkt, berechnet meinetwegen Positionen usw., und dann folgt nur ein Befehl: repaint()
> -> schon ist auf sauberen Wege irgendwann die paint-Methode dran, die nimmt sich was an Informationen nötig ist und malt exakt den aktuellen Zustand



Das "Problem" ist nur, dass die entsprechende Methode anhand der Panelgröße die Berechnungen durchführt. (Deswegen übergebe ich ja das Panel)
Ich bin mir ziemlich sicher, dass das momentan vorhandene Flackern durch das getGraphics() ausgelöst wird, weil ich wegen dem Problem hier schonmal einen Thread aufgemacht hatte. 

GUI-Programmer:
Ich versuche zu verstehen, welchen Ansatz du mir da erläutern möchtest. Ich glaube, du erzeugst ein neues (eigenes) Graphics-Objekt, mit dem du dann malst?

Ich glaube, es wäre besser, wenn ich mal ein bisschen Code poste, damit wir nicht aneinander vorbeireden.

Eigentlich baut in der Panel-Klasse vieles auf den booleschen Variablen auf, die von Controllern gesetzt werden. Je nach Wert, werden die entsprechenden Methoden beim repaint() dann ausgeführt oder auch nicht.

Klasse des Panels:

```
public class BoardGraphic extends JPanel implements Observer {

	private static final long serialVersionUID = 1L;
	ActivePiece activepiece = null;
	boolean isDragging, isPressed, moveExecuted = false;
	final int CELLSIZE = 80;

	public BoardGraphic() {
		this.setPreferredSize(new Dimension(650, 600));
		this.setBackground(new Color(230, 230, 250));
		this.addMouseListener(new Mouselistener(this));
		this.addMouseMotionListener(new MousemotionListener(this));
		new BoardObserver(this);
	}
	
	public void paintComponent(Graphics g) {
		super.paintComponent(g);
		int xStart = this.getWidth() / 2;
		int yStart = this.getHeight() / 2;
		this.setFont(g);
		
		//Spielbrett
		for (int i = 0; i < 7; i++) {
			for (int j = 0; j < 7; j++) {
				if (i % 2 == 0) {
					if (j % 2 == 0) {
						g.setColor(new Color(105, 105, 105));
					} else {
						g.setColor(new Color(240, 230, 140));
					}
				} else {
					if (j % 2 == 0) {
						g.setColor(new Color(240, 230, 140));
					} else {
						g.setColor(new Color(105, 105, 105));
					}
				}
				g.fillRect(xStart - 280 + j * CELLSIZE, yStart - 280 + i * CELLSIZE, CELLSIZE, CELLSIZE);		
			}
		}
		// Zeilenbeschriftungen links
		for (int i = 1; i < 8; i++) {
			g.drawString(Integer.toString(i), xStart - 320, (yStart - 230) + (i - 1) * CELLSIZE);
		}
		// Zeilenbeschriftungen rechts
		for (int i = 1; i < 8; i++) {
			g.drawString(Integer.toString(i), xStart + 305, (yStart - 230) + (i - 1) * CELLSIZE);
		}
		//Spaltenbeschriftungen oben
		for (int i = 1; i < 8; i++) {
			char zwischen = (char) (i+96);
			g.drawString(Character.toString(zwischen), xStart-250 + (i-1)* CELLSIZE, yStart-310 );
		}
		//Spaltenbeschriftungen unten
		for (int i = 1; i < 8; i++) {
			char zwischen = (char) (i+96);
			g.drawString(Character.toString(zwischen), xStart-240 + (i-1)* CELLSIZE, yStart+320 );
		}
		
		drawPieces(g);
		IO.println("Repaint");
	
		
		if (this.isPressed && this.activepiece != null) {
			drawStartField(g);
			drawPossibleFields(g);
			
		}
		if (this.isDragging && this.activepiece != null){
			drawStartField(g);
			drawPossibleFields(g);
			drawHoveringField(g);
			drawPieceWhileDragging(this.activepiece.getDragX(), this.activepiece.getDragY(), g);
			}
		
		if (this.moveExecuted){
			IO.println("Animator");
			Animator anim = new Animator(this);
			anim.start()

		}
	}
```

Klasse Animator: (Ganz unten in der run mache ich das getGraphics)

```
public class Animator extends Thread {

	BoardGraphic panel;
	
	public Animator(BoardGraphic panel){
		this.panel=panel;
		
	}
	
	public void animateMove(int releasedX, int releasedY, Graphics g) {

		int xStart = panel.getWidth() / 2 - 280
				+ (panel.getActivepiece().getCol() * panel.CELLSIZE);
		int yStart = panel.getHeight() / 2 - 280
				+ (panel.getActivepiece().getRow() * panel.CELLSIZE);
		// Bestimmen der Model-Zielfelder der aktiven Spielfigur
		int[] pos = panel.getPositionOnBoard(releasedX, releasedY);
		int xEnd = panel.getWidth() / 2 - 280 + (pos[1] * panel.CELLSIZE);
		int yEnd = panel.getHeight() / 2 - 280 + (pos[0] * panel.CELLSIZE);

		// Horizontaler Abstand ist größer als vertikaler Abstand, zunächst horizontale,
		// dann vertikale Animation durchführen
		if (Math.abs(xEnd - xStart) > Math.abs(yEnd - yStart)) {
			verticalMove(yStart, yEnd, horizontalMove(xStart, xEnd, yStart, g), g);
		}
		
		// Vertikaler Abstand ist größer als horizontaler Abstand, zunächst vertikale, 
		// dann horizontale Animation durchführen
		else {
			horizontalMove(xStart, xEnd, verticalMove(yStart, yEnd, xStart, g), g);
		}
		panel.repaint();
		panel.setMoveExecuted(false);
	}
	
	//Animiert einen horizontalen Zug und gibt den aktuellen Wert von xStart zurück
	public int horizontalMove(int xStart, int xEnd, int yStart, Graphics g) {
		while (Math.abs(xEnd - xStart) > 0) {
			g.drawImage(panel.loadPieceImages(panel.activepiece.getPiece()), xStart,
					yStart, panel);
			if (xStart < xEnd) {
				xStart += 1;
			} else {
				xStart -= 1;
			}
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}panel.setMoveExecuted(false);
			panel.repaint();
		}
		return xStart;
	}
	//Animiert einen vertikalen Zug und gibt den aktuellen Wert von yStart zurück
	public int verticalMove(int yStart, int yEnd, int xStart, Graphics g) {
		while (Math.abs(yEnd - yStart) > 0) {
			g.drawImage(panel.loadPieceImages(panel.activepiece.getPiece()), xStart,
					yStart, panel);
			if (yStart < yEnd) {
				yStart += 1;
			} else {
				yStart -= 1;
			}
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}panel.setMoveExecuted(false);
			panel.repaint();
		}
		return yStart;
	}

	
	 public void run() {
		animateMove(panel.getActivepiece().getReleasedX(),panel.getActivepiece().getReleasedY(), panel.getGraphics());
	 }
	
}
```

Es mag ja auch sein, dass ich mit meinem Code völlig auf dem Holzweg bin  Danke für Eure Mühe!

Achja, danke für das Tutorial, ich werde es mir morgen mal genauer angucken.


----------



## lal000r (26. Okt 2011)

Achja, vielleicht sollte ich noch die Intention dessen bekanntgeben, weswegen ich versuche in einem extra Thread zu malen.

Der Zug der Spielfigur soll animiert erfolgen und das realisiere ich momentan so, dass ich nach jedem "Verschieben" der Figur nach x oder y den Thread für 20ms pausieren lasse. Wenn ich das in der Haupt-paintComponent-Methode versuche, hängt das komplette Spiel kurz und dann wird der Zug trotzdem blitzschnell ausgeführt.


----------



## SlaterB (26. Okt 2011)

das mag deine Intention sein, aber ist kein sinnvoller Grund,
paint malt einfach, fertig, leichter könnte es nicht sein,
paint malt weder mehrfach, noch wartet es, noch muss es die Zeit für Berechnungen nutzen, 
all das macht der Thread und wann immer der es für nötig hält zu zeichnen, z.b. vor den 20ms Warten, ruft er repaint() auf,
einfach und sauber, vom Flackern abgesehen


----------

