# EINFACHE Verständnisfrage zu repaint(), paintComponent(), usw.



## ernst (3. Mrz 2010)

Hallo allerseits,
habe ein Verständnisproblem mit repaint(), paintComponent(), usw. 
Mein Problem: Was macht repaint alles?
Habe dazu folgendes recherchiert:
Aufrufreihenfolge von repaint() in Swing:
repaint() --> paint() --> paintComponent(..) --> paintBorder() --> paintChildren()

I) (Demo-Programm dazu befindet sich weiter unten. Es wurde mit dem Netbeans Visual Editor erstellt)
Angenommen  repaint() wird in einer Subklasse von JFrame aufgerufen (SubJFrame genannt). 
Das Fenster SubJFrame soll eine Zeichenfläche SubJPanel (eine Subklasse von JPanel) enthalten, ein paar Textfelder, Buttons usw.
(Im Demo-Programm enthält das Fenster der Einfachheit halber genau eine Zeichenfläche und genau ein Textfeld).
In einer Methode von SubJFrame soll nun repaint() aufgerufen werden.
Was passiert?
repaint() ruft intern paintComponent(..) auf. 
Was macht paintComponent(..) ? 
1) Zeichnet es nur die Zeichenfläche des Fensters (bzw. die Umrandung mit paintBorder())?
2) Was macht dann aber paintChildren() ?  Siehe Aufrufreihenfolge oben.
3) Wer veranlasst, dass - nicht nur die Zeichenfläche - sondern auch die Textfelder (und der darin enthaltene Text), Buttons usw. neu gezeichnet werden?
Habe zu Testzwecken in SubJFrame kein repaint(() gemacht, aber den Inhalt eines Textfelds (mit einem Timer) und mit
setText(..) laufend ändern lassen. Trotzdem (obwohl kein repaint() gemacht wird) wird der Inhalt des Textfelds auf dem Bildschirm laufend verändert (aktualisiert).  Warum wird das Textfeld ohne repaint verändert und warum braucht man - um das laufend verschobene Rechteck in den Zeichenfläche darzustellen- den Aufruf von repaint()?  
Die Java-Doku hilft mir hier nicht weiter (bzw. damit kann ich diesbezüglich wenig anfangen).

mfg
Ernst


```
package pack1;


import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Timer;

public class SubJFrame extends javax.swing.JFrame {
    private SubJPanel subJPanel;
    private Timer t;
    private int testzahl;
    private String teststr;

    public SubJFrame() {
        initComponents();
        testzahl=0;
        subJPanel = new SubJPanel();
        // An die Zeichenfläche muss das SpielJFrame montiert werden.
        jPanel1.add(subJPanel, "Center");

        t = new Timer(2,
                new ActionListener() {
                    public void actionPerformed(ActionEvent ae) {
                        testzahl++;
                        teststr = String.valueOf(testzahl);
                        jTextField1.setText(teststr);
                        //repaint();
                    }
                });
        t.start();
    }






    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">
    private void initComponents() {

        jPanel1 = new javax.swing.JPanel();
        jTextField1 = new javax.swing.JTextField();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);

        jPanel1.setBackground(new java.awt.Color(204, 255, 204));
        jPanel1.setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(0, 0, 0)));
        jPanel1.setLayout(new java.awt.BorderLayout());

        jTextField1.setText("jTextField1");

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, 390, Short.MAX_VALUE)
                    .addGroup(layout.createSequentialGroup()
                        .addGap(10, 10, 10)
                        .addComponent(jTextField1, javax.swing.GroupLayout.PREFERRED_SIZE, 85, javax.swing.GroupLayout.PREFERRED_SIZE)))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addGap(6, 6, 6)
                .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, 170, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addGap(38, 38, 38)
                .addComponent(jTextField1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addContainerGap(66, Short.MAX_VALUE))
        );

        pack();
    }// </editor-fold>

    /**
    * @param args the command line arguments
    */
    public static void main(String args[]) {
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                new SubJFrame().setVisible(true);
            }
        });
    }

    // Variables declaration - do not modify
    private javax.swing.JPanel jPanel1;
    private javax.swing.JTextField jTextField1;
    // End of variables declaration

}
```


```
package pack1;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Timer;

public class SubJPanel extends javax.swing.JPanel {
    private int ortX;
    private int ortY;

    public SubJPanel() {
        initComponents();
        ortX=0;
        ortY=0;
    }


    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.fillRect(ortX, ortY, 30, 25);
        if(ortX <= 400){
            ortX = ortX + 1;
        }
        else{
            ortX=0;
        }
    }


    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">                          
    private void initComponents() {

        setBackground(new java.awt.Color(255, 255, 204));
        setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(0, 0, 0)));

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 398, Short.MAX_VALUE)
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 298, Short.MAX_VALUE)
        );
    }// </editor-fold>                        


    // Variables declaration - do not modify                     
    // End of variables declaration                   

}
```


----------



## Michael... (3. Mrz 2010)

Wie Du schon sagtest bei Swingkomponenten wird zunächst paint(...)  aufgerufen, welche selbst wiederum in folgender Reihefolge

```
paintComponent(...) zeichnen der "leeren" Komponente
paintBorder(...) zeichenen der Border
paintChildren(...) ruft letztendlich paint() der zur Komponente gehörenden Subkomponenten auf
damit diese sich "auf" das Graphics Objekt zeichnen können.
```
aufruft.
Dass im Falle von JTextField der Text auch ohne aktives Aufrufen von repaint() geändert wird liegt wohl daran, dass diese Methode - wie auch immer - ein Neuzeichnen der Komponente anfordert.


----------



## ernst (3. Mrz 2010)

Michael... hat gesagt.:


> Wie Du schon sagtest bei Swingkomponenten wird zunächst paint(...)  aufgerufen, welche selbst wiederum in folgender Reihefolge
> 
> ```
> paintComponent(...) zeichnen der "leeren" Komponente
> ...


Danke für deinen Beitrag.
Um das noch besser zu verstehen, habe ich noch eine Bitte:
Kannst du mir das an meinem konkreten Beispiel (ein Fenster besitzt genau eine Zeichenfläche und ein Textfeld) erklären?
paintComponent(...) zeichnen der "leeren" Komponente (welche konkret in meinem Beispiel?)
paintBorder(...) zeichenen der Border (welche konkret in meinem Beispiel?)
paintChildren(...) ruft letztendlich paint() der zur Komponente gehörenden Subkomponenten auf(welche konkret in meinem Beispiel?)

mfg
Ernst


----------



## Marco13 (3. Mrz 2010)

Mal ganz abgesehen davon, dass in so einem TextFeld manchmal ein Cursor blinkt... : Was ist so abwegig daran, dass schon "setText" ein repaint auslöst? (Sowas wie 'rectangle.x=newX' macht das nicht, da muss man manuell repainten...)


----------



## ernst (3. Mrz 2010)

Marco13 hat gesagt.:


> Mal ganz abgesehen davon, dass in so einem TextFeld manchmal ein Cursor blinkt... : Was ist so abwegig daran, dass schon "setText" ein repaint auslöst? (Sowas wie 'rectangle.x=newX' macht das nicht, da muss man manuell repainten...)


Aber genauso könnte man auch fragen:
Warum löst eine Änderung in einer Zeichenfläche (konkret hier in meinem Fall die Verschiebung eines Rechtecks an einen anderen Ort der Zeichenfläche) kein repaint aus?

mfg
Ernst


----------



## Marco13 (3. Mrz 2010)

Erstmal wäre es bei dem Beispiel so, dass man mit
rectangle.x = newX;
nur einer Variablen einen neuen Wert zuweisen würde. An eine Variable kann man keinen Listener hängen. Da braucht's schon einen Methodenaufruf wie
rectangle.setX(newX);
und es müßte intern die richtige "Verdrahtung" mit Listenern geben. Außerdem wäre ja nicht sichergestellt, das so ein Rechteck angezeigt werden soll. Vielleicht will man einfach nur irgendwas drin speichern oder sonstwie damit rum-rechnen....

Bei einem TextField ist die Sache aber klarer: Wenn sich darin etwas ändert, muss sowieso neu gezeichnet werden - sonst sieht man ja nichts. (OB nun wirklich explizit von einem "setText" über verschlungene Wege ein "repaint" ausgelöst wird, weiß ich nicht - das könnte man nachprüfen, aber ich gehe stark davon aus). (Wobei sich mir die Frage stellt, in welchem Zusammenhang die Frage, ob ein setText denn ein repaint auslöst oder nicht, relevant sein könnte....!?)


----------



## André Uhres (4. Mrz 2010)

In deinem Fall geschieht die Verschiebung innerhalb von "paintComponent". Das ist eine Methode, die durch "repaint" ausgelöst wird (nicht eine Methode, die selbst ein repaint auslöst). 
Im Timer würde ich "subJPanel.repaint()" aufrufen. Da "subJPanel" weder Rand noch Kinder hat (und keine Transparenz oder Überlappung zu behandeln ist), wird außer der Komponente selbst(paintComponent) weiter nichts gezeichnet.
Bei "jPanel1.repaint()" würde zuerst das leere jPanel1 gezeichnet werden, dann sein Rand und schließlich sein Kind (subJPanel).
Eine Einführung in den Malmechanismus findest du hier: Malen in Swing Teil 1: der grundlegende Mechanismus - Byte-Welt Wiki


----------



## ernst (4. Mrz 2010)

André Uhres hat gesagt.:


> In deinem Fall geschieht die Verschiebung innerhalb von "paintComponent". Das ist eine Methode, die durch "repaint" ausgelöst wird (nicht eine Methode, die selbst ein repaint auslöst).
> Im Timer würde ich "subJPanel.repaint()" aufrufen. Da "subJPanel" weder Rand noch Kinder hat (und keine Transparenz oder Überlappung zu behandeln ist), wird außer der Komponente selbst(paintComponent) weiter nichts gezeichnet.
> Bei "jPanel1.repaint()" würde zuerst das leere jPanel1 gezeichnet werden, dann sein Rand und schließlich sein Kind (subJPanel).
> Eine Einführung in den Malmechanismus findest du hier: Malen in Swing Teil 1: der grundlegende Mechanismus - Byte-Welt Wiki


Um nachzuprüfen, ob ich alles richtig verstanden habe:

1)
Die Kinder von subJFrame sind:
jPanel1 und jTextfield1, weil sie mit der Methode add... an das Fenster subJFrame montiert wurden.
Also (Montage mit)
addComponent(jPanel1,...) und 
addComponent(jTextfield1,...).
Ist das alles richtig?

2)
Das Kind von jPanel1 ist subJPanel, weil subJPanel an jPanel1 montiert wurde mit:
jPanel1.add(subJPanel, "Center"); 
Damit ist subJPanel ein Kindeskind von subJFrame.
Ist das alles richtig?

3)
"Im Timer würde ich "subJPanel.repaint()" aufrufen. Da "subJPanel" weder Rand noch Kinder hat (und keine Transparenz oder Überlappung zu behandeln ist), wird außer der Komponente selbst(paintComponent) weiter nichts gezeichnet."
Was wäre, wenn subJPanel noch Kinder hätte?.
Dann würden durch subJPanel.repaint() auch die Kinder von subJPanel gezeichnet.
Ist das richtig?

4)
Was würde gemacht werden, wenn man (statt subJPanel.repaint()) nur repaint() machen würde?
Meine Antwort:
a) Es würde das Kind jPanel1 gezeichnet werden (und dann das Kind von jPanel1, also subJPanel)
b) Dann würde das andere Kind, also jTextField1 gezeichnet werden.
Kurz: Es würde _alles_ gezeichnet werden.
Ist das alles richtig?

5)
Warum machst du subJPanel.repaint(), wenn man mit repaint() auch alles zeichnen könnte ?
Ich vermute wegen der Laufzeit.
Stimmt das ?

mfg
Ernst


----------



## Michael... (4. Mrz 2010)

zu 1) bis 3): ja, wenn das so zutrifft, wie Du es beschrieben hast.


> 4)
> Was würde gemacht werden, wenn man (statt subJPanel.repaint()) nur repaint() machen würde?


Das würde gar nicht funktionieren, da Du die in einer inneren Klasse befindest und diese Klasse besitzt keine Methode repaint();


> Meine Antwort:
> a) Es würde das Kind jPanel1 gezeichnet werden (und dann das Kind von jPanel1, also subJPanel)


selbst wenn's funktionieren würde, wieso sollte jPanel1 neu gezeichnet werden?
Methoden werden immer an ihren Objekten aufgerufen. Entweder man befindet sich "im" Objekt (innerhalb der Klasse die jPanel1 definiert) dann kann man die Methode direkt aufrufen oder man befindet sich nicht im Objekt ruft die Methode über eine Referenzvariable auf z.B. jPanel1.repaint();

zu 5) es hat sich ja nur in subPanel etwas "verändert", warum sollte dann alles andere neu gezeichnet werden.


----------



## André Uhres (4. Mrz 2010)

ernst hat gesagt.:


> Um nachzuprüfen, ob ich alles richtig verstanden habe


Du hast alles richtig verstanden.


----------



## André Uhres (4. Mrz 2010)

Michael... hat gesagt.:
			
		

> Das würde gar nicht funktionieren, da Du dich in einer inneren Klasse befindest und diese Klasse besitzt keine Methode repaint();


Das funktioniert trotzdem, weil innere Klassen auf die Methoden der äusseren Klasse zugreifen können.


			
				Michael... hat gesagt.:
			
		

> selbst wenn's funktionieren würde, wieso sollte jPanel1 neu gezeichnet werden?


Weil in diesem Fall der ganze Frame neu gemalt wird (die vollständige Containerhierarchie).


----------



## Michael... (5. Mrz 2010)

Stimmt der Timer befindet sich ja in der von JFrame abgeleiteten Klasse und ein einfaches repaint() würde dann die Methode des JFrame aufrufen.


----------



## ernst (5. Mrz 2010)

André Uhres hat gesagt.:


> Du hast alles richtig verstanden.


Eine Nachfrage:
Angenommen, man montiert das Kind jPanel1 (von subJFrame) nicht mit add an das Fenster subJFrame.
Dann kann man zwar 
jPanel1.repaint()
aufrufen (und das Programm wird auch fehlerfrei ausgeführt), aber
die Zeichenfläche wird nicht auf den Bildschirm gezeichnet.
Warum? Weil das Montieren mit add eine notwendige Voraussetzung für das Ausgeben der Zeichenfläche auf dem Bildschirm ist.
Ist das richtig?

mfg
Ernst


----------



## Michael... (5. Mrz 2010)

ernst hat gesagt.:


> Weil das Montieren mit add eine notwendige Voraussetzung für das Ausgeben der Zeichenfläche auf dem Bildschirm ist.
> Ist das richtig?


Korrekt, jede graphische Komponente braucht ein Fenster in dem es dargestellt wird.

Du kannst ja auch eine DVD in einem DVD Player abspielen lassen - solange der DVD Player nicht am Fernseher angeschlossen ist, läuft zwar der Film aber sehen wirst Du ihn trotzdem nicht ;-)


----------



## ernst (5. Mrz 2010)

Michael... hat gesagt.:


> Korrekt, jede graphische Komponente braucht ein Fenster in dem es dargestellt wird.
> 
> Du kannst ja auch eine DVD in einem DVD Player abspielen lassen - solange der DVD Player nicht am Fernseher angeschlossen ist, läuft zwar der Film aber sehen wirst Du ihn trotzdem nicht ;-)



Danke für die Info.
Ein sehr schöner, _anschaulicher_ Vergleich.

mfg
Ernst


----------



## André Uhres (5. Mrz 2010)

ernst hat gesagt.:


> ...und das Programm wird auch fehlerfrei ausgeführt...


..mit der Einschränkung, daß  die "paint" Methode der vom sichtbaren Fenster losgelösten Komponente nicht ausgeführt wird. 
Ähnlich verhält es sich, wenn eine Komponente zwar in ein sichtbares Fenster eingebunden ist, aber diese Komponente
- hat keine Größe (Länge=Höhe=0)
- wird von einer anderen Komponente überdeckt
- wurde mit setVisible(false) unsichtbar gemacht
In jedem dieser Fälle kann man zwar einen "repaint" Antrag für die Komponente stellen, aber die "paint" Methode dieser Komponente wird nicht ausgeführt.


----------



## ernst (6. Mrz 2010)

Danke an alle, die mir mit ihren Beiträgen geholfen haben.
Damit ist dieses Thema für mich erst mal erledigt.

mfg
Ernst


----------

