Auf Components zeichnen

Aus Byte-Welt Wiki
Zur Navigation springenZur Suche springen

Siehe auch Malen in AWT und Swing

Um eine Grafik auf eine Component- also auch z.B. einen Frame - zu zeichnen, muss grundsätzlich die Methode public void paint (Graphics g) überschrieben werden. Diese Methode wird vom Toolkit jedesmal auf dem GUI-Thread aufgerufen, wenn es Zeit ist zu malen. Bei JComponent sollte übrigens die Methode paintComponent überschrieben werden. Vor allem bei JFrames ist es auch wichtig, die paint-Methode der Superklasse aufzurufen: super.paint(g); Generell wird jedoch davon abgeraten, direkt auf einen JFrame zu malen. Statt dessen sollte man auf eine JComponent malen, die dem JFrame hinzugefügt wird. Bei GUI-Komponenten ist zudem zu beachten, dass AWT und Swing nicht threadsicher sind, und diese aus anderen Threads nicht gezeichnet werden können, es sei denn, diese Threads wurden über die statischen Methoden aus java.awt.EventQueue, javax.swing.SwingUtilities oder mit einem SwingWorker mit dem GUI-Thread synchronisiert (dieser Thread wird auch Event Dispatch Thread genannt, oder kurz EDT).

Ziemlich nutzlos ist es hingegen, über die Graphics aus Component#getGraphics() zu zeichnen. Beim nächsten repaint, oder wenn der Frame zum Beispiel minimiert und wieder maximiert wird, wird das so Gezeichnete wieder verschwinden. Wann immer man also getGraphics()-Methode benutzt wird (und das wird fast nie der Fall sein), sollte man genau wissen, was man macht.

Die einzige Möglichkeit, während des Programmablaufes gezeichnete Dinge permanent auf die Component zu zeichnen, besteht somit darin, alles, was gezeichnet werden soll, in einer Datenstruktur (Array, ArrayList, usw.) zu speichern und das dann in der Methode paint()/paintComponent() auf das Graphics-Objekt zu übertragen, das dort als Parameter vom Toolkit übergeben wird. Dies könnte zum Beispiel so aussehen:

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

/**
 * Diese Component muss zum JFrame hinzugefügt werden.
 */
public class JPaintComponent extends JComponent {

    private List<PaintableObject> paints = new ArrayList<PaintableObject>();

    public void paintObj(final PaintableObject po) {
        paints.add(po);
    }

    public List<PaintableObject> getObjects() {
        return paints;
    }

    public void removeObj(final PaintableObject po) {
        paints.remove(po);
    }

    public void clear() {
        paints.clear();
    }

    @Override
    protected void paintComponent(final Graphics g) {
        for (PaintableObject po : paints) {
            po.update(g);
        }
    }

    //nur zum Testen:
    public static void main(final String[] args) {
        Runnable gui = new Runnable() {

            public void run() {
                JFrame f = new JFrame("JPaintComponent");
                f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                f.setSize(220, 250);
                f.setLocationRelativeTo(null);
                JPaintComponent c = new JPaintComponent();
                c.paintObj(new PaintableLine(Color.RED, 10, 10, 200, 200));
                c.paintObj(new PaintableLine(Color.BLUE, 10, 200, 200, 10));
                f.add(c);
                f.setVisible(true);
            }
        };
        EventQueue.invokeLater(gui);
    }
}
import java.awt.*;

/**
 * Nur Klassen, die von dieser Klasse abgeleitet wurden, können gezeichnet werden.
 */
public abstract class PaintableObject {

    private Color actual;

    public PaintableObject(final Color actual) {
        this.actual = actual;
    }

    public void setColor(final Color newColor) {
        actual = newColor;
    }

    void update(final Graphics g) {
        g.setColor(actual);
        paint(g);
    }

    public abstract void paint(final Graphics g);
}
import java.awt.*;

/**
 * Beispielimplementation einer Linie.
 */
public class PaintableLine extends PaintableObject {

    private int x1;
    private int y1;
    private int x2;
    private int y2;

    public PaintableLine(final Color c, final int x1, final int y1, final int x2, final int y2) {
        super(c);
        this.x1 = x1;
        this.x2 = x2;
        this.y1 = y1;
        this.y2 = y2;
    }

    public void paint(final Graphics g) {
        g.drawLine(x1, y1, x2, y2);
    }
}

Um neu zeichnen zu lassen, kann die Methode repaint() aufgerufen werden. Es wird nicht empfohlen, dass Programme paint()/paintComponent() direkt aufrufen. Die Aufrufe dieser Methoden werden durch die VM automatisch erledigt, sobald dies nötig werden sollte.