Java Applet flackert

Donnidonis

Commander
Registriert
Apr. 2009
Beiträge
2.712
Ich hab ein kleines Problem.
Ich hab ein kleines Applet geschireben, welches ca 100 Zeilen Code besitzt.
Mein Problem jedoch ist, wenn ich die repaint(); ausführe fängt es ziemlich an zu flackern, was mir nach ner Zeit auf die Nerven geht. Mein Applet ist eine kleine Analoguhr, mit Datumsanzeige und Digtaleranzeige.
Ich hab gelesen das Problem is mit "DoubleBuffering" zu lösen, nur ich weiß nicht wie ich das benutzen soll. Hab auch nichts gutes über Google gefunden.
Kann mir von euch wer Helfen?

Hier mein Code:
Code:
import java.applet.Applet;
import java.awt.*;
import java.util.Calendar;
import java.util.GregorianCalendar;

public class Uhr extends Applet {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	public void init() {
		setBackground(new Color(255, 255, 255));
	}

	public void paint(Graphics g) {
		
		// Antialiasing
		Graphics2D g2 = (Graphics2D) g;
		g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
				RenderingHints.VALUE_ANTIALIAS_ON);

		// Mittelpunkt
		int mittelpunkt = 100;
		int radius = 100;

		// Uhrzeit
		Calendar date = new GregorianCalendar();
		int hours = date.get(Calendar.HOUR_OF_DAY);
		int minutes = date.get(Calendar.MINUTE);
		int seconds = date.get(Calendar.SECOND);
        
        //Datum 
        String year = "" + date.get(Calendar.YEAR);
        String month = "" + (date.get(Calendar.MONTH) + 1); 
        String day = "" + date.get(Calendar.DAY_OF_MONTH);
        String datum = day + "." + month + "." + year;
        
        //Uhr Digital
        String shours = "" + hours;
        String sminutes = "" + minutes;
        String sseconds = "" + seconds;
        String digital = shours + ":" + sminutes + ":" + sseconds;
        
        //Striche bei jeder Stunde
     		for (int i = 0; i < 12; i++) {
     			g2.drawLine(
     					mittelpunkt + (int) ((radius - 15) * Math.cos(Math.toRadians(30 * i))),
     					mittelpunkt + (int) ((radius - 15) * Math.sin(Math.toRadians(30 * i))),
     					mittelpunkt + (int) (radius * Math.cos(Math.toRadians(30 * i))),
     					mittelpunkt + (int) (radius * Math.sin(Math.toRadians(30 * i))));
     		}
     		
     	//Striche bei jeder Minute
             for (int i=0;i<60;i++) {
                 g2.drawLine(
                 		mittelpunkt + (int) ((radius - 2) * Math.cos(Math.toRadians(6 * i))),
                 		mittelpunkt + (int) ((radius - 2) * Math.sin(Math.toRadians(6 * i))),
                 		mittelpunkt + (int) (radius * Math.cos(Math.toRadians(6 * i))),
                 		mittelpunkt + (int) (radius * Math.sin(Math.toRadians(6 * i))));
             }

        //Stunden
        g2.drawLine(
            mittelpunkt,mittelpunkt,mittelpunkt+(int)(60*Math.cos(Math.toRadians(hours%12*30+minutes/2.0-90))),
            mittelpunkt+(int)(60*Math.sin(Math.toRadians(hours%12*30+minutes/2.0-90))));

        //Minuten
        g2.drawLine(
            mittelpunkt,mittelpunkt,mittelpunkt+(int)(75*Math.cos(Math.toRadians(minutes*6-90))),
            mittelpunkt+(int)(75*Math.sin(Math.toRadians(minutes*6-90))));

        //Sekunden
        g2.setColor(new Color(255,0,0));
        g2.drawLine(
            mittelpunkt,mittelpunkt,mittelpunkt+(int)(90*Math.cos(Math.toRadians(seconds*6-90))),
            mittelpunkt+(int)(90*Math.sin(Math.toRadians(seconds*6-90))));

        //Kreis in Mitte
        g2.setColor(new Color (0,0,0));
     	g2.fillArc(97, 97, 6, 6, 0, 360);
     	
     	//Datum
     	g2.setColor(Color.BLUE);
     	g2.setFont(new Font("", Font.PLAIN, 24));
     	g2.drawString(datum, 50, 75);
     	
     	//Digitale Uhr
     	g2.setColor(Color.RED);
     	g2.setFont(new Font("", Font.PLAIN, 24));
     	g2.drawString(digital, 55, 150);
        
		//Etwas warten
		try {
			Thread.sleep(500);
		} catch (InterruptedException ex) {
			System.out.println("Nicht möglich");
		}

		// Fenster erneuern
		repaint();
	}
}
 
Niemals, NIEMALS, in der paint()-Methode Threads paussieren (damit pausierst du den GUI-Thread, sprich, deine Oberfläche friert ein) oder nochmals repaint() aufrufen (Rekursion)!

Eigenen Thread erzeugen zum Steueren! Gegebenenfalls auch Nachdenken, ob du Doublebuffern willst...
 
Haben grade in der Schule jetzt mit Applets angefangen, deswegn hab ich noch nicht sooo viel Ahnung davon. Ich weiß nur nicht wie ich es sonnst lösen könnte. Doublebuffern will ich ja, weiß nur nicht wie das geht. Das wollte ich ja gerne wissen ;) Und wie meinst du das mit dem extra Thread erzeugen?
 
Du solltest einen javax.swing.Timer verwenden, der alle x Sekunden ein Repaint anstößt.

Hast Du schon mal nach Double-Buffering Tutorials Ausschau gehalten? Ist wirklich nicht schwer. Anstatt direkt in den Screen Grafikkontext zu zeichnen, erstellst Du einen Buffer, führst dort Deine Zeichenoperationen durch und zeichnest dann den Buffer in einem Rutsch.

Hier ein Beispiel: http://www.realapplets.com/tutorial/DoubleBuffering.html
 
Ob Applet oder Application macht in dem Fall wohl wenig Unterschied.

Okay, also, kurze Einführung in paint() und repaint():

Durch das Überschreiben der paint()-Methode, wie du es beispielsweise getan hast, definiert man das Aussehen der jeweiligen Komponente. Ist die korrekte Vorgehensweise und wurde von dir auch weitesgehend ordentlich umgesetzt.
Nun ist es aber wichtig zu bedenken, wer diese paint()-Methode aufruft, und wann. Das geschieht in nahezu allen Fällen von Javas AWT-Thread. Dieser Thread ist einfach dafür verantwortlich, das die GUI immer so aussieht, wie sie eben aussehen soll. (Ladebalken bewegen sich, Sanduhren drehen sich, Buttons werden "eingedrückt", wenn mit der Maus auf sie klickt, und so weiter und so fort)
Wenn beispiels ein Fenster, oder in deinem Fall ein Applet auf dem Bildschirm angezeigt werden soll, dann durchläuft der AWT-Thread alle Komponenten und zeichnet sie auf den Schirm. Nun stell dir einmal vor, du würdest nicht nur eine Uhr, sondern gleich zehn, jede für eine verschiedene Zeitzone, anzeigen lassen. Dann würde die erste aktualisiert werden, der AWT Thread aber 500ms warten, dann in die nächste Uhr / Komponente springen, diese zeichnen, 500ms warten, und so weiter. Du merkst, ziemlicher Mist ;)

Das bedeutet, man macht das ganze etwas anders. Und zwar schreibt man die paint()-Methoden immer so, dass sie so schnell wie möglich ausgeführt werden.

Um nun steueren zu können, dass jede Sekunde neu gezeichnet werden soll, baust du einfach einen zusätzlichen Thread, der das entsprechende repaint() für deine Komponente aufruft.

Double-Buffering hat damit nun wiederrum gar nichts zu tun, sondern ist eine andere Baustelle. Hier wird zuerst alles, was angezeigt werden soll, in ein eigenes Bild geschrieben, was dann, nach allen Zeichenoperationen, einmal in einem Rutsch auf den Bildschirm gezeichnet wird. Damit reduziert man Flackern deutlich!

Ich pfriemel schnell ein Beispiel zusammen und reiche es nach...
 
Zuletzt bearbeitet: (Unzählige Rechtschreibfehler -.-)
Erstmal dickes Danke an Killkrog. Sehr gut erklärt ;)
Ich werd mir mal die Seite von soares anschauen und sie durcharbeiten.
Auch danke dafür :)

So, was in dem Kapitel gemacht wird hab ich bis jetzt alles verstanden.
Ein kleines Problem hab ich damit jedoch, und zwar hiermit:
"dim = getSize();"
Das er sich damit die größe holt ist mir klar, nur ist die hier immer gleich.
Ich hab jetzt leider noch nicht herausgefunden, wie ich die ändern kann.
Mit setSize(int x, int y) hab ich leider noch nicht viel Erfolg gehabt.
Kann mir dabei auch jemand helfen?
 
Zuletzt bearbeitet:
So, Beispiel ist fertig. Ist etwas länger geworden, dient aber hoffentlich der Übersichtlichkeit. Eventuell beantwortet das auch schon deine Frage betreffend der Größe.
Falls nicht, einfach nochmal anhauen!

Example.java
Code:
public class Example extends Applet {

	public void init() {
		// Hintergrundfarbe einstellen
		setBackground(Color.WHITE);

		// Panel, welches die Uhr zeichnet, erzeugen und einfügen
		final ClockPanel cPanel = new ClockPanel();
		add(cPanel);
		
		// Applet auf die korrekte Größe skalieren
		setSize(cPanel.getPreferredSize());

		// Neuen Thread starten, der jede Sekunde versucht neu zu zeichnen
		new Thread() {

			public void run() {
				while (true) {
					try {
						sleep(1000);
					} catch (InterruptedException e) {}

					cPanel.repaint();
				}
			};
		}.start();
	}
}

ClockPanel.java
Code:
public class ClockPanel extends JPanel {

	// ===================================================================
	// {[> Attributes
	// ==============
	private GregorianCalendar calendar = new GregorianCalendar();
	private BufferedImage offscreenImage;



	// ===================================================================
	// {[> Initializers and Constructors
	// =================================
	public ClockPanel() {
		setPreferredSize(new Dimension(400, 400));
	}



	// ===================================================================
	// {[> Private Methods
	// ===================
	private void paintClockBorder(Graphics2D g, int center, int radius) {
		g.setColor(Color.BLACK);

		int x1, y1, x2, y2;

		for (int i = 0; i < 12; i++) {
			x1 = center + (int) ((radius - 15) * Math.cos(Math.toRadians(30 * i)));
			y1 = center + (int) ((radius - 15) * Math.sin(Math.toRadians(30 * i)));
			x2 = center + (int) (radius * Math.cos(Math.toRadians(30 * i)));
			y2 = center + (int) (radius * Math.sin(Math.toRadians(30 * i)));
			g.drawLine(x1, y1, x2, y2);
		}

		for (int i = 0; i < 60; i++) {
			x1 = center + (int) ((radius - 2) * Math.cos(Math.toRadians(6 * i)));
			y1 = center + (int) ((radius - 2) * Math.sin(Math.toRadians(6 * i)));
			x2 = center + (int) (radius * Math.cos(Math.toRadians(6 * i)));
			y2 = center + (int) (radius * Math.sin(Math.toRadians(6 * i)));
			g.drawLine(x1, y1, x2, y2);
		}
	}

	private void paintClockCenterDot(Graphics2D g, int center, int size) {
		g.setColor(Color.BLACK);
		g.fillArc(center - size / 2, center - size / 2, size, size, 0, 360);
	}

	private void paintClockDate(Graphics2D g, int center, int xOffset, int yOffset, String text) {
		int textWidth = g.getFontMetrics(g.getFont()).stringWidth(text);
		int textHeight = (int) g.getFont().getLineMetrics(text, g.getFontRenderContext()).getHeight();

		g.drawString(text, xOffset + center - textWidth / 2, yOffset + center + textHeight / 2);
	}

	private void paintClockHands(Graphics2D g, int center, int radius, int hours, int minutes, int seconds) {
		int x, y;

		g.setColor(Color.BLACK);
		x = center + (int) (radius * 0.5 * Math.cos(Math.toRadians(hours % 12 * 30 + minutes / 2.0 - 90)));
		y = center + (int) (radius * 0.5 * Math.sin(Math.toRadians(hours % 12 * 30 + minutes / 2.0 - 90)));
		g.drawLine(center, center, x, y);

		x = center + (int) (radius * 0.65 * Math.cos(Math.toRadians(minutes * 6 - 90)));
		y = center + (int) (radius * 0.65 * Math.sin(Math.toRadians(minutes * 6 - 90)));
		g.drawLine(center, center, x, y);

		g.setColor(Color.RED);
		x = center + (int) (radius * 0.8 * Math.cos(Math.toRadians(seconds * 6 - 90)));
		y = center + (int) (radius * 0.8 * Math.sin(Math.toRadians(seconds * 6 - 90)));
		g.drawLine(center, center, x, y);
	}

	private void paintClockTimeDigital(Graphics2D g, int center, int xOffset, int yOffset, String text) {
		int textWidth = g.getFontMetrics(g.getFont()).stringWidth(text);
		int textHeight = (int) g.getFont().getLineMetrics(text, g.getFontRenderContext()).getHeight();

		g.drawString(text, xOffset + center - textWidth / 2, yOffset + center + textHeight / 2);
	}



	// ===================================================================
	// {[> Public Methods
	// ==================
	public void paint(Graphics g) {
		// Sicherstellen, dass das Offscreen-Image existiert und die korrekte Größe hat!
		final int width = getWidth(), height = getHeight();
		if (offscreenImage == null || offscreenImage.getWidth() != width || offscreenImage.getHeight() != height) {
			offscreenImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
		}
		Graphics2D g2d = offscreenImage.createGraphics();

		// Erstmal alles löschen (mit der Hintergrundfarbe des beinhaltenden Containers überschreiben)
		g2d.setColor(getParent().getBackground());
		g2d.fillRect(0, 0, width, height);

		// Antialiasing aktivieren
		g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

		// Paar Variablen initialisieren und Uhrzeit bestimmen
		final int center = width / 2;
		final int radius = center - 15;

		calendar.setTimeInMillis(System.currentTimeMillis());
		final int hours = calendar.get(Calendar.HOUR_OF_DAY);
		final int minutes = calendar.get(Calendar.MINUTE);
		final int seconds = calendar.get(Calendar.SECOND);

		final int year = calendar.get(Calendar.YEAR);
		final int month = calendar.get(Calendar.MONTH) + 1;
		final int day = calendar.get(Calendar.DAY_OF_MONTH);

		// Die einzelnen Elemente der Uhr zeichnen
		paintClockBorder(g2d, center, radius);
		paintClockCenterDot(g2d, center, 6);
		paintClockHands(g2d, center, radius, hours, minutes, seconds);
		g2d.setColor(Color.BLUE);
		g2d.setFont(g.getFont().deriveFont(24f));
		paintClockDate(g2d, center, 0, -100, String.format("%02d.%02d.%04d", day, month, year));
		paintClockTimeDigital(g2d, center, 0, 100, String.format("%02d:%02d:%02d", hours, minutes, seconds));

		// Aufräumen
		g2d.dispose();

		// Den Inhalt des OffscreenImage in einem Rutsch zeichnen lassen
		g.drawImage(offscreenImage, 0, 0, null);
	}
}
 
Ich werde es durcharbeiten und mich so bald wie möglich melden, sobald ich Fragen habe melde iczh mich wieder hier.
Und mal ein dickes danke für deine Mühe :)
 
Wobei man darauf hinweisen sollte, dass repaint() eine der wenigen Methoden in Swing ist, die Thread-safe ist! Ansonsten müsste man selbst dafür sorgen, weswegen man oft gleich den Swing-Timer nimmt, der auch noch weitere Funktionalitäten besitzt.
 
Und da habe ich direkt eine Frage zum Code.
Und zwar hierzu : private void paintClockBorder(Graphics2D g, int center, int radius)

Wieso kannst du in der Methode mit center und radius rechnen?
Ich seh nirgens das du die initialisiert hast. Oder wie funktioniert das?
Wo beklommt der int den Wert her?
 
Achso, die nehmen dann die Werte aus der paint Methode?
Also:
final int center = width / 2;
final int radius = center - 15;
die 2?

und warum genau ist der radius cemter - 15?
 
Genau. Diese Werte werden benutzt, weil sie beim Aufruf der jeweiligen Methode als Parameter übergeben werden.

Zum Radius:
center = width / 2
radius = width / 2, da ich aber ungern zweimal rechne, in dem fall radius = center.
Die "-15" sind nur da, damit die Elemente der Uhr "nicht direkt am Rand kleben" ;)
 
Vielleicht noch ein kleiner Hinweis zum Überschreiben der richtigen JComponent-Methode:
Wenn es um das Zeichnen von Komponenten geht, nimmt man paintComponent(), nicht paint().

In der Methode paint() das Zeichnen von Inhalt (paintComponent), Rahmen (paintBorder) und Kind-Komponenten (paintChildren) organisiert.
 
Und noch eine Frage:
calendar.setTimeInMillis(System.currentTimeMillis());
Diese Zeile. Ich hab die mal auskommentiert, dann geht das Applet nicht mehr richtig.
Was bewirkt die Zeile? Kann damit nicht viel anfangen leider...
 
Net so schwer.
In deinem alten Versuch hast du jedes mal, wenn die Paint aufgerufen wurde, ein neues Objekt vom Typ GregorianCalendar erzeugt, von dem die Zeiten ausgelesen und angezeigt. Ansich natürlich korrekt, allerdings ist es erstrebenswert, so selten wie möglich neue Objekte zu initialisieren. Das erzeugt Overhead, außerdem muss jedes Objekt irgendwann wieder von der GarbageCollection aufgeräumt werden. Wenn das jetzt nur jede Sekunde passiert, ist es eigentlich völlig egal, aber entweder man programmiert immer ordentlich, oder halt eben nicht ;)
Ich habe daher nur einmal eine Variable 'calendar' deklariert und initialisiert. (Siehst du oben in der Datei bei 'Attributes')
Jedes mal, wenn nun die paint()-Methode aufgerufen wird, wird diesem Objekt nur die aktuelle Unix-Zeit in Millisekunden übergeben. Damit aktualisiert der GregorianCalendar selbstständig sein Datum.
 
Ich brauche noch eine kleine Hilfe ;)
Und zwar zu diesen Variablen:
int xOffset, int yOffset, String text
Diese sind ja wieder als Parameter übergeben.
Nur ich seh nirgens wo diese auch deklariert werden.
Hab ich das übersehen?
Oder wie hast du das gelöst?
 
Also... Du scheinst ja noch wirklich gar keine Ahnung vom Programmieren zu haben. Such dir mal ein paar schöne Bücher oder Tutorials, die dir die Grundlagen vermitteln (Was sind Methoden, Paramter, Variablen, etc.)
Die Offsets und Text wurden direkt im Methodenaufruf übergeben.
 
Es geht mit den Kenntnissen.
Hab schon viele Tutorials durchgearbeitet, nur mit Methoden hab ich halt erst neu angefangen, deswegn hab ich da so viele Fragen. Danke dafür das du sie mir beantwortest ;).
Werde deinen Quellcode noch weiter durcharbeiten. Habe schon viel neues Gelernt ;)
 
Zurück
Oben