Thread fängt immer "von vorne" an?
*Markus
- java
Hallo,
zum besseren Verständnis habe ich aus einer Game-Engine ein Testprogramm abgeleitet, welches fette Punkte auf dem Ausgabefenster zeichnen soll.
Dabei passiert aber folgendes:
Minimiere, maximiere, oder verschiebe ich das Fenster, fängt der Thread wieder von vorne an, d.H. alle bisher gezeichneten Punkte sind verschwunden. Zuerst dachte ich, es läge daran, dass ich den Thread stoppte und wieder startete, was auch logisch erscheint. Danach verknüpfte ich die WindowListener-Methoden mit Klassen-Methoden, und zwar führte ich z.B. eine "isPaused"-Variable ein, die den Thread weiterlaufen lässt, aber nur das Punktsetzen aussetzt, solange das Fenster nicht den Fokus hat.
Kommt das Fenster wieder in den Fokus, läuft der Thread zwar immer noch, und die Punktsetzung fängt wieder an, aber die alten Punkte sind trotzdem weg. Woran liegt das?
Ach, und noch etwas: Ich versuche beim Schließen des Fensters den Thread ordnungsgemäß zu beenden, indem ich die Schleifenvariable "running" einfach auf false setze. Dabei will ich mir zur Überprüfung einen Text ausgeben lassen (unter der run()-Methode), aber der Text wird nie ausgegeben. Kann ich trotzdem sicher sein, dass der Thread ordnungsgemäß beendet wurde?
Liegt es vielleicht daran, dass im Moment des Schließens das Fensterobjekt bereits vernichtet wird, und der Text deswegen nicht ausgegeben werden kann?
Hier die verwendeten Klassen:
-------PaintGUI
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Toolkit;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import javax.swing.JFrame;
public class PaintGUI extends JFrame implements WindowListener {
private PaintPanel paintpanel;
public void drawGUI() {
paintpanel = new PaintPanel();
paintpanel.setLayout(new FlowLayout());
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setTitle("PaintGUI");
setSize(new Dimension(Toolkit.getDefaultToolkit().getScreenSize()));
add(paintpanel);
addWindowListener(this);
setVisible(true);
}
public void windowActivated(WindowEvent e) {
// TODO Auto-generated method stub
paintpanel.unpauseAnimator();
}
public void windowClosed(WindowEvent e) {
// TODO Auto-generated method stub
paintpanel.stopAnimator();
}
public void windowClosing(WindowEvent e) {
paintpanel.stopAnimator();
}
public void windowDeactivated(WindowEvent e) {
// TODO Auto-generated method stub
paintpanel.pauseAnimator();
}
public void windowDeiconified(WindowEvent e) {
// TODO Auto-generated method stub
}
public void windowIconified(WindowEvent e) {
// TODO Auto-generated method stub
}
public void windowOpened(WindowEvent e) {
// TODO Auto-generated method stub
}
}
_____PaintPanel____________
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import javax.swing.JPanel;
public class PaintPanel extends JPanel implements Runnable {
private final int DOTSIZE = 10;
private Image image = null;
private Graphics g;
private boolean running = false;
private Thread animator;
private boolean isPaused = false;
public void run() {
running = true;
while(running) {
if (!isPaused)
drawPoint();
try {
Thread.yield(); // give another thread a chance to run
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Animation successfully stopped");
}
public void addNotify() {
// only start the animation once the JPanel has been added to the JFrame
super.addNotify();
startAnimator();
}
public void startAnimator() {
if (animator == null || !running) {
animator = new Thread(this);
animator.start();
}
}
public void stopAnimator() {
running = false;
}
public void pauseAnimator() {
isPaused = true;
}
public void unpauseAnimator() {
isPaused = false;
}
public void drawPoint() {
int screenWidth = Toolkit.getDefaultToolkit().getScreenSize().width;
int screenHeight = Toolkit.getDefaultToolkit().getScreenSize().height;
int xPosition = (int)(Math.random() * screenWidth);
int yPosition = (int)(Math.random() * screenHeight);
if (image == null) {
image = createImage(screenWidth, screenHeight);
if (image == null)
return;
else
g = getGraphics();
}
g.setColor(Color.BLACK);
g.drawOval(xPosition, yPosition, DOTSIZE, DOTSIZE);
g.fillOval(xPosition, yPosition, DOTSIZE, DOTSIZE);
}
}
___________MAIN___________
public class PaintMain {
/**
* @param args
*/
public static void main(String[] args) {
PaintGUI gui = new PaintGUI();
gui.drawGUI();
}
}
Markus
Hallo,
Minimiere, maximiere, oder verschiebe ich das Fenster, fängt der Thread wieder von vorne an, d.H. alle bisher gezeichneten Punkte sind verschwunden.
Bist du sicher, daß das eine mit dem anderen zu tun hat? Ich glaube
nicht, daß der Thread neu gestartet wird. Der Code läßt mich das
zumindest nicht annehmen, soweit ich ihn mir angesehen habe.
Vielmehr liegt das Problem der verschwundenen Punkte wohl vielmehr
darin, daß du in "flüchtigen" Grafikspeicher zeichnest. Wenn du direkt
in Panel zeichnest, indem du dir das Graphics-Objekt davon holst,
zeichnest du nur in den der Komponente zugeordneten Grafikspeicher.
Sobald die Komponente bewegt oder verdeckt wird, geht auch dieser
Inhalt verloren. Er muß neu gezeichnet werden.
Stattdessen könntest du einen Backbuffer verwenden, indem du z.B. ein
BufferedImage erzeugt und nur in diese zeichnest. Sobald du mit dem
Zeichen fertig bist, gibst du das BufferedImage über g.drawImage(...)
auf den Panel aus.
Wenn nun Teile oder das gesamte Panel neu gezeichnet werden muß, weil
es verdeckt wurde, kann direkt dieses BufferedImage nochmal ausgegeben
werden, ohne daß erst wieder x Punkte neu gezeichnet werden müssen.
Dies kannst du in der zu überschreibenden Methode "paint(Graphics)"
tun.
Das Backbuffer-Konzept dürfte auch in den meisten Java-Büchern zumindest
grob erklärt sein.
Ach, und noch etwas: Ich versuche beim Schließen des Fensters den Thread ordnungsgemäß zu beenden, indem ich die Schleifenvariable "running" einfach auf false setze. Dabei will ich mir zur Überprüfung einen Text ausgeben lassen (unter der run()-Methode), aber der Text wird nie ausgegeben. Kann ich trotzdem sicher sein, dass der Thread ordnungsgemäß beendet wurde?
Naja, wenn du die Default Close Operation auf "Exit" setzt, dann
schafft es der Thread nicht mehr rechtzeitig überhaupt noch etwas
zu tun, bevor die Anwendung "hart" beendet wird.
Lass mal diese Zeile weg:
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Stattdessen rufst du dann z.B. "System.exit(0)" in PaintPanel.run()
als letzte Anweisung auf. Dann dürfte noch alles, was nach der
while-Schleife kommt, ausgeführt werden.
Liegt es vielleicht daran, dass im Moment des Schließens das Fensterobjekt bereits vernichtet wird, und der Text deswegen nicht ausgegeben werden kann?
Nicht das Fenster-Objekt, sondern die gesamte Anwendung.
Übrigens, der Aufruf "Thread.yield()" dürfte an dieser Stelle reichlich
sinnlos sein, weil du den Thread ohnehin schlafen legst und somit
andere Threads ausgeführt werden können.
Gruß
Slyh
Hallo,
Stattdessen könntest du einen Backbuffer verwenden, indem du z.B. ein
BufferedImage erzeugt und nur in diese zeichnest. Sobald du mit dem
Zeichen fertig bist, gibst du das BufferedImage über g.drawImage(...)
auf den Panel aus. [...]
Du hast recht. Auf diese Weise funktioniert es.
Lass mal diese Zeile weg:
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Stattdessen rufst du dann z.B. "System.exit(0)" in PaintPanel.run()
als letzte Anweisung auf. Dann dürfte noch alles, was nach der
while-Schleife kommt, ausgeführt werden.
Auch hier hast du recht.
Übrigens, der Aufruf "Thread.yield()" dürfte an dieser Stelle reichlich
sinnlos sein, weil du den Thread ohnehin schlafen legst und somit
andere Threads ausgeführt werden können.
Ok, ich wusste nicht genau, was der Thread im Schlafzustand macht und ob er es deswegen zulässt, andere Threads aufzurufen.
Hier nun meine Lösung:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.LayoutManager;
import java.awt.Toolkit;
import javax.swing.JPanel;
public class PaintPanel extends JPanel implements Runnable {
private final int DOTSIZE = 10;
private Image image = null;
private Graphics g;
private int screenWidth;
private int screenHeight;
private boolean running = false;
private Thread animator;
private boolean isPaused = false;
public PaintPanel(LayoutManager manager) {
setLayout(manager);
screenWidth = Toolkit.getDefaultToolkit().getScreenSize().width;
screenHeight = Toolkit.getDefaultToolkit().getScreenSize().height;
}
public void run() {
running = true;
while(running) {
if (!isPaused) {
bufferScreen();
paintScreen();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Animation successfully stopped");
System.exit(0);
}
public void addNotify() {
// only start the animation once the JPanel has been added to the JFrame
super.addNotify();
startAnimator();
}
public void startAnimator() {
if (animator == null || !running) {
animator = new Thread(this);
animator.start();
}
}
public void stopAnimator() {
running = false;
}
public void pauseAnimator() {
isPaused = true;
}
public void unpauseAnimator() {
isPaused = false;
}
//Grafik muss zuerst gepuffert werden, damit beim Verschieben des Fensters
//diese nicht wieder verschwindet.
public void bufferScreen() {
int xPosition = (int)(Math.random() * screenWidth);
int yPosition = (int)(Math.random() * screenHeight);
if (image == null) {
image = createImage(screenWidth, screenHeight);
if (image == null)
return;
else
g = image.getGraphics();
}
g.setColor(Color.BLACK);
g.drawOval(xPosition, yPosition, DOTSIZE, DOTSIZE);
g.fillOval(xPosition, yPosition, DOTSIZE, DOTSIZE);
}
public void paintScreen() {
Graphics graphic;
try {
graphic = this.getGraphics();
if ((graphic != null) && (image != null))
graphic.drawImage(image, 0, 0, null);
// Sync the display on some systems.
// (on Linux, this fixes event queue problems)
Toolkit.getDefaultToolkit().sync();
graphic.dispose();
}
catch (Exception e) // quite commonly seen at applet destruction
{ System.out.println("Graphics error: " + e); }
} // end of paintScreen()
}
Und danke für die rasche Lösung. Jetzt ist mir auch klar, warum in der Game-Engine dies mit zwei Methoden gelöst wurde, eben wegen den Doppelbuffer-Effekt.
Markus