Swing repaint() ruft paintComponent(g1d) nicht auf

liann

Mitglied
Hallo, ich habe gerade vermutlich einen blackout, aber nach 40 minuten im code und internet suchen , finde ich echt nichts 🤯.
repaint() wird bei dem Label augerufen, jedoch paintComponent(g1d) nicht:
Java:
// JLabel object draw
draw = new JLabel() {
            @Override
            protected void paintComponent(Graphics g1d) {
                super.paintComponent(g1d);
                draw((SunGraphics2D) g1d);
                System.out.println("painted!");
                // wird nicht aufgerufen
            }

            @Override
            public void repaint() {
                super.repaint();
                System.out.println("repainted draw!"); // wird immer aufegrufen
            }
        };

private void draw(SunGraphics2D g2) {
        g2 = (SunGraphics2D) g2.create();
        drawBackground(g2);
        drawGames(g2);
        g2.dispose();
// auch ohne create() und somit auch ohne dispose() klappt es nicht
    }

private void drawGames(Graphics2D g2) {
        for (Drawable drawable : games)
            if (drawable != null)
                drawable.draw(g2);
        for (Drawable drawable : games)
            if (drawable != null)
                drawable.drawInfoText(g2);
    // hier müssten keine Probleme sein: die gleichen methoden werden in anderen Klassen auch aufgerufen
    }

    private void drawBackground(SunGraphics2D g2) {
        g2.setColor(Color.darkGray);
        g2.fillRect(0, 0, getWidth(), getHeight());
    }
Wo ist mein Fehler??
 
1. die super-Aufrufe sollten immer die letzte Anweisung sein,
2. repaint() solltest du nicht überschreiben, sondern nur aufrufen,
3. dispose() muss imho nicht aufgerufen werden,
 
repaint() solltest du nicht überschreiben, sondern nur aufrufen,
gilt das für alle oder nur für das label zum zeichnen? Denn in meinem JFrame wird repaint() überschrieben mit:
Java:
@Override
public void repaint() {
    super.repaint();
    draw.repaint();
}
Das überschreiben im Label war nur da, um mir selber sicher zu gehen, dass die methode aufgerufen wird, was sie wird.
Die anderen Sachen habe ich kurz geändert, jedoch klappt es immer noch nicht
 
Okay also ich habe jetzt ein mögliches Problem gefunden, wenn ich nämlich repaint() per knopfdruck aufrufe, klappt alles. Dann müsste es an folgender Methode liegen. Ich weiß aber nicht, was daran falsch sein sollte. Ich will nämlich einen Übergang zwischen zwei JFrames erzeugen, der flüssig ist.
Java:
 public void transformTo(JFrame jframe) {
        while (jframe.getWidth() != getWidth() || jframe.getHeight() != getHeight()) {
            int x = jframe.getWidth() - getWidth();
            int y = jframe.getHeight() - getHeight();
            if (x < 0) {
                x++;
            } else if (x > 0) {
                x--;
            }
            if (y < 0) {
                y++;
            } else if (y > 0) {
                y--;
            }
            setSize(jframe.getWidth() - x, jframe.getHeight() - y);
            setLocationRelativeTo(null);
            repaint(); // ruft nicht warum auch immer paintComponent(g1d)
        }
}
     private void addBtn(){
         JButton b = new JButton();
         add(b);
         b.addActionListener(e -> repaint()); // ruft paintComponent(g1d) auf
     }
 
Ich weiß aber nicht, was daran falsch sein sollte. Ich will nämlich einen Übergang zwischen zwei JFrames erzeugen, der flüssig ist.
Das Problem ist, dass Deine transferTo-Methode im Event Dispatch Thread (EDT, aka UI-Thread) ausgeführt wird. So lange Deine Schleife läuft, wird sich Dein UI nicht aktualisieren. Du muss dafür sorgen, dass die Schleife in einem anderen Thread ausgeführt wird, während die Methoden, die das UI verändern, im EDT laufen. Das repaint() am Ende der Schleife dürfte überflüssig sein, da das Fenster bei einer Größenänderung sowieso neu gezeichnet werden muss.

Je nach Implementierung musst Du dann berücksichtigen, dass nun zwei Threads im Spiel sind und sich die Größenänderung nicht sofort auswirkt. D. h. die Prüfung zu Beginn der Schleife würde ohne weitere Maßnahmen ggf. auf die alten Werte zugreifen.

Die einfachste Variante scheint mir hier ein Swing-Timer zu sein, auch wenn dieser nicht besonders präzise ist.
 
Jetzt möchte ich auch einfach mal ein paar Punkte kommentieren.

1. die super-Aufrufe sollten immer die letzte Anweisung sein,
2. repaint() solltest du nicht überschreiben, sondern nur aufrufen,
3. dispose() muss imho nicht aufgerufen werden,

1. Also an welcher Stelle die super Aufrufe erfolgen ist nicht vorgegeben. Die Frage ist halt, ob und an welcher Stelle die Funktionalität gewünscht wird. Das ist also nicht so pauschal ausdrückbar.

2. In der Regel reicht es, repaint nur zu nutzen. Aber wenn die Funktionalität erweitert werden soll, dann spricht natürlich auch nichts dagegen, repaint zu überschreiben. Die Frage ist also: was ist dieses draw? Die Logik scheint mir ein wenig verrückt zu sein, aber generell spricht nichts dagegen, auch an der Stelle etwas einzufügen. Aber darauf gehe ich später noch einmal ausführlicher ein.

3. dispose() ist richtig, denn er hat ein create(), sprich er erzeugt die Graphics Instanz und muss daher diese auch entsorgen.
Aber wozu dient der create() Aufruf? Der ist aus meiner Sicht unnötig. Die Instanz, die übergeben wird, kann direkt verwendet werden.

Generell ist wichtig, die Grundlagen zu verstehen. Was passiert da denn, wenn ein invalidate, repaint oder ähnliches gemacht wird - sei es aktiv im code oder indirekt durch das System?

Erst einmal: Was passiert da in so einem Programm? Man hat, wenn man es einfach betrachtet, einfach eine Schleife in einem Thread (EDT Thread), die Events abarbeitet. Events können sein: Tastendrücke, Mausclicks, Mausbewegungen, Anforderungen, etwas zu tun, ... Das ist also wichtig, denn ein repaint ist eigentlich nichts anderes als: Wie packen ein Event an das Fenster in die Eventqueue, die sagt: Mal dich neu. Das findet somit erst statt, wenn das Event bearbeitet werden kann. So lange die Applikation also nicht zu dem Event kommt, passiert nichts. (Siehe Antwort von @mihe7)

Wenn ein Betriebssystem Fenster darstellt, dann interessiert es sich nicht für den Inhalt. Für die Darstellung des Inhalts ist das Programm zuständig. Was aber das Betriebssystem interessiert ist: Ist das Fenster korrekt dargestellt. Das ist etwas, das sich z.B. verändert, wenn ein anderes Fenster das Fenster Deiner Applikation überdeckt. Dann ist klar: Dann ist der Inhalt des Fensters in dem überlappten Bereich nicht mehr gültig. Das kann man auch erreichen, indem man invalidate aufruft und den Bereich angibt, der nicht mehr gültig sein soll.
Wenn das Betriebssystem nun so einen Bereich erkennt, dann folgt daraus, dass das Betriebssystem das Fenster auffordert, diesen Bereich neu zu malen.

Wenn man nun ein Programm schreibt, dann kann man das nutzen: immer, wenn sich Daten ändern, dann müssen die dargestellt werden. Also macht man z.B. ein repaint Aufruf auf einem Fenster.
Das sorgt dann dafür, dass das ganze Fenster incl. aller Controls und so, neu gemalt wird. Um die Childs wird sich automatisch gekümmert. Wenn das "draw" also sowas wie ein Child Element ist, ist dieses Überschreiben unnötig.
Und wenn eine Applikation mehrere Fenster hat, dann ist auch denkbar: Wenn ich beim Hauptfenster repaint aufrufe, dann soll auch auf einem / allen anderen Fenster repaint aufgerufen werden. Das wäre eine valide Anforderung, die dann z.B. in einer so überschriebenen repaint Methode resultieren könnten.
 
Erst einmal danke für di ganzen Antworten!
Heißt das jetzt, dass repaint() einfach ignoriert wird, da gerade eine andere Aufgabe ausgeführt wird? Und ich mache den Übergang so, dass ich 2 Klassen habe, die beide von JFrame erben. Das eine ist sichtbar, das andere nicht. Wenn ich das Fenster "wechseln" will, zum Beispiel nach einem Mausklick von dem User, rufe ich beim Hauptfenster transformTo(secondWindow) auf. Allerdings wird nur ein kleines rechteck gezeichnet, was der größe von vorher entspricht. Wenn ich jetzt das Fenster wirklich übermalen will, muss ich also was machen? Oder soll ich eine ganz andere Methode für den Zweck schreiben, die irgendwas anders macht?
 
Heißt das jetzt, dass repaint() einfach ignoriert wird, da gerade eine andere Aufgabe ausgeführt wird?
Ja, so lange die Aufgabe läuft. Stell Dir einen Stapel mit Aufgaben vor, der abgearbeitet werden muss. Swing (eigentlich AWT) arbeitet diesen Stapel ab. Eine dieser Aufgaben ist Deine Methode, die irgendwann mal drankommt. In dieser Methode erzeugst Du nun neue Aufgaben (repaint() -> mal mir das Fenster neu), die landen ganz unten im Stapel. Wenn Deine Methode (Aufgabe) erledigt ist, kommt die nächste dran, bis irgendwann dann auch das repaint() an der Reihe ist. Hinzu kommt, dass der Spaß intern bzgl. der Anzeige optimiert wird (das aber nur nebenbei).

Hier mal was zusammengeschustert:
Java:
import java.awt.Dimension;
import java.awt.GraphicsEnvironment;
import java.awt.Point;

import javax.swing.*;

public class Test {
    static class FrameTransform {
        public static final int DEFAULT_DELAY = 20;
        private final Timer timer;
        private final JFrame frame;
        private final Dimension from;
        private final Dimension to;
        private final int duration;

        private long startTime;

        public FrameTransform(JFrame frame, Dimension from, Dimension to, 
                              int duration) {
            this(frame, from, to, duration, DEFAULT_DELAY);
        }

        public FrameTransform(JFrame frame, Dimension from, Dimension to, 
                              int duration, int delay) {
            this.frame = frame;
            this.from = from;
            this.to = to;
            this.duration = duration;

            timer = new Timer(delay, e -> animate());
            timer.setRepeats(true);
        }

        public void start() {
            this.startTime = System.currentTimeMillis();
            frame.setVisible(true);
            frame.toFront();
            timer.start();
        }

        private void animate() {
            long elapsed = System.currentTimeMillis() - startTime; 
            if (elapsed >= duration) {
                resize(to);
                timer.stop();
            }

            int widthDiff = to.width - from.width;
            int heightDiff = to.height - from.height;
            int relevantDiff = Math.abs(widthDiff) > Math.abs(heightDiff) ?
                   widthDiff : heightDiff;

            double fraction = ((double) relevantDiff) / duration;

            int width = from.width + bound(elapsed * fraction, widthDiff);
            int height = from.height + bound(elapsed * fraction, heightDiff);

            resize(new Dimension(width, height));
        }

        private int bound(double value, int boundary) {
            return (int) (boundary < 0 
                    ? Math.max(boundary, value)
                    : Math.min(boundary, value));
        }

        private void resize(Dimension size) {
            Point center = GraphicsEnvironment
                     .getLocalGraphicsEnvironment()
                     .getCenterPoint();
                  
            int x = center.x - size.width / 2;
            int y = center.y - size.height / 2;
            frame.setBounds(x, y, size.width, size.height);
        }
    }

    private final Dimension defaultSize = new Dimension(800, 600);
    private final Dimension resizedSize = new Dimension(200, 200);


    public void run() {
        final JFrame frame = createFrame("The Frame");

        JButton button = new JButton("Animate");
        button.addActionListener(e -> {
            Dimension size = frame.getSize();
            Dimension from = size.equals(defaultSize) ? defaultSize : resizedSize;
            Dimension to = size.equals(defaultSize) ? resizedSize : defaultSize;

            new FrameTransform(frame, from, to, 5000).start();
        });

        frame.add(button);
        frame.setVisible(true);
    }

    private JFrame createFrame(String title) {
        JFrame frame = new JFrame(title);
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.setSize(defaultSize);
        frame.setLocationRelativeTo(null);
        return frame;
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> new Test().run());
    }
}
 
Was ist das denn für eine dumme Antwort? Da hat jemand das Prinzip von AWT noch nicht verstanden.... traurig.

Wenn Du Deine Aussage etwas ausführen könntest anstatt nur so rum zu poltern.

Wenn die Ersteller der Meinung gewesen wären, dass repaint generell nicht überschrieben werden sollte, dann wäre die Methode wohl final.

Wenn an der Funktionalität von Repaint eine Änderung notwendig wäre: Warum ist es aus Deiner Sicht nicht legitim, dies zu überschreiben?
(Außer natürlich, dass Du dieser Meinung bist und Du natürlich unfehlbar bist .... Aber ich denke mal eher, dass Du irgendwelche Texte einfach nicht richtig verstanden hast wie so oft... repaint wird nicht überschrieben, um irgendwelche Controls zu malen. Evtl. willst Du schlicht auf sowas hinaus.... aber das ist auch nicht gemacht worden, sondern es ging um reine repaint Funktionalitäten ...)
 
Danke an alle, ich merke mir: Timer > Thread.sleep(long), danke
Achtung: es gibt zwei Timer-Klassen (java.util.Timer und javax.swing.Timer). Der hier verwendete Swing-Timer ist halt eine Möglichkeit, um in mehr oder weniger regelmäßigen Abständen Code im EDT auszuführen. Der Code selbst läuft also nicht nebenher, so dass das UI blockiert, so lange animate() läuft. Wenn Du wirklich Aufgaben von langer Dauer ausführen musst, kannst Du z. B. SwingWorker verwenden oder selbst einen Thread erzeugen.
 

Zurück
Oben