repaint() funktioniert nicht wie gewünscht

  • Themenstarter Themenstarter Jessy12
  • Beginndatum Beginndatum
J

Jessy12

Gast
Hallo, ich habe ein JPanel folgendermaßen initialisiert:

Java:
pan2 = new JPanel() {
			@Override public void paintComponent(Graphics g) {
				System.out.println("TEST");
				g.setColor(Color.GREEN);
				g.fillRect(x, y, 5, 5);
			}
		};

Wenn ich dann aber Folgendes aufrufe, dann passiert nichts 🙁 Woran liegt das?
Java:
pan2.repaint();
 
Das geht nicht weil:
* Du hast kein Swing-Fenster rundherum, keine Ereignisschleife, nix
* Das Panel ist in keiner Content-Pane

Wenn eines der beiden zutrifft: Swing-Tutorial lesen.
Sonst: Gesamten Code posten.
 
Das ist der gesamte Code. Das Programm hat keinen Sinn und dient nur zum experimentieren. Aber warum funktioniert hier die repaint() Methode nicht?

Java:
package testflaeche;

import java.awt.event.*;
import javax.swing.*;
import java.awt.*;

public class Test2 {
	JFrame frame;
	JTextField txt1;
	JPanel pan2;
	private int x;
	private int y;
	
	public Test2() {
		x = 0; y = 0;
		frame = new JFrame();
		txt1 = new JTextField("Sinus", 7);
		JPanel pan1 = new JPanel();
		pan1.setBackground(Color.DARK_GRAY);
		pan2 = new JPanel() {
			@Override public void paintComponent(Graphics g) {
				System.out.println("TEST");
				g.setColor(Color.GREEN);
				g.fillRect(x, y, 5, 5);
			}
		};
		ButtonOne but1 = new ButtonOne("Random");
		but1.addActionListener(but1);
		ButtonTwo but2 = new ButtonTwo("Go!");
		but2.addActionListener(but2);
		pan2.add(but1); pan2.add(but2);
		frame.setSize(new Dimension(300,300));
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setLocationRelativeTo(null);
		frame.getContentPane().add(BorderLayout.NORTH, txt1);
		frame.getContentPane().add(BorderLayout.CENTER, pan1);
		frame.getContentPane().add(BorderLayout.SOUTH, pan2);
		frame.setVisible(true);
	}
	
	class ButtonOne extends JButton implements ActionListener {
		public ButtonOne(String name) {
			super(name);
		}
		
		@Override public void actionPerformed(ActionEvent evt) {
			int r = (int)(Math.random() * 3 + 1);
			switch(r) {
				case 1:
					txt1.setText("Sinus");
					break;
				case 2:
					txt1.setText("Cosinus");
					break;
				case 3:
					txt1.setText("xQuadrat");
					break;
			}
		}
	}
	
	class ButtonTwo extends JButton implements ActionListener {
		public ButtonTwo(String name) {
			super(name);
		}
		
		@Override public void actionPerformed(ActionEvent evt) {
			for(int i = 0; i <= 300; i++) {
				System.out.println(x + "  " + y);
				x = i;
				y = (int)(30*Math.sin(i) + 100);
				pan2.repaint();
				try {
					Thread.sleep(50);
				}
				catch(InterruptedException exc) {
					exc.printStackTrace();
				}
			}
		}
	}
	
	public static void main(String[] args) {
		Test2 t = new Test2();
	}
}
 
Wenn ich den "Go!" Button drücke, dann müsste doch repaint() 301-Mal aufgerufen werden...Darum geht es mir. Denn das passiert nicht...
 
Also ich nehme mal an, dass der Button, den du auf pan2 packst, praktisch alles verdeckt.
Nebenbei, wenn du ein 5x5 Pixel rotes Rechteck zeichnest, fällt das vielleicht nicht so auf 😉
 
ein Panel ohne Inhalt hat standardmäßig die Größe 1x1 oder ähnlich in dein meisten Layouts,
setPreferredSize() kann helfen

edit: ok, hier nicht der Fall, es werden Komponenten eingefügt, was sich mit paint eh kreuzt
und die bemeckerte Schleife ist sicherlich das größte Hindernis
 
Wie meinst du "alles überdeckt"?

Das Rechteck sehe ich und es sollte sich eigentlich bewegen bzw. eine Animation zustandegekommen 🙁
 
Nebenbei, Java optimiert ein bisschen bei repaint, er fasst die Aufrufe möglicherweise zusammen 🙂
Schlagwort fällt mir gerade nicht mehr ein, bitte mal jemand, dem es einfällt, hier posten.
 
Was hat das mit preferredSize bzw. der größe zu tun? es wird alles richtig angezeigt. Bloß repaint() ruft nicht die paintComponent-Methode in der for-Schleife auf
 
Starte mal das Programm und verändere die größe des Fensters (achte darauf, wie sich deine Komponenten neu ausrichten)
dann drück Go!
und verändere wieder die größe des Fensters(achte darauf, wie sich deine komponenten nicht neu ausrichten).

Problem (wie schon geschrieben): bei dir läuft alles im EDT. Hier hilft der SwingWorker
 
Nuja, du fügst einen Button hinzu, im BorderLayout south skaliert die Komponente da Horizontal 100%, wenn ich mich nicht irre.
 
@Tomate: Während die Schleife läuft funktioniert das neuzeichnen beim vergrößern und verkleinen des Fensters nicht :/ Woran genau liegt das und was ist EDT. Kannst Du mir das hier grob erklären? 🙂 wie umgeht man dieses Problem?
 
Man beachten meinen Link aus #7 ;-). Der EDT ist der Event Dispatcher Thread, darin wird das GUI z.B. neu gezeichnet. Wenn du in diesem Thread eine Prozedur startest, die länger benötigt, dann blockiert das das GUI. Deswegen gibt es den SwingWorker. Der führt deine Berechnung in einem eigenen Thread aus und ermöglicht das veröffentlichen von Zwischenergebnissen (um das GUI zu aktualisieren).
 
Ich sehe gerade, dass es SwingWorker erst seit Java6 gibt. Wie hat man denn vorher Animationen über for-Schleifen realisiert? Mein Buch basiert auf Java5 und da steht, dass ich in eine for-Schleife ein repaint() einbinden soll...
 
der repaint() funktioniert nun. jedoch gibt es ein weiteres Problem. Wenn ich den folgende Code ausführe, dann kommt es zu komischen Anzeigefehlern: das JTextField und ein JButton werden bei mir plötzlichdoppelt angezeigt. Des Weiteren funktioniert setBackground() auf pan1 nicht. Kann mir jemand erklären warum?

Java:
package testflaeche;

import java.awt.event.*;
import javax.swing.*;
import java.awt.*;
import java.awt.List;
import java.util.*;
import java.io.*;

public class Test2 {
	JFrame frame;
	JTextField txt1;
	JPanel pan2;
	JPanel pan1;
	private int x;
	private int y;
	
	public Test2() {
		x = 0; y = 0;
		frame = new JFrame();
		txt1 = new JTextField("Cosinus", 7);
		pan1 = new JPanel() {
			@Override public void paintComponent(Graphics g) {
				g.setColor(Color.GREEN);
				g.fillRect(x, y, 5, 5);
			}
		};
		pan1.setBackground(Color.DARK_GRAY);
		pan2 = new JPanel();
		ButtonOne but1 = new ButtonOne("Random");
		but1.addActionListener(but1);
		ButtonTwo but2 = new ButtonTwo("Go!");
		but2.addActionListener(but2);
		pan2.add(but1); pan2.add(but2);
		frame.setSize(new Dimension(300,300));
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setLocationRelativeTo(null);
		frame.getContentPane().add(BorderLayout.NORTH, txt1);
		frame.getContentPane().add(BorderLayout.CENTER, pan1);
		frame.getContentPane().add(BorderLayout.SOUTH, pan2);
		frame.setVisible(true);
	}
	
	class ButtonOne extends JButton implements ActionListener {
		public ButtonOne(String name) {
			super(name);
		}
		
		@Override public void actionPerformed(ActionEvent evt) {
			int r = (int)(Math.random() * 3 + 1);
			switch(r) {
				case 1:
					txt1.setText("Sinus");
					break;
				case 2:
					txt1.setText("Cosinus");
					break;
				case 3:
					txt1.setText("xQuadrat");
					break;
			}
		}
	}
	
	class ButtonTwo extends JButton implements ActionListener {
		public ButtonTwo(String name) {
			super(name);
		}
		
		@Override public void actionPerformed(ActionEvent evt) {
			SwingWorker work = new SwingWorker() {
				@Override public Object doInBackground() {
					for(int i = 0; i < 300; i++) {
						System.out.println(x + "  " + y);
						x = i;
						y = (int)(30*Math.sin(i) + 80);
						publish();
						try {
							Thread.sleep(50);
						}
						catch(InterruptedException exc) {
							exc.printStackTrace();
						}
					}
					return null;
				}
				
		        @Override protected void process(java.util.List chunks){
		        	pan1.repaint();
		        }
			};
			work.execute();
		}
	}
	
	public static void main(String[] args) {
		Test2 t = new Test2();
	}
}
package testflaeche;

import java.awt.event.*;
import javax.swing.*;
import java.awt.*;
import java.awt.List;
import java.util.*;
import java.io.*;

public class Test2 {
	JFrame frame;
	JTextField txt1;
	JPanel pan2;
	JPanel pan1;
	private int x;
	private int y;
	
	public Test2() {
		x = 0; y = 0;
		frame = new JFrame();
		txt1 = new JTextField("Cosinus", 7);
		pan1 = new JPanel() {
			@Override public void paintComponent(Graphics g) {
				g.setColor(Color.GREEN);
				g.fillRect(x, y, 5, 5);
			}
		};
		pan1.setBackground(Color.DARK_GRAY);
		pan2 = new JPanel();
		ButtonOne but1 = new ButtonOne("Random");
		but1.addActionListener(but1);
		ButtonTwo but2 = new ButtonTwo("Go!");
		but2.addActionListener(but2);
		pan2.add(but1); pan2.add(but2);
		frame.setSize(new Dimension(300,300));
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setLocationRelativeTo(null);
		frame.getContentPane().add(BorderLayout.NORTH, txt1);
		frame.getContentPane().add(BorderLayout.CENTER, pan1);
		frame.getContentPane().add(BorderLayout.SOUTH, pan2);
		frame.setVisible(true);
	}
	
	class ButtonOne extends JButton implements ActionListener {
		public ButtonOne(String name) {
			super(name);
		}
		
		@Override public void actionPerformed(ActionEvent evt) {
			int r = (int)(Math.random() * 3 + 1);
			switch(r) {
				case 1:
					txt1.setText("Sinus");
					break;
				case 2:
					txt1.setText("Cosinus");
					break;
				case 3:
					txt1.setText("xQuadrat");
					break;
			}
		}
	}
	
	class ButtonTwo extends JButton implements ActionListener {
		public ButtonTwo(String name) {
			super(name);
		}
		
		@Override public void actionPerformed(ActionEvent evt) {
			SwingWorker work = new SwingWorker() {
				@Override public Object doInBackground() {
					for(int i = 0; i < 300; i++) {
						System.out.println(x + "  " + y);
						x = i;
						y = (int)(30*Math.sin(i) + 80);
						publish();
						try {
							Thread.sleep(50);
						}
						catch(InterruptedException exc) {
							exc.printStackTrace();
						}
					}
					return null;
				}
				
		        @Override protected void process(java.util.List chunks){
		        	pan1.repaint();
		        }
			};
			work.execute();
		}
	}
	
	public static void main(String[] args) {
		Test2 t = new Test2();
	}
}
 
ich kann im Code zu doppelten Komponenten keinen Fehler sehen,
abgesehen davon dass du anscheinend den Code zweimal gepostet hast,

bei mir passiert das auch nicht, Screenshot?

---------

ein gesetzter Background muss ja auch irgendwann irgendwo gezeichnet werden, aber du hast paintComponent() überschrieben,
wie immer auf der Welt du darauf gekommen bist, wenn du dich an seriöse Tutorials gehalten hast, dann müsstest du eigentlich wissen,
das standardmäßig der erste Befehl in überschriebener paintComponent
super.paintComponent(g); lautet,
schon wird u.a. der Hintergrund gemalt, vor allem auch alles bisherige gelöscht, was vielleicht von Vorteil oder von Nachteil für dich ist,
im Zweifel alles was gezeichnet werden soll merken und bei jedem paint komplett neu zeichnen, von leerer Fläche (in Hintergrundfarbe) ausgehen
 
nachdem man festgestellt hat, dass der Code einfach doppelt gepostet ist^^ liegt der Fehler einfach in der paintComponent-Methode:

als erstes ruft man (immer) die Methoden der Oberklassen auf.
[java=24] super.paintComponent(g);[/code]

dann wird auch der Hintergrund dunkelgrau und die anderen Componenten verhalten sich wie erwartet.

ansonsten hast du halt ein solches, oder ähnliches Verhalten: (random-, go-Button, und das textfield oben sind doppelt gezeichnet) natürlich wird auch das grüne quadrat immer wieder gezeichnet, so dass sich ein Verlauf ergibt, aber nach dem ersten resizen des Frames wäre es auch wieder weg.

Edit:
Wenn du (wieder) gerne den Verlauf der gezeichnenten grünen Quadrate haben möchtest, solltest du dir die Positionen in irgendeiner Collection speichern und dementsprechend darüber iterieren und dann alle zeichnen lassen.
 

Anhänge

  • Unbenannt.PNG
    Unbenannt.PNG
    13,6 KB · Aufrufe: 27
Zuletzt bearbeitet:
Vielen Dank für die ausführlichen Antworten 🙂 In meinem Buch (Java von Kopf bis Fuß) stand nichts davon, dass man immer super.paintComponent(g) aurufen sollte. Noch eine letzte Frage, falls ich euch nicht nerve: Wie speichere ich denn alles was bisher gezeichnet wurde und lade es dann wieder?

Und noch eine weitere Frage: getGraphics() soll man ja generell überhaupt nicht verwenden habe ich gelesen. Meine Frage ist aber dann, wie ich bspw. ein Event mit paintComponent verknüpfe, also dass z.B. bei einem Mausklick an der Stelle ein Kreis gezeichnet wird.


Vielen Dank
 
Und noch eine weitere Frage: getGraphics() soll man ja generell überhaupt nicht verwenden habe ich gelesen. Meine Frage ist aber dann, wie ich bspw. ein Event mit paintComponent verknüpfe
Gar nicht - zumindest nicht direkt ;-)
Mausklick:
1. Objekt wird erzeugt/definiert/geändert
2. repaint() der Komponente wird aufgerufen

In der paintComponent() wird das Objekt, bzw. anhand der Informationen aus dem Objekt gezeichnet.
 
> In meinem Buch (Java von Kopf bis Fuß) stand nichts davon
Galileo Computing :: Java ist auch eine Insel – 20 Grafikprogrammierung

> Wie speichere ich denn alles was bisher gezeichnet wurde und lade es dann wieder?
auf beliebige Art, die dir sinnvoll erscheint, z.B. eine Liste von Point-Objekten,
die Thread-Schleife fügt neue ein + repaint(),
die paint-Methode durchläuft die Liste und zeichnet,

kann evtl. Synchronisationsprobleme geben, wenn beide gleichzeitig auf die Liste zugreifen..
 
Wie speichere ich denn alles was bisher gezeichnet wurde und lade es dann wieder?
...
Edit:
Wenn du (wieder) gerne den Verlauf der gezeichnenten grünen Quadrate haben möchtest, solltest du dir die Positionen in irgendeiner Collection speichern und dementsprechend darüber iterieren und dann alle zeichnen lassen.

Dir irgendeine Collection anlegen:
[java=17]
ArrayList<Point> arrayList = new ArrayList<Point>();[/code]

diese dann füllen:
[java=76]
Point p = new Point();
p.x = i;
p.y = (int) (30 * Math.sin(i) + 80);
arrayList.add(p);


[/code]


und dann noch in der paintComponent drüber iterieren:
[java=22]
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.GREEN);
for (int i = 0; i < arrayList.size(); i++) {
Point p = arrayList.get(i);
g.fillRect(p.x, p.y, 5, 5);
}
}[/code]
 
Vielen Dank, inzwischen ist mir das alles klar 🙂

Ich versuche gerade ein eigenes Paintprogramm zu programmieren und wenn ich die verschiedenen Formen mit Collections speichere, dann klappt das auch wunderbar. Jetzt stehe ich aber doch nochmal vor einem Problem: Ich möchte einen Radiergummi implementieren. Da hilft es mir ja nichts mehr, wenn ich nur die einzelnen Formen speichere, sondern ich müsste tatsächlich das ganze Bild irgendwie speichern. Ist das möglich?
 
du kannst auf ein BufferedImage malen, dann ist auch getGraphics() mal erlaubt, suche im Internet nach Beispielen,
in paintComponent dann einfach immer nur das Image malen
 
Nur überflogen, aber: Wenn man 200 mal repaint() aufruft, wird deswegen NICHT notwendigerweise auch 200 mal paintComponent aufgerufen. Die repaint()-Befehle werden ggf. zu einem einzelnen paintComponent-Aufruf zusammengefasst. Aber das scheint hier ja das geringste Problem zu sein 😉
 
Nur überflogen, aber: Wenn man 200 mal repaint() aufruft, wird deswegen NICHT notwendigerweise auch 200 mal paintComponent aufgerufen. Die repaint()-Befehle werden ggf. zu einem einzelnen paintComponent-Aufruf zusammengefasst. Aber das scheint hier ja das geringste Problem zu sein 😉

Um das zu umgehen kann man doch einen SwingWorker verwenden, oder?
 
da gibt es nichts zu umgehen, jedenfalls nicht mit der Anzahl der paint-Aufrufe, dass die nur 190, 70 oder 5 sind ist vielleicht nicht wünschenswert, aber korrekt, höchstens von Systemlast/ optimaler Programmierung abhängig (wenig Aktionen im paint)

richtig ist, dass Dinge, deren Anzahl Ausführen wichtig ist, etwa für Berechnungen, nicht in die paint gehören, sondern irgendwo anders hin,
das meinst du vielleicht, SwingWorker gerne eine Variante dafür, richtig
 

Zurück
Oben