Richtiger Umgang mit dem Grafikkontext

In Delphi ist es jederzeit erlaubt, sich von einer Komponente einen Grafikkontext (= TCanvas) zu besorgen und darauf zu zeichnen. Das ist insbesondere praktisch, wenn ein Maus-Event eine grafische Reaktion (z.B. Ziehen eines Rechtecks über eine Zeichenfläche) hervorruft. Die Zeichenaktion wird einfach innerhalb der Event-Bearbeitungsmethode platziert.

Auch unter Java kann man sich jederzeit mit JComponent.getGraphics() einen Grafikkontext zu jeder beliebigen Komponente besorgen. Meine ersten Versuche, grafische Reaktionen auf Mausereignisse zu implementieren, sahen daher etwas so aus:

	public void mousePressed(MouseEvent arg0) {
                /*
                 * Don't do this:
                 */
		Graphics2D g2d = (Graphics2D) getGraphics();
		g2d.setColor(Color.red);
		g2d.drawRect(arg0.getX(), arg0.getY(), 10, 10);
	}

Sie produzierten zwar manchmal das richtige Ergebnis, oft kam es aber auch zu sehr seltsamen Artefakten oder zu gar keiner Reaktion. Inzwischen ist mir klar: Zeichnen in Java ist nur innerhalb der PaintComponent(Graphics g) - Methode erlaubt! Es gibt eine ausführliche Dokumentation von Sun zum Zeichnen unter AWT/Swing, die recht gut erklärt, welche Methoden beim Neuzeichnen eines Bildschirmausschnitts der Reihe nach aufgerufen werden. Weshalb obiges unter Swing nicht funktioniert, wird leider nicht erklärt, aber zumindest findet man unter der Überschrift Lightweights & System-triggered Painting einen Hinweis darauf sowie die Anleitung, wie man es richtig macht:

Ein Problem besteht weiterhin: Alles im übergebenen Rechteck muss komplett neugezeichnet werden. Im Fall eines vektororientierten Zeichenprogramms kann dies aufwändige Zeichenoperationen vieler hundert kleiner Objekte unter- und überhalb des veränderten Objekts bedeuten. Dies dauert i.a. viel zu lange.

Lösung: Zu Beginn der Mausziehaktion werden zwei Puffer (BufferedImage) angelegt. In den ersten wird der Hintergrund sowie alle Objekte unterhalb des gezogenen Objekts gezeichnet. Der zweite wird zunächst transparent gefüllt. Dann werden alle Objekte hineingezeichnet, die unterhalb des gezogenen Objekts liegen. Beim jedem MouseMove-Event wird dann der Reihe nach folgendes gemacht:

  1. Ersten Puffer in den Bildschirm kopieren
  2. gezogenes Objekt zeichnen
  3. zweiten Puffer in den Bildschirm kopieren


Befinden sich viele Objekte auf dem Bildschirm, so kommt es durch das Füllen der beiden Puffer zu Beginn des „Ziehvorgangs“ zu einer kurzen Verzögerung, die sich anfühlt, als müsse man das gezogene Objekt von seinem bisherigen Platz „wegreißen“ und linear mit der Anzahl der Objekte auf dem sichtbaren Teil des Bildschirms skaliert. Danach läuft der ganze Ziehvorgang glatt durch (konstante Laufzeit).