JFrame zeichnen - Ablauf wiederholt sich unerwünscht
lima-city → Forum → Programmiersprachen → Java
blocken
code
dauer
durchgang
fehler
fenster
import
klasse
liste
methode
multi
native
run
spielfeld
spielstein
stein
stift
swing
synchronisation
url
-
Hallo zusammen,
ich möchte ein Programm schreiben, welches erstmal eine beliebige Anzahl an Spielsteinen nebeneinander zeichnet. Dafür habe ich zwei Klassen geschrieben, einmal eine Klasse für das Frame und dann die App Klasse:
App:
public class App { public static void main(String[] args) { AppFrame frame = new AppFrame(); frame.setSize(980, 700); frame.setVisible(true); } }
AppFrame:
import java.awt.Color; import java.awt.Graphics; import javax.swing.JFrame; public class AppFrame extends JFrame { public AppFrame() { super(); setDefaultCloseOperation(EXIT_ON_CLOSE); } public void paint(Graphics stift) { int x = 100; int y = 100; int width = 100; int heigth = 100; for(int i = 0; i < 4; i++){ zeichneSpielstein(stift, x, y, width, heigth, Color.RED, Color.yellow, Color.GREEN, Color.blue); x += 100; try { Thread.sleep(700); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } } } public void zeichneSpielstein(Graphics stift, int x, int y, int width, int heigth, Color farbe1, Color farbe2, Color farbe3, Color farbe4) { stift.drawRect(x, y, width, heigth); // Oberes Dreieck int[] xcoords = { x + (width - 1), x + 1, x + (width / 2) }; int[] ycoords = { y + 1, y + 1, y + (heigth / 2) }; stift.setColor(farbe1); stift.fillPolygon(xcoords, ycoords, xcoords.length); // Rechtes Dreieck int[] xcoords2 = { x + (width - 1), x + (width - 1), x + (width / 2) }; int[] ycoords2 = { y + (heigth - 1), y + 1, y + (heigth / 2) }; stift.setColor(farbe2); stift.fillPolygon(xcoords2, ycoords2, xcoords2.length); // Unteres Dreieck int[] xcoords3 = { x + 1, x + width, x + (width / 2) }; int[] ycoords3 = { y + heigth, y + heigth, y + (heigth / 2) }; stift.setColor(farbe3); stift.fillPolygon(xcoords3, ycoords3, xcoords3.length); // Linkes Dreieck int[] xcoords4 = { x + 1, x + 1, x + (width / 2) }; int[] ycoords4 = { y + 1, y + (heigth - 1), y + (heigth / 2) }; stift.setColor(farbe4); stift.fillPolygon(xcoords4, ycoords4, xcoords4.length); stift.setColor(Color.BLACK); } }
In der Frame Klasse habe ich eine Mehtode definiert, die so einen Spielstein zeichnet. In der Methode "paint" habe ich dann eine Schleife die diesen Spielstein zum testen 4 mal nebeneinander zeichnen soll, mit einer kleinen Verzögerung dazwischen.
Das klappt auch alles soweit, außer das sich der Ablauf wiederholt, sobald einmal die 4 Spielsteine gezeichnet wurden. Das heißt er zeichnet die 4 Spielsteine, dann gehen auf einmal alle wieder weg und danach zeichnet er die Steine erneut. Und erst danach kann man das Programm beenden. Ich hab keine Idee woran das liegen könnte.
Ich hoffe mir kann jemand hier helfen.
Viele Grüße -
Diskutiere mit und stelle Fragen: Jetzt kostenlos anmelden!
lima-city: Gratis werbefreier Webspace für deine eigene Homepage
-
Innerhalb der paint-Methode sollst du niemals irgendetwas tun, was mehr als ganz kurz Zeit braucht. Grund: paint wird immer dann aufgerufen, wenn es was zu zeichnen gibt. Das kann z.b. sein, wenn das Fenster gerade von einem anderen überdeckt und jetzt wieder sichtbar war oder die Größe geändert wurde. Was du tun kannst: in ein BufferedImage zeichnen und dieses dann innerhalb von paint auf das JFrame zeichnen. Damit kannst du dir auch beim Zeichnen des BufferedImages Zeit lassen … du musst dann nur auf Synchronisation aufpassen.
In deinem Fall reicht es allerdings, wenn du dir in einer Liste merkst, wo überall ein Spielstein gezeichnet werden soll und du in paint diese Liste »durcharbeitest«.
Beitrag zuletzt geändert: 26.5.2015 16:47:50 von hackyourlife -
hackyourlife schrieb:
Was du tun kannst: in ein BufferedImage zeichnen und dieses dann innerhalb von paint auf das JFrame zeichnen. Damit kannst du dir auch beim Zeichnen des BufferedImages Zeit lassen ? du musst dann nur auf Synchronisation aufpassen.
Ok ich denke ich werde mal das mit dem BufferedImage versuchen. Ich habe dazu hier mal eine Anleitung gefunden:
http://javabeginners.de/Grafik/Auf_ein_Bild_zeichnen.php
Aber in dem Beispiel zeichnet er in ein bestehendes Bild. Wie kann ich denn jetzt sozusagen ein leeres BufferedImage erstellen?
Ich habs so probiert:
this.image = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB);
Also ich hab erstmal zum Testen alles aus dem Beispiel kopiert, nur die Zuweisung der image Variable in die obrige geändert.
Jetzt öffnet sich bei mir ein Fenster mit einem schwarzen Quadrat in der mitte. Das sollte ja nicht so sein oder?
EDIT:
Klappt doch, ich musste das BufferedImage nur größer machen.
Jetzt ist es also schwarz, wie kann ich das jetzt in Weiß machen?
Beitrag zuletzt geändert: 26.5.2015 17:43:43 von ultimate-bravery -
ultimate-bravery schrieb:
Ein weißes gefülltes Rechteck in der Größe des Image malen.
Klappt doch, ich musste das BufferedImage nur größer machen.
Jetzt ist es also schwarz, wie kann ich das jetzt in Weiß machen?
Ein neues BufferedImage hat in allen Pixeln den Farbwert 0, also schwarz. -
Ich habs jetzt erstmal so realisiert:
import java.awt.BorderLayout; import java.awt.Color; import java.awt.Font; import java.awt.Graphics; import java.awt.image.BufferedImage; import javax.swing.ImageIcon; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.border.LineBorder; public class AppFrame2 extends JFrame { private static final long serialVersionUID = 1L; private BufferedImage image; public AppFrame2() { this.image = new BufferedImage(980, 700, BufferedImage.TYPE_INT_RGB); int x = 100; int y = 100; int width = 100; int heigth = 100; for(int i = 0; i < 4; i++){ zeichneSpielstein(x, y, width, heigth, Color.RED, Color.yellow, Color.GREEN, Color.blue); x += 100; try { Thread.sleep(700); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } } } public void paint(Graphics g){ g.drawImage(image, 0, 0, null); } public void zeichneSpielstein(int x, int y, int width, int heigth, Color farbe1, Color farbe2, Color farbe3, Color farbe4) { Graphics stift = image.getGraphics(); stift.drawRect(x, y, width, heigth); // Oberes Dreieck int[] xcoords = { x + (width - 1), x + 1, x + (width / 2) }; int[] ycoords = { y + 1, y + 1, y + (heigth / 2) }; stift.setColor(farbe1); stift.fillPolygon(xcoords, ycoords, xcoords.length); // Rechtes Dreieck int[] xcoords2 = { x + (width - 1), x + (width - 1), x + (width / 2) }; int[] ycoords2 = { y + (heigth - 1), y + 1, y + (heigth / 2) }; stift.setColor(farbe2); stift.fillPolygon(xcoords2, ycoords2, xcoords2.length); // Unteres Dreieck int[] xcoords3 = { x + 1, x + width, x + (width / 2) }; int[] ycoords3 = { y + heigth, y + heigth, y + (heigth / 2) }; stift.setColor(farbe3); stift.fillPolygon(xcoords3, ycoords3, xcoords3.length); // Linkes Dreieck int[] xcoords4 = { x + 1, x + 1, x + (width / 2) }; int[] ycoords4 = { y + 1, y + (heigth - 1), y + (heigth / 2) }; stift.setColor(farbe4); stift.fillPolygon(xcoords4, ycoords4, xcoords4.length); stift.setColor(Color.BLACK); } }
Allerdings wartet er jetzt so lange bis alle Spielsteine gezeichnet wurden und dann öffnet er erst das Fenster. Aber man soll ja sehen wie die Spielsteine gezeichnet werden. Jemand ne Idee was ich ändern sollte? -
ultimate-bravery schrieb:
Du brauchst Multi-Threading. Also:
Jemand ne Idee was ich ändern sollte?
1) mach eine Klasse »Spielstein«, die eine Position (x, y), Größe (width, height) und 4 Farben enthält
2) Erstell eine List<Spielstein> steine = new ArrayList<Spielstein>();
3) Erstell einen Thread, der immer wartet und alle heiligen Zeiten mal in die steine-Liste einen neuen Spielstein legt; pass aber auf Synchronisation auf, sonst gibts da Probleme.
4) Der Thread sorgt für ein »repaint()«, wenn er einen Stein hinzugefügt hat, eventuell musst du da auch dafür sorgen, dass das vom richtigen Thread aus aufgerufen wird
5) paint() enthält ein »for(Spielstein s : steine) zeichneSpielstein(s);«
6) zeichneSpielstein(s) macht das selbe wie dein jetztiges zeichneSpielstein, nur nimmt es die Werte aus einem »Spielstein«-Objekt und nicht einzeln als Parameter.
Es wäre allerdings gut, wenn du eine grundlegende Ahnung von Multi-Threading hättest, denn da kann man viel falsch machen … und wenn du so vorgehst wie ich es beschrieben hab, dann sparst du dir sogar das BufferedImage.
Eine Möglichkeit wie du dir das mit mehreren Threads sparen kannst wäre noch, wenn du das nicht im Konstruktor, sondern erst, wenn das Fenster schon sichtbar ist, »zeichnest«. Dennoch ist dann mein Vorschlag mit Liste effizienter und einfacher. Also ca so: Konstruktor erstellt nur das Fenster, und es gibt eine extra Methode »animate()«, die du aus der »App« aufrufst und die das dann gemütlich erscheinen lässt.
Beitrag zuletzt geändert: 26.5.2015 22:12:58 von hackyourlife -
hackyourlife schrieb:
Du brauchst Multi-Threading....
Super danke :) Ich habs soweit hinbekommen. Hier ist mein Code:
public class AppFrame2 extends JFrame { private static final long serialVersionUID = 1L; private ArrayList<Spielstein> spielsteine = new ArrayList<Spielstein>(); public AppFrame2() { super(); setDefaultCloseOperation(EXIT_ON_CLOSE); MultiThread t1 = new MultiThread(); t1.start(); } public void paint(Graphics g){ for(Spielstein each: spielsteine){ zeichneSpielstein(g, each); } } public void zeichneSpielstein(Graphics stift, Spielstein s) { int x = s.getX(); int y = s.getY(); int width = s.getWidth(); int heigth = s.getHeigth(); Color farbe1 = s.getOben(); Color farbe2 = s.getRechts(); Color farbe3 = s.getUnten(); Color farbe4 = s.getLinks(); stift.drawRect(x, y, width, heigth); // Oberes Dreieck int[] xcoords = { x + (width - 1), x + 1, x + (width / 2) }; int[] ycoords = { y + 1, y + 1, y + (heigth / 2) }; stift.setColor(farbe1); stift.fillPolygon(xcoords, ycoords, xcoords.length); // Rechtes Dreieck int[] xcoords2 = { x + (width - 1), x + (width - 1), x + (width / 2) }; int[] ycoords2 = { y + (heigth - 1), y + 1, y + (heigth / 2) }; stift.setColor(farbe2); stift.fillPolygon(xcoords2, ycoords2, xcoords2.length); // Unteres Dreieck int[] xcoords3 = { x + 1, x + width, x + (width / 2) }; int[] ycoords3 = { y + heigth, y + heigth, y + (heigth / 2) }; stift.setColor(farbe3); stift.fillPolygon(xcoords3, ycoords3, xcoords3.length); // Linkes Dreieck int[] xcoords4 = { x + 1, x + 1, x + (width / 2) }; int[] ycoords4 = { y + 1, y + (heigth - 1), y + (heigth / 2) }; stift.setColor(farbe4); stift.fillPolygon(xcoords4, ycoords4, xcoords4.length); stift.setColor(Color.BLACK); } public class MultiThread extends Thread { public void run(){ spielsteine.add(new Spielstein(100, 100, 100, 100, Color.RED, Color.YELLOW, Color.ORANGE, Color.BLUE)); repaint(); try { Thread.sleep(700); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } spielsteine.add(new Spielstein(200, 100, 100, 100, Color.RED, Color.YELLOW, Color.ORANGE, Color.BLUE)); repaint(); } } }
Allerdings habe ich jetzt noch keine Synchronisation eingebaut. Ist das nötig? Und wenn ja, muss das dann einfach um den ganzen Teil in der Run Methode oder wie muss ich das machen? -
ultimate-bravery schrieb:
Das ist immer dann nötig, wenn du von mehreren Threads aus auf eine gemeinsame Datenstruktur (in dem Fall die Liste »spielsteine«) zugreifst. Du müsstest also entweder einen Vector statt der ArrayList nutzen (denn Vector ist »thread-safe«, da musst du dich nicht um Synchronisierung kümmern) oder bei jedem Zugriff auf die Liste ein »synchronized(spielsteine) { … }« herumlegen. Aber immer nur um den Bereich, der direkt auf die Liste zugreift, sonst sperrst du dir das Multi-Threading damit aus! Der Vector ist hier allerdings wohl die elegantere und einfachere Lösung.
Allerdings habe ich jetzt noch keine Synchronisation eingebaut. Ist das nötig? Und wenn ja, muss das dann einfach um den ganzen Teil in der Run Methode oder wie muss ich das machen? -
hackyourlife schrieb:
Der Vector ist hier allerdings wohl die elegantere und einfachere Lösung.
Ok danke! Ich hab jetzt einen Vector, wo alle Spielsteine am Anfang drin sind (spielsteine) und einen Vector wo die Spielsteine aus dem ersten Vector hinzugefügt werden (spielfeld). Jetzt möchte ich es so machen, dass ein Spielstein wieder vom Spielfeld entfernt wird, wenn die linke Seite des neu hinzugefügten Spielsteins nicht die gleiche Farbe hat wie die rechte Seite des zuvor gesetzten Spielsteins. Hier ist mein Code der Thread Klasse:
public class MultiThread extends Thread { public void run(){ // Spielfeld füllen int durchgang = 1; for(int i = 0; i < spielsteine.size(); i++){ spielfeld.add(spielsteine.get(i)); repaint(); sleeep(700); if(durchgang > 1){ if(spielsteine.get(i).getLinks() != spielfeld.get(spielfeld.size()-2).getRechts()){ System.out.println("Fehler bei Durchgang: " + durchgang); spielfeld.remove(spielfeld.size()-1); repaint(); } } durchgang++; } } public void sleeep(int dauer){ try { Thread.sleep(dauer); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
Also er vergleicht halt den aktuell hinzugefügten Spielstein mit dem davor hinzugefügtem Spielstein. Und wenn die Farben nicht gleich sind wird der letzte Stein aus dem Spielfeld wieder gelöscht und dann wird neu gezeichnet. Allerdings funktioniert das bei mir nicht. Die Steine werden trotzdem noch gezeichnet.
Bei einem Durchgang kam auch gerade folgender Fehler:
Exception in thread "AWT-EventQueue-0" java.util.ConcurrentModificationException
at java.util.Vector$Itr.checkForComodification(Unknown Source)
at java.util.Vector$Itr.next(Unknown Source)
at klassen.AppFrame2.paint(AppFrame2.java:31)
at javax.swing.RepaintManager$4.run(Unknown Source)
at javax.swing.RepaintManager$4.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$1.doIntersectionPrivilege(Unknown Source)
at javax.swing.RepaintManager.paintDirtyRegions(Unknown Source)
at javax.swing.RepaintManager.paintDirtyRegions(Unknown Source)
at javax.swing.RepaintManager.prePaintDirtyRegions(Unknown Source)
at javax.swing.RepaintManager.access$1300(Unknown Source)
at javax.swing.RepaintManager$ProcessingRunnable.run(Unknown Source)
at java.awt.event.InvocationEvent.dispatch(Unknown Source)
at java.awt.EventQueue.dispatchEventImpl(Unknown Source)
at java.awt.EventQueue.access$500(Unknown Source)
at java.awt.EventQueue$3.run(Unknown Source)
at java.awt.EventQueue$3.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$1.doIntersectionPrivilege(Unknown Source)
at java.awt.EventQueue.dispatchEvent(Unknown Source)
at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.run(Unknown Source)
Und hier noch die Paint Methode:
public void paint(Graphics g){ for(Spielstein each: spielfeld){ zeichneSpielstein(g, each); } }
Zeile 31 ist das wo das for in der paint Methode anfängt. Die Fehler kamen grad aber nur bei einem Durchlauf. Wenn ich es jetzt nochmal starte zeichnet er einfach alle Spielsteine und löscht die betroffenen halt nicht wieder.
Weißt du was falsch ist?
EDIT:
Es funktioniert jetzt. Lag wohl daran, dass jedem Spielstein am Anfang schon feste Koordinaten zugeteilt wurde. Das mit den Koordinaten zuteilen mache ich jetzt in der paint Methode. Jetzt klappt es.
Beitrag zuletzt geändert: 28.5.2015 13:27:49 von ultimate-bravery -
Diskutiere mit und stelle Fragen: Jetzt kostenlos anmelden!
lima-city: Gratis werbefreier Webspace für deine eigene Homepage