2D-Grafik PaintComponent() übernimmt keine Werte aus update()

Petra19

Mitglied
Hallo zusammen!

Ich möchte Rechtecke zeichnen und habe mein Programm in 4 Klassen aufgeteilt:

Controller, Model(=Formen), View und Fenster

- Fenster - verwaltet die View (JPanel)
- Model(=Formen) - verwaltet die Formen in einer Liste
- Controller - steuert die Daten
- View - hier soll gezeichnet werden und hier liegt mein Problem
+Attribute:
#form,x1,x2,y1,y2​
+Methoden:
#View() - initialisiert die Attribute mit Kreis/0/0/0/0
#paintComponent()
#update()​

Der Observer scheint zu funktionieren, da die Daten in der Methode update() ankommen. System.out zeigt folgendes:

Update: Rechteck/10/10/100/50
Update: Rechteck/20/20/80/70

Aber diese Daten kommen leider nicht in paintComponent() an:

PaintComponent: Kreis/0/0/0/0
PaintComponent: Kreis/0/0/0/0

Vielen Dank für Eure Mühen,

Petra

[Java=42]
import java.awt.Color;
import java.awt.Graphics;
import java.util.ArrayList;
import java.util.Observable;
import java.util.Observer;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
/*
* Controller enthält main()
*/

public class Controller {
Formen formenListe;

public Controller(){
new Fenster();
View formenView=new View();
formenListe =new Formen();
formenListe.addObserver(formenView);
}

public void add(String s){
formenListe.add(s);
}

public static void main(String[] args){
SwingUtilities.invokeLater(new Runnable() {
public void run() {
Controller c=new Controller();
c.add("Rechteck,10,10,100,50");
c.add("Rechteck,20,20,80,70");
}
});
}
}

/* *************************************************************
* Model
*/
class Formen extends Observable{
ArrayList<String> liste=null;
public Formen()
{
liste=new ArrayList<String>();
}

public void add(String s)
{
liste.add(s);
setChanged();
notifyObservers();
}
public String[][] getFormen()
{
//In jeder Zeile sind 5 Werte enthalten
String[][] s=new String[liste.size()][5];
for (int i=0;i<liste.size();i++)
{
s=liste.get(i).split(",");;
}
return s;
}
}

/* *************************************************************
* Fenster
*/

class Fenster extends JFrame
{

private static final long serialVersionUID = 1L;
View view=new View();

public Fenster()
{
super();
this.setSize(254, 162);
this.setTitle("Zeichnen");
this.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
view.setSize(this.getWidth(),this.getHeight());
add(view);
this.setVisible(true);
}
}

/* *************************************************************
* View
*/

class View extends JPanel implements Observer{

private static final long serialVersionUID = 1L;
private String form;
private int x1,x2,y1,y2;

public View(){
super();
this.setSize(300, 200);
this.setLayout(null);
form="Kreis";
x1=0;x2=0;y1=0;y2=0;
System.out.println("Init: "+form+"/"+x1+"/"+y1+"/"+x2+"/"+y2);
this.setVisible(true);
}

@Override
protected void paintComponent( Graphics g ) {
super.paintComponents( g );
g.setColor(Color.black);
g.drawRect( x2, y2, x1-x2, y1-y2 );
System.out.println("PaintComponent: "+form+"/"+x1+"/"+y1+"/"+x2+"/"+y2);
}

public void update(Observable arg0, Object arg1){
String[][] s=((Formen)arg0).getFormen();
for (int i=0;i<s.length;i++){
form=s[0];
x1=Integer.parseInt(s[1]);
y1=Integer.parseInt(s[2]);
x2=Integer.parseInt(s[3]);
y2=Integer.parseInt(s[4]);
System.out.println("Update: "+form+"/"+x1+"/"+y1+"/"+x2+"/"+y2);
this.revalidate();
}
}
}
[/Java]
 
Du benutzt da zwei verschiedene Objekte von View. Das Wichtige, welches du in Controller erstellst benutzt du gar nicht, sondenr erstellst einfach in Fenster ein neues Objekt und addest das zum Frame hinzu(dieses hast du aber gar nicht mit addObserver angemeldet!)

Erstelle also nur ein Objekt/Panel 🙂
 
Hallo eRaaa,

vielen Dank für Deine schnelle Antwort. Das war's!

Ich habe den Quelltext in den Zeilen 58 bis 63 wie unten dargestellt geändert. Jetz wird genau ein Rechteck gezeichnet, nämlich nur das zweite:

PaintComponent: Rechteck/20/20/80/70
PaintComponent: Rechteck/20/20/80/70

Das bedeutet, dass in der for-Schleife der Aufruf
Java:
this.revalidate();
nicht bei jedem Durchlauf gestartet wird, sondern erst im Anschluss?

Kann ich das irgentwie erreichen?

Vielen Dank im Voraus,

Petra

Java:
public Controller(){
		View formenView=new View();
		new Fenster(formenView);
		formenListe =new Formen();
		formenListe.addObserver(formenView);
	}
 
Also normal ruft man ja eh eig. eher repaint() auf..(und du hast immernoch in deiner paintComponent den falschen super-Aufruf drin (ohne das s hinten ;D) aber das hat alles nichts mit deinem Problem zu tun denke ich.
Das Problem ist, dass du einfach zu schnell Werte hinzufügst und änderst und der Zeichenthread gar nicht so schnell zeichnen kann, auch durch repaint() wird nicht SOFORT gezeichnet *glaub*, d.h. es wird gezeichnet wenn es möglich ist. D.h. es ist dann natürlich höchst wahrscheinlich, dass du mit deinem 2. add /update die Werte wieder überschrieben hast wenn er sich denn dann entschiedne hat zu zeichnen.

Hmm..was man da gegen tun kann? Mhm, nicht immer die Werte überschreiben, also nicht immer nur ein Wert merken sonenr evtl. eben immer die Collection.Also die Schleife in der paint, nicht in der update-Methode(die setzt einfach nur die COllection oder was auch immer neu und ruft repaint() auf)...aber evtl. müsste man da genauer drüber nachdenken ;D
 
Hallo zusammen!

Ich habe den Vorschlag von eRaaa aufgegriffen und die Schleife in die paintComponent()-Methode eingefügt:

Nun funktioniert es wie gewünscht! (->siehe Quelltext weiter unten)

Dafür wirkt die paintComponent()-Methode etwas unübersichtlicher und die Klasse View ist nicht nur für die Anzeige zuständig sondern auch für die Konvertierung der Daten.???:L

Da finde ich die Lösung im http://www.java-forum.org/awt-swing-swt/43939-zeichnen-swing-tutorial.html eleganter...

Ich sehe das doch richtig, dass das View sich nur auf die Darstellung der Daten beschränken soll, oder?
Sollte sich die Methode update() dann nicht besser in der Klasse Fenster befinden, oder ist das egal (=je nach Anwendung)?

Vielen Dank für Eure Hilfe,

Petra

Java:
import java.awt.Color;
import java.awt.Graphics;
import java.util.ArrayList;
import java.util.Observable;
import java.util.Observer;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
/*
 * Controller enthält main()
 */

public class Controller {
	Formen formenListe;
	
	public Controller(){
		View formenView=new View();
		new Fenster(formenView);
		formenListe =new Formen();
		formenListe.addObserver(formenView);
	}
	
	public void add(String s){
		formenListe.add(s);
	}
		
	public static void main(String[] args){
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				Controller c=new Controller();
				c.add("Rechteck,10,10,100,50");
				c.add("Rechteck,20,20,80,70");
			}
		});
	}
}

/* *************************************************************
 * Model
 */
class Formen extends Observable{
	ArrayList<String> liste=null;
	public Formen()
	{
		liste=new ArrayList<String>();
	}
	
	public void add(String s)
	{
		liste.add(s);
		setChanged();
		notifyObservers();
	}
	public ArrayList<String> getListe(){
		return liste;
	}
}

/* *************************************************************
 * Fenster
 */

class Fenster extends JFrame
{
	private static final long serialVersionUID = 1L;
	
	public Fenster(View view)
	{
		super();
		this.setSize(254, 162);
		this.setTitle("Zeichnen");
		this.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
		view.setSize(this.getWidth(),this.getHeight());
		add(view);
		this.setVisible(true);		
	}
}

/* *************************************************************
 * View
 */

class View extends JPanel implements Observer{ 
	
	private static final long serialVersionUID = 1L;
	private ArrayList<String> liste=null;
	
	public View(){
		super();
		this.setSize(300, 200);
		this.setLayout(null);
		liste=new ArrayList<String>();
		this.setVisible(true);
	}
	
	@Override  
	protected void paintComponent( Graphics g ) {
		super.paintComponent( g );
		g.setColor(Color.black);
		String[] str;
		for (int i=0;i<liste.size();i++){
			str=liste.get(i).split(",");
			g.drawRect( Integer.parseInt(str[1]), Integer.parseInt(str[2]), Integer.parseInt(str[3])-Integer.parseInt(str[1]), Integer.parseInt(str[4])-Integer.parseInt(str[2]) );
		}
	}
	
	public void update(Observable arg0, Object arg1){
		liste=((Formen)arg0).getListe();
		this.repaint();
	}
}
 
Die Vorgehensweise hier ist prinzipiell schon richtig: Dein Modell (Klasse Formen) benachrichtig deinen View über Änderungen, welcher diese dann auf seine Art verarbeiten muss. D.h. die update-Methode kannst du durchaus im View lassen. Allein die Umwandlung der Daten könnte man noch anders gestalten, muss man aber nicht.
Man könnte z.B. anstelle der String[] direkt Rectangle-Objekte erzeugen und die im View Zeichnen lassen, aber ich halte das nun erstmal eher für Geschmackssache.

Meiner Meinung nach obliegt es dem View (und nur ihm), die Daten des Modells irgendwie darzustellen. Dazu kann u.U. auch eine Umformulierung der Daten gehören. Schließlich können in der Theorie mehrere Views auf einem Modell arbeiten und die jeweiligen Daten anders darstellen. Eine Umformung ist hierbei bestimmt keine Seltenheit.
 
Hallo KrokoDiehl,

vielen Dank für Deine Hilfe.

Ich habe Deine Idee aufgegriffen und lasse in der View Rectangel2d-Objekte erzeugen. Dazu habe ich in der Klasse View eine Methode setListOfShapes() geschrieben, die die Werte aus der ArrayList<String> ausliest und umwandelt.

Ich bin dabei Deiner Ansicht gefolgt, dass eine Umwandlung der Daten innerhalb der View erfolgt. Aber dennoch benötige ich ein String[]-Array, oder habe ich eine Methode übersehen bzw. Dich missverstanden?

Vielen Dank im Voraus,

Petra

Java:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Observable;
import java.util.Observer;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
/*
 * Controller enthält main()
 */

public class Controller {
	Formen formenListe;
	
	public Controller(){
		View formenView=new View();
		new Fenster(formenView);
		formenListe =new Formen();
		formenListe.addObserver(formenView);
	}
	
	public void add(String s){
		formenListe.add(s);
	}
		
	public static void main(String[] args){
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				Controller c=new Controller();
				c.add("Rechteck,10,10,100,40");
				c.add("Rechteck,20,20,40,50");
				c.add("Rechteck,30,30,60,60");
				c.add("Rechteck,40,40,80,70");
			}
		});
	}
}

/* *************************************************************
 * Model
 */
class Formen extends Observable{
	ArrayList<String> liste=null;
	
	public Formen(){
		liste=new ArrayList<String>();
	}
	
	public void add(String s){
		liste.add(s);
		setChanged();
		notifyObservers();
	}
	public ArrayList<String> getListe(){
		return liste;
	}
}

/* *************************************************************
 * Fenster
 */

class Fenster extends JFrame
{
	private static final long serialVersionUID = 1L;
	
	public Fenster(View view)
	{
		super();
		this.setSize(254, 162);
		this.setTitle("Zeichnen");
		this.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
		view.setSize(this.getWidth(),this.getHeight());
		add(view);
		this.setVisible(true);		
	}
}

/* *************************************************************
 * View
 */

class View extends JPanel implements Observer{ 
	
	private static final long serialVersionUID = 1L;
	private ArrayList<Shape> listOfShapes=null;
	
	public View(){
		super();
		this.setSize(300, 200);
		this.setLayout(null);
		listOfShapes=new ArrayList<Shape>();
		this.setVisible(true);
	}
	
	@Override  
	protected void paintComponent( Graphics g ) {
		super.paintComponent( g );
		Graphics2D g2d = (Graphics2D)g;
		g2d.setColor(Color.black); 
		for (int i=0;i<listOfShapes.size();i++){
			g2d.draw(listOfShapes.get(i));		
		}
	}
	
	public void update(Observable arg0, Object arg1){
		setListOfShapes(((Formen)arg0).getListe());
		this.repaint();
	}
	
	public void setListOfShapes(ArrayList<String> liste)
	{
		String[] str;
		for (int i=0;i<liste.size();i++){
			str=liste.get(i).split(",");
			if (str[0].compareTo("Rechteck")==0)
				listOfShapes.add(new Rectangle2D.Float(Float.parseFloat(str[1]),Float.parseFloat(str[2]),Float.parseFloat(str[3]),Float.parseFloat(str[4])));
		}
	}
}
 
Hm, ich dachte eher an folgende Stelle:
[JAVA=35] c.add("Rechteck,10,10,100,40");
c.add("Rechteck,20,20,40,50");
c.add("Rechteck,30,30,60,60");
c.add("Rechteck,40,40,80,70");
[/code]
Hat es einen bestimmten Grund, dass es hier Strings sind? Wenn du gleich mit Rectangles arbeitest, würdest du dir die anderswo die Umwandlung sparen. Aber damit stelle ich nicht die Umwandlung an sich in Frage, sondern die Datenhaltung im Modell. Spricht nicht
Code:
ArrayList<String>
sondern
Code:
ArrayList<Rectangle>
(oder Shape...). 🙂

Vielleicht ist deine erstere Lösung etwas übersichtlicher, hat aber das Problem, dass die Umwandlung String-->Rechteck beim Zeichnen gemacht wird. Das halte ich für schlimmer als deine neuste Variante mit
Code:
setListOfShapes()
😉
Außerdem muss man dabei auch bedenken, dass diese Umwandlung immer wieder gemacht wird, was ja überflüssig ist.
 

Zurück
Oben