*Markus: Thread fängt immer "von vorne" an?

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

  1. 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

    1. 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