Problem mit paintComponent()

Turing0001

Aktives Mitglied
Hallo ihr Java-Enthusiasten,

ich habe gerade ein wahrscheinlich typisches Anfängerproblem. Ich habe ein JFrame mit einem JPanel und einem Menü mit zwei Menüeinträgen. Bei Klick auf den ersten Eintrag sollen im JPanel Zufallslinien gezeichnet werden, beim zweiten Menüeintrag dasselbe für Zufallsrechtecke. Das funktioniert auch bis auf die Tatsache dass die Rechtecke bzw. Linien erst sichtbar werden, wenn der ganze Zeichenvorgang abgeschlossen ist. Ich habe zwischen jedem Zeichnen eine Zeitverzögerung eingebaut, da man den Zeichenvorgang beobachten können soll. Ein analoges AWT-Programm funktioniert, dei Swing Version jedoch nicht. Die Lösung ist wahrscheinlich so simpel dass es mir peinlich sein sollte. Hier mal die beiden Quellcodes. Entscheidend ist die zweite (PaintPanel). Zuerst das Hauptprogramm:

Java:
package paintpackage;

import java.awt.BorderLayout;
import java.awt.EventQueue;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import javax.swing.JMenuBar;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

public class PaintWindow extends JFrame {

    private JPanel contentPane;
    PaintPanel myPanel;

    /**
     * Launch the application.
     */
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    PaintWindow frame = new PaintWindow();
                    frame.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * Create the frame.
     */
    public PaintWindow() {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setBounds(100, 100, 801, 561);
        
        JMenuBar menuBar = new JMenuBar();
        setJMenuBar(menuBar);
        
        JMenu mnNewMenu = new JMenu("Paint");
        menuBar.add(mnNewMenu);
        
        JMenuItem mntmNewMenuItem_1 = new JMenuItem("Rectangle");
        mntmNewMenuItem_1.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                myPanel.setMode(2);
                myPanel.repaint();
            }
        });
        mnNewMenu.add(mntmNewMenuItem_1);
        
        JMenuItem mntmNewMenuItem = new JMenuItem("Line");
        mntmNewMenuItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                myPanel.setMode(1);
                myPanel.repaint();
            }
        });
        mnNewMenu.add(mntmNewMenuItem);
        contentPane = new JPanel();
        contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
        setContentPane(contentPane);
        contentPane.setLayout(null);
        
        myPanel= new PaintPanel();
        myPanel.setBounds(10, 11, 765, 478);
        contentPane.add(myPanel);
        myPanel.setLayout(null);
    }
}

Und nun noch der zweite Teil:

Java:
package paintpackage;

import java.awt.Graphics;

import javax.swing.JPanel;

public class PaintPanel extends JPanel
{
    int paintMode = -1;
    int x1,y1,x2,y2,width,height;
    
    public void setMode(int mode)
    {
        paintMode = mode;
    }
    
    public void paintComponent(Graphics g)
    {
        super.paintComponent(g);
        if(paintMode == 1)
        {
            for(int i =1;i<1000;i++)
            {
                System.out.println(i);
                x1 = (int)(Math.random() * 50);
                y1 = (int)(Math.random() * 50);
                x2 = (int)(Math.random() * 400);
                y2 = (int)(Math.random() * 400);
                g.drawLine(x1,y1,x2,y2);
                
                try
                {
                    Thread.sleep(50);
                }
                catch (Exception ex)
                {
                    
                }
                
                
            }
        }
        if(paintMode == 2)
        {
            for(int i =1;i<1000;i++)
            {
                System.out.println(i);
                x1 = (int)(Math.random() * 50);
                y1 = (int)(Math.random() * 50);
                width = (int)(Math.random() * 400);
                height = (int)(Math.random() * 400);
                g.fillRect(x1,y1,width,height);
                try
                {
                    Thread.sleep(50);
                }
                catch (Exception ex)
                {
                    
                }
            }
        
        
    }

    }

}

Ich sehe vermutlich hier gerade den Wald vor lauter Bäumen nicht.
Vielen Dank schon mal für eure Hilfe!
 

mihe7

Top Contributor
Swing-Komponenten setzen standardmäßig double buffering ein, und auch sonst ist der Ansatz, einfach mal direkt irgendwas zu zeichnen, nicht unbedingt sinnvoll. Du könntest versuchen, setDoubleBuffered(false) im Konstruktor aufzurufen. Normalerweise läuft das aber so, dass man Änderungen vornimmt und anschließend repaint() aufruft.
 

Turing0001

Aktives Mitglied
Swing-Komponenten setzen standardmäßig double buffering ein, und auch sonst ist der Ansatz, einfach mal direkt irgendwas zu zeichnen, nicht unbedingt sinnvoll. Du könntest versuchen, setDoubleBuffered(false) im Konstruktor aufzurufen. Normalerweise läuft das aber so, dass man Änderungen vornimmt und anschließend repaint() aufruft.
Hallo,

vielen Dank, aber das hilft mir leider gerade gar nicht weiter. setDoubleBuffered(false) im Konstruktor bringt nichts. Habe es mit einem Timer und repaint in der actionPerformed()-Funktion probiert, aber dabei verhaut er das Menü seltsamerweise. Trotzdem Vielen Dank für die Mühe.
 

Turing0001

Aktives Mitglied
?!? Funktioniert bei mir astrein.
Hallo miha7,

jetzt aber doch noch eine Frage: Kann ich in Swing verhindern, dass beim Zeichnen der alte Inhalt gelöscht wird, also die Linien alle sichtbar bleiben? Im AWT muss ich dazu die update() Funktion überschreiben, wie ist das in Swing, konnte beim Googlen nichts Brauchbares finden.
 

Turing0001

Aktives Mitglied
Hallo mihe,

bei mir jetzt auch. Timer-Code gelöscht, neu geschrieben-funzt! Hol's der Geier!! 😄. Vielen Dank nochmal.
Und nochmal: Wenn ich super.paintComponent weglasse, geht es so wie ich will, aber die Menüleiste taucht plötzlich zwei Mal auf (???).
Hier nochmal mein verändertes PaintPanel (PaintWindow habe ich so gelassen wie vorher):

Java:
package paintpackage;

import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JPanel;
import javax.swing.Timer;

public class PaintPanel extends JPanel implements ActionListener
{
    int paintMode = -1;
    int x1,y1,x2,y2,width,height;
    int count = 0,i=1;
    Timer timer;
    public PaintPanel()
    {
         //setDoubleBuffered(false);
            
    }
    
    public void setMode(int mode)
    {
        paintMode = mode;
        timer = new Timer(10, this);
        timer.start();
        
    }
    
    
    
    public void paintComponent(Graphics g)
    {
        
        //super.paintComponent(g);
        if(paintMode == 1)
        {
            //for(i =1;i<1000;i++)
            //{
                //System.out.println(i);
                x1 = (int)(Math.random() * 50);
                y1 = (int)(Math.random() * 50);
                x2 = (int)(Math.random() * 400);
                y2 = (int)(Math.random() * 400);
                g.drawLine(x1,y1,x2,y2);
                

        }
        if(paintMode == 2)
        {
            
                //System.out.println(i);
                x1 = (int)(Math.random() * 50);
                y1 = (int)(Math.random() * 50);
                width = (int)(Math.random() * 400);
                height = (int)(Math.random() * 400);
                g.fillRect(x1,y1,width,height);
                try
                {
                    Thread.sleep(50);
                }
                catch (Exception ex)
                {
                    
                }
            
        
        
    }

    }

    @Override
    public void actionPerformed(ActionEvent e) {
        count++;
        if (count == 1000)
            timer.stop();
        else
        {
            repaint();
        }
    }

    
    

}
 

mihe7

Top Contributor
Java:
import java.awt.Graphics;
import java.awt.image.BufferedImage;

import javax.swing.JPanel;
import javax.swing.Timer;

public class PaintPanel extends JPanel
{
    int paintMode = -1;
    int x1,y1,x2,y2,width,height;

    private BufferedImage img;

    public void setMode(int mode)
    {
        paintMode = mode;
    }

    public void paintElements() {
        img = new BufferedImage(400, 400, BufferedImage.TYPE_INT_ARGB);

        long start = System.currentTimeMillis();

        Timer t = new Timer(50, e -> {
            if (System.currentTimeMillis() - start > 10000) {
                ((Timer) e.getSource()).stop();
                return;
            }

            int x1 = (int)(Math.random() * 50);
            int y1 = (int)(Math.random() * 50);
            int x2 = (int)(Math.random() * 400);
            int y2 = (int)(Math.random() * 400);

            Graphics g = img.createGraphics();
            g.setColor(java.awt.Color.BLACK);
            if (paintMode == 1) {
                g.drawLine(x1, y1, x2, y2);
            } else if (paintMode == 2) {
                g.fillRect(x1, y1, x2, y2);
            }
            g.dispose();
            
            paintImmediately(getVisibleRect());
        });
        t.start();
    }

    
    public void paintComponent(Graphics g)
    {
        g.drawImage(img, 0, 0, null);
    }

}
 

Turing0001

Aktives Mitglied
Java:
import java.awt.Graphics;
import java.awt.image.BufferedImage;

import javax.swing.JPanel;
import javax.swing.Timer;

public class PaintPanel extends JPanel
{
    int paintMode = -1;
    int x1,y1,x2,y2,width,height;

    private BufferedImage img;

    public void setMode(int mode)
    {
        paintMode = mode;
    }

    public void paintElements() {
        img = new BufferedImage(400, 400, BufferedImage.TYPE_INT_ARGB);

        long start = System.currentTimeMillis();

        Timer t = new Timer(50, e -> {
            if (System.currentTimeMillis() - start > 10000) {
                ((Timer) e.getSource()).stop();
                return;
            }

            int x1 = (int)(Math.random() * 50);
            int y1 = (int)(Math.random() * 50);
            int x2 = (int)(Math.random() * 400);
            int y2 = (int)(Math.random() * 400);

            Graphics g = img.createGraphics();
            g.setColor(java.awt.Color.BLACK);
            if (paintMode == 1) {
                g.drawLine(x1, y1, x2, y2);
            } else if (paintMode == 2) {
                g.fillRect(x1, y1, x2, y2);
            }
            g.dispose();
           
            paintImmediately(getVisibleRect());
        });
        t.start();
    }

   
    public void paintComponent(Graphics g)
    {
        g.drawImage(img, 0, 0, null);
    }

}
Hallo mihe7,

das geht echt nicht einfacher??? Ist doch ein Witz, oder?
 

mihe7

Top Contributor
Hallo mihe7,

das geht echt nicht einfacher??? Ist doch ein Witz, oder?
Definiere "einfacher". Der Ansatz, der unter AWT "funktioniert", zeichnet einfach aber sobald das Fenster neu gezeichnet werden muss, ist Deine Zeichnung weg.

Nebenbei bemerkt, ist das keine Eigenheit von Swing. Windows schickt WM_PAINT-Ereignisse an ein Fenster und dann hat sich das Fenster neu zu zeichnen. Soll der vorherige Fensterinhalt dabei nicht verlorengehen, muss er irgendwo gespeichert werden. Hier mach ich das - aufgrund der Anzahl der Objekte - halt in einem Bild. Du kannst natürlich auch Linien speichern und diese in paintComponent malen.
 

Turing0001

Aktives Mitglied
Definiere "einfacher". Der Ansatz, der unter AWT "funktioniert", zeichnet einfach aber sobald das Fenster neu gezeichnet werden muss, ist Deine Zeichnung weg.

Nebenbei bemerkt, ist das keine Eigenheit von Swing. Windows schickt WM_PAINT-Ereignisse an ein Fenster und dann hat sich das Fenster neu zu zeichnen. Soll der vorherige Fensterinhalt dabei nicht verlorengehen, muss er irgendwo gespeichert werden. Hier mach ich das - aufgrund der Anzahl der Objekte - halt in einem Bild. Du kannst natürlich auch Linien speichern und diese in paintComponent malen.
Hallo mihe,

vielen Dank für Deine Mühe. Aber sonderlich elegant ist das Ganze dennoch nicht, da hätte ich mir einen einfacheren Mechanismus gewünscht. Wie gesagt, wenn ich super.paintComponent weglasse funktioniert es wie ich will, aber selstsamerweise erscheint dann meine Meüleiste doppelt. A propos doppelt: Ich ahbe festgestellt, dass die paintComponent.Funktion zwei Mal aufgerufen wird beim Aufbau des Fenster, also noch bevor ich per Klick eine Zeichenoperation starte. Warum? Welchen Sinn macht das? Hier nochmal meine Codeversion ohne Deine Verbesserungen:

Java:
package paintpackage;

import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JPanel;
import javax.swing.Timer;

public class PaintPanel extends JPanel implements ActionListener
{
    int paintMode = -1;
    int x1,y1,x2,y2,width,height;
    int count = 0,i=1;
    Timer timer;
    public PaintPanel()
    {
         setDoubleBuffered(false);
            
    }
    
    public void setMode(int mode)
    {
        paintMode = mode;
        timer = new Timer(10, this);
        timer.start();
        
    }
    
    
    
    public void paintComponent(Graphics g)
    {
        System.out.println("In paint");
        
        super.paintComponent(g);
        if(paintMode == 1)
        {
        
                x1 = (int)(Math.random() * 50);
                y1 = (int)(Math.random() * 50);
                x2 = (int)(Math.random() * 400);
                y2 = (int)(Math.random() * 400);
                g.drawLine(x1,y1,x2,y2);
                

        }
        if(paintMode == 2)
        {
            
                //System.out.println(i);
                x1 = (int)(Math.random() * 50);
                y1 = (int)(Math.random() * 50);
                width = (int)(Math.random() * 400);
                height = (int)(Math.random() * 400);
                g.fillRect(x1,y1,width,height);
                
            
        
        
    }

    }

    @Override
    public void actionPerformed(ActionEvent e) {
        count++;
        if (count == 1000)
        {
            count = 0;
            timer.stop();
        }
        else
        {
            repaint();
        }
    }

    
    

}

Ich habe wie Du siehst noch eine Meldung in die paint-Funktion eingebaut. Warum also zwei Aufrufe?
 

Turing0001

Aktives Mitglied
Wo nutzt du die Komponente und was rufst du da genau auf?
Hallo Kneitzel,
schön, wieder von Dir zu hören. Hier das Hauptprogramm nochmal (GUI erstellt mit WindowBuilder):

Java:
package paintpackage;

import java.awt.BorderLayout;
import java.awt.EventQueue;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import javax.swing.JMenuBar;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

public class PaintWindow extends JFrame {

    private JPanel contentPane;
    PaintPanel myPanel;

    /**
     * Launch the application.
     */
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    PaintWindow frame = new PaintWindow();
                    frame.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
        
    }

    /**
     * Create the frame.
     */
    public PaintWindow() {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setBounds(100, 100, 801, 561);
        
        JMenuBar menuBar = new JMenuBar();
        setJMenuBar(menuBar);
        
        JMenu mnNewMenu = new JMenu("Paint");
        menuBar.add(mnNewMenu);
        
        JMenuItem mntmNewMenuItem_1 = new JMenuItem("Rectangle");
        mntmNewMenuItem_1.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                myPanel.setMode(2);
                myPanel.repaint();
            }
        });
        mnNewMenu.add(mntmNewMenuItem_1);
        
        JMenuItem mntmNewMenuItem = new JMenuItem("Line");
        mntmNewMenuItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                myPanel.setMode(1);
                myPanel.repaint();
            }
        });
        mnNewMenu.add(mntmNewMenuItem);
        contentPane = new JPanel();
        contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
        setContentPane(contentPane);
        contentPane.setLayout(null);
        
        myPanel= new PaintPanel();
        myPanel.setBounds(10, 11, 765, 478);
        contentPane.add(myPanel);
        myPanel.setLayout(null);
    }
}


Wenn wir gerade dabei sind: Nach dem Eclipse Update von vor einigen Tagen kann ich meine alten Projekte nicht mehr als Jar exportieren, die Klassen mit der jeweiligen main-Funktion werden nicht mehr angezeigt, nur die neu erstellten Projekte. Ich weiß, Du bist kein Fan von Eclipse, aber ist Dir trotzdem eine Möglichkeit bekannt, das zu ändern ohne alle Projekte neue importieren zu müssen?
 
K

kneitzel

Gast
Also der Reihe nach:
a) Wieso wird paintComponent mehrfach aufgerufen: Hier habe ich erst einmal keine Erklärung. Generell ist dieser Aufruf aber nicht limitiert und kann durch sehr viele Dinge ausgelöst werden. Daher sollte es keine Rolle spielen.

b) Du hast die Variablen schon als Instanzvariablen und nicht als lokale Variablen. Zieh doch die Ermittlung neuer Werte raus aus paintComponent. Eine Methode sollte nur machen, was ihr Name aussagt: paint component. Also malen der Komponente. Alles Andere gehört da schlicht nicht rein!
Sobald Du das sauber getrennt hast, ist a) auch kein Thema mehr. paintComponent kann so oft aufgerufen werden, wie es will. Es malt dann ja nur die Komponente. Bei Erzeugung der Komponente und beim Timer werden dann ggf. neue Werte erfasst und ein neue Malen erzwungen.
Damit kommst Du übrigens auch zu dem, zu dem Dir etwas geraten wurde von @mihe7. Er hat das dann nur noch etwas optimiert mit der Anzeige - wieso solltest Du ggf. feststellen können, wenn Du das Fenster verschiebst oder in der Größe änderst. Aber er meinte ja auch:
Du kannst natürlich auch Linien speichern und diese in paintComponent malen.
und das ist, was die Änderung, die ich hier vorschlage, macht. Es macht es nicht komplizierter. Es macht es sogar einfacher lesbar und Dinge kommen da hin, wo sie hin gehören. Das, was bei @mihe7 komplizierter aussieht wird übrigens, sobald man es weiter ausbaut, deutlich einfacher weil man Dinge besser untergliedern kann (z.b. incl. besserer Untergliederung in Model und einer View) - auch wenn da noch alles zusammen gehalten wurde ...

c) Bezüglich Eclipse: Hier möchte ich einfach den Rat geben, den ich immer gebe: Schau Dir Maven oder Gradle an. Dann hast Du die projekt-Konfiguration in einer (i.d.R. kleinen) Datei, die Du auch einfach posten kannst. Egal bei welcher IDE: Da jetzt Einstellungen prüfen ist extrem blöd. Da tun sich die IDEs auch nicht. Auch bei IntelliJ möchte ich keine Artifakt-Konfiguraionen durchgehen müssen. (Und das sind auch Dinge, die die Software Entwickler nicht wicklich nutzen. Das grenzt also auch die Unterstützung, die man bekommen kann, etwas ein.)

Clean Code Hinweise:
Den Timer sollte man meiner Meinung nach NIE mit timer = new Timer(10, this); und dann der Klasse mit implements ActionListener implementieren.
Begründung ist einfach:
- Der Name actionperformed sagt nicht aus, WAS es macht sondern gibt nur eine kleine Info, wie es getriggert wird.
- Bei mehreren Actions kommt es dann zu unschönen Code-Aufbauten von wegen: Wer ist denn der Sender des Events... Das führt dann in der Regel entweder zu großen schwer lesbaren Methoden oder man ein Code-Konstrukt, das andere Methoden aufruft und die niemand will ...

Refactoring:
1. implements ActionListener löschen
2. actionPerformed verliert das @Override und wird umbenannt. Benenne es so, dass klar ist, was da passiert.
3. Trage eine Methodenreferenz statt dem this ein: timer = new Timer(10, this::deinTollerNeuerName);
 

Turing0001

Aktives Mitglied
Also der Reihe nach:
a) Wieso wird paintComponent mehrfach aufgerufen: Hier habe ich erst einmal keine Erklärung. Generell ist dieser Aufruf aber nicht limitiert und kann durch sehr viele Dinge ausgelöst werden. Daher sollte es keine Rolle spielen.

b) Du hast die Variablen schon als Instanzvariablen und nicht als lokale Variablen. Zieh doch die Ermittlung neuer Werte raus aus paintComponent. Eine Methode sollte nur machen, was ihr Name aussagt: paint component. Also malen der Komponente. Alles Andere gehört da schlicht nicht rein!
Sobald Du das sauber getrennt hast, ist a) auch kein Thema mehr. paintComponent kann so oft aufgerufen werden, wie es will. Es malt dann ja nur die Komponente. Bei Erzeugung der Komponente und beim Timer werden dann ggf. neue Werte erfasst und ein neue Malen erzwungen.
Damit kommst Du übrigens auch zu dem, zu dem Dir etwas geraten wurde von @mihe7. Er hat das dann nur noch etwas optimiert mit der Anzeige - wieso solltest Du ggf. feststellen können, wenn Du das Fenster verschiebst oder in der Größe änderst. Aber er meinte ja auch:

und das ist, was die Änderung, die ich hier vorschlage, macht. Es macht es nicht komplizierter. Es macht es sogar einfacher lesbar und Dinge kommen da hin, wo sie hin gehören. Das, was bei @mihe7 komplizierter aussieht wird übrigens, sobald man es weiter ausbaut, deutlich einfacher weil man Dinge besser untergliedern kann (z.b. incl. besserer Untergliederung in Model und einer View) - auch wenn da noch alles zusammen gehalten wurde ...

c) Bezüglich Eclipse: Hier möchte ich einfach den Rat geben, den ich immer gebe: Schau Dir Maven oder Gradle an. Dann hast Du die projekt-Konfiguration in einer (i.d.R. kleinen) Datei, die Du auch einfach posten kannst. Egal bei welcher IDE: Da jetzt Einstellungen prüfen ist extrem blöd. Da tun sich die IDEs auch nicht. Auch bei IntelliJ möchte ich keine Artifakt-Konfiguraionen durchgehen müssen. (Und das sind auch Dinge, die die Software Entwickler nicht wicklich nutzen. Das grenzt also auch die Unterstützung, die man bekommen kann, etwas ein.)

Clean Code Hinweise:
Den Timer sollte man meiner Meinung nach NIE mit timer = new Timer(10, this); und dann der Klasse mit implements ActionListener implementieren.
Begründung ist einfach:
- Der Name actionperformed sagt nicht aus, WAS es macht sondern gibt nur eine kleine Info, wie es getriggert wird.
- Bei mehreren Actions kommt es dann zu unschönen Code-Aufbauten von wegen: Wer ist denn der Sender des Events... Das führt dann in der Regel entweder zu großen schwer lesbaren Methoden oder man ein Code-Konstrukt, das andere Methoden aufruft und die niemand will ...

Refactoring:
1. implements ActionListener löschen
2. actionPerformed verliert das @Override und wird umbenannt. Benenne es so, dass klar ist, was da passiert.
3. Trage eine Methodenreferenz statt dem this ein: timer = new Timer(10, this::deinTollerNeuerName);
Hallo Kneitzel,
vielen Dank für die ausführliche Antwort. Werde mich gleich mal dran machen.
 

Turing0001

Aktives Mitglied
Also der Reihe nach:
a) Wieso wird paintComponent mehrfach aufgerufen: Hier habe ich erst einmal keine Erklärung. Generell ist dieser Aufruf aber nicht limitiert und kann durch sehr viele Dinge ausgelöst werden. Daher sollte es keine Rolle spielen.

b) Du hast die Variablen schon als Instanzvariablen und nicht als lokale Variablen. Zieh doch die Ermittlung neuer Werte raus aus paintComponent. Eine Methode sollte nur machen, was ihr Name aussagt: paint component. Also malen der Komponente. Alles Andere gehört da schlicht nicht rein!
Sobald Du das sauber getrennt hast, ist a) auch kein Thema mehr. paintComponent kann so oft aufgerufen werden, wie es will. Es malt dann ja nur die Komponente. Bei Erzeugung der Komponente und beim Timer werden dann ggf. neue Werte erfasst und ein neue Malen erzwungen.
Damit kommst Du übrigens auch zu dem, zu dem Dir etwas geraten wurde von @mihe7. Er hat das dann nur noch etwas optimiert mit der Anzeige - wieso solltest Du ggf. feststellen können, wenn Du das Fenster verschiebst oder in der Größe änderst. Aber er meinte ja auch:

und das ist, was die Änderung, die ich hier vorschlage, macht. Es macht es nicht komplizierter. Es macht es sogar einfacher lesbar und Dinge kommen da hin, wo sie hin gehören. Das, was bei @mihe7 komplizierter aussieht wird übrigens, sobald man es weiter ausbaut, deutlich einfacher weil man Dinge besser untergliedern kann (z.b. incl. besserer Untergliederung in Model und einer View) - auch wenn da noch alles zusammen gehalten wurde ...

c) Bezüglich Eclipse: Hier möchte ich einfach den Rat geben, den ich immer gebe: Schau Dir Maven oder Gradle an. Dann hast Du die projekt-Konfiguration in einer (i.d.R. kleinen) Datei, die Du auch einfach posten kannst. Egal bei welcher IDE: Da jetzt Einstellungen prüfen ist extrem blöd. Da tun sich die IDEs auch nicht. Auch bei IntelliJ möchte ich keine Artifakt-Konfiguraionen durchgehen müssen. (Und das sind auch Dinge, die die Software Entwickler nicht wicklich nutzen. Das grenzt also auch die Unterstützung, die man bekommen kann, etwas ein.)

Clean Code Hinweise:
Den Timer sollte man meiner Meinung nach NIE mit timer = new Timer(10, this); und dann der Klasse mit implements ActionListener implementieren.
Begründung ist einfach:
- Der Name actionperformed sagt nicht aus, WAS es macht sondern gibt nur eine kleine Info, wie es getriggert wird.
- Bei mehreren Actions kommt es dann zu unschönen Code-Aufbauten von wegen: Wer ist denn der Sender des Events... Das führt dann in der Regel entweder zu großen schwer lesbaren Methoden oder man ein Code-Konstrukt, das andere Methoden aufruft und die niemand will ...

Refactoring:
1. implements ActionListener löschen
2. actionPerformed verliert das @Override und wird umbenannt. Benenne es so, dass klar ist, was da passiert.
3. Trage eine Methodenreferenz statt dem this ein: timer = new Timer(10, this::deinTollerNeuerName);
Hallo Kneitzel,

ich habe den Code jetzt soweit umgebaut, funktioniert. Aber ein Mysterium hätte ich noch gerne geklärt. In meiner ursprünglichen Version (s.o.) erscheint die Menüleiste plötzlich doppelt, wenn ich super.paintComponent() weglasse. Wie kommt dieser merkwürdige Effekt zustande?
 

Blender3D

Top Contributor
die Menüleiste plötzlich doppelt, wenn ich super.paintComponent() weglasse. Wie kommt dieser merkwürdige Effekt zustande?
paintComponent() ruft die Paint-Methode des UI-Delegaten auf, wenn der UI-Delegat nicht- ist.null
Es werden alle notwendigen enthalten Elemente neu gezeichnet. Du überlädst die Methode und musst zwangläufig auch die Methode der Elternklasse aufrufen. Andernfalls kommt es zu solchen unerwünschten Effekten wie von dir oben erwähnt.
 

Blender3D

Top Contributor
ich habe den Code jetzt soweit umgebaut, funktioniert.
Hier ein Beispiel das denCode über die zu zeichnenden Objekte aufbaut.

Es ermöglicht das Zeichen von Rechtecken, Linien, Kreisen oder gemischt. Durch den Aufbau über die Objekte kann man sehr einfach weitere Formen hinzufügen.
1624450090351.png




[CODE lang="java" title="PaintWindowStart"]import javax.swing.JFrame;

public class PaintWindowStart {
public static void main(String[] args) {
JFrame frame = new JFrame("Paint Window");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
PaintPanel panel = new PaintPanel(600, 600);
frame.setJMenuBar(new PaintWindowMenu(panel));
frame.add(panel);
frame.pack();
frame.setVisible(true);
}
}[/CODE]
[CODE lang="java" title="PaintPanel Hauptprogramm"]import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JPanel;
import paintpackage.ToolObject2D.SHAPE;

@SuppressWarnings("serial")
public class PaintPanel extends JPanel implements ActionListener, Runnable {
private SHAPE mode = SHAPE.RECTANGLE;
private Object2D[] shapes = new Object2D[100];
private int id = 0;
private boolean running = false;
private Thread animator;

public PaintPanel(int width, int height) {
setPreferredSize(new Dimension(width, height));
start();
}

/**
* Listens to menueItems clicks.
*
* @see PaintWindowMenu#PaintWindowMenu(ActionListener)
*/
@Override
public void actionPerformed(ActionEvent e) {
mode = getMode(e.getActionCommand());
}

private SHAPE getMode(String cmd) {
SHAPE[] shapes = SHAPE.values();
for (int i = 0; i < shapes.length; i++) {
if (cmd.equals(shapes.toString()))
return shapes;
}
return null;
}

private synchronized void newShape() {
int maxSize = getWidth() / 5;
shapes[id] = ToolObject2D.getRndObject2D(mode, new Limit(getWidth()), new Limit(getHeight()),
new Limit(maxSize / 5, maxSize), new Limit(maxSize / 5, maxSize));
id++;
if (id >= shapes.length)
id = 0;
}

@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (int i = 0; i < shapes.length; i++) {
if (shapes != null)
shapes.draw(g);
}
}

@Override
public void run() {
while (running) {
newShape();
repaint();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

private void start() {
stop();
running = true;
animator = new Thread(this);
animator.start();
}

private void stop() {
running = false;
while (animator != null && animator.isAlive())
;
animator = null;
}
}[/CODE]
[CODE lang="java" title="PaintWindowMenu"]import java.awt.event.ActionListener;

import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;

import paintpackage.ToolObject2D.SHAPE;

@SuppressWarnings("serial")
public class PaintWindowMenu extends JMenuBar {
private JMenu menu = new JMenu("shapes");

/**
* @param listener
* ActionListener for menu entries
*/
public PaintWindowMenu(ActionListener listener) {
SHAPE[] shapes = SHAPE.values();
for (int i = 0; i < shapes.length; i++) {
JMenuItem item = new JMenuItem(shapes.toString());
menu.add(item);
item.addActionListener(listener);
}
add(menu);
}
}[/CODE]

[CODE lang="java" title="Limit erzeugt Zufallszahlen in einer Range"]public class Limit {
private final static Random rnd = new Random(System.currentTimeMillis());
public final int min;
public final int max;

public Limit(int min, int max) {
if (min > max) {
int tmp = min;
min = max;
max = tmp;
}
this.min = min;
this.max = max;
}

public Limit(int max) {
min = 0;
this.max = max;
}

public int getRndValue() {
return rnd.nextInt(max - min + 1) + min;
}

public int range() {
return max - min;
}

@Override
public String toString() {
return "[" + min + "-" + max + "]";
}

}[/CODE]
[CODE lang="java" title="ToolObject2D Klasse zum generieren der Zeichenobjekten" highlight="60-77"]import java.awt.Color;
import java.awt.Point;

public class ToolObject2D {
public enum SHAPE {
RECTANGLE, CIRCLE, LINESEGMENT, RND_SHAPE
}

public static Object2D createRectangle(Point p, int width, int height) {
return new Rectangle(p.x, p.y, width, height);
}

public static Object2D createLineSegment(Point p, Point p1) {
return new LineSegment(p.x, p.y, p1.x, p1.y);
}

public static Object2D createCircle(Point p, int radius) {
return new Circle(p.x, p.y, radius);
}

public static int getNumObjKind() {
return SHAPE.values().length;
}

public static Color getRndColor() {
Limit limit = new Limit(255);
return new Color(limit.getRndValue(), limit.getRndValue(), limit.getRndValue());
}

public static Point getRndPos(Limit x, Limit y) {
return new Point(x.getRndValue(), y.getRndValue());
}

public static SHAPE getRndShape() {
int id = 0;
SHAPE[] values = SHAPE.values();
Limit limit = new Limit(getNumObjKind() - 1);
do {
id = limit.getRndValue();
} while (values[id].equals(SHAPE.RND_SHAPE));
return values[id];
}

/**
* Creates a random Object2D.
*
* @param shape
* @param x
* Limit of x-position.
* @param y
* Limit of y-position.
* @param width
* Limit of object's width.
* @param height
* Limit of object's height
* @return Object2D
*/

public static Object2D getRndObject2D(SHAPE shape, Limit x, Limit y, Limit width, Limit height) {
Object2D o = null;
Point pos = getRndPos(x, y);
switch (shape) {
case CIRCLE:
o = createCircle(pos, width.getRndValue() / 2);
break;
case LINESEGMENT:
o = createLineSegment(pos, getRndPos(x, y));
break;
case RECTANGLE:
o = createRectangle(pos, width.getRndValue(), height.getRndValue());
break;
case RND_SHAPE:
o = getRndObject2D(getRndShape(), x, y, width, height);
break;
}
o.setColor(getRndColor());
return o;
}
}[/CODE]
[CODE lang="java" title="Object2D ist die Basisklasse für die gezeichneten Objekte"]import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;

public abstract class Object2D {
protected int x;
protected int y;
protected Color color = Color.BLACK;

public Object2D(int x, int y) {
this.x = x;
this.y = y;
}

public final void draw(Graphics g) {
Color old = g.getColor();
if (color != null)
g.setColor(color);
drawObject(g);
g.setColor(old);
}

abstract protected void drawObject(Graphics g);

public Rectangle getBoundingBox() {
return new Rectangle(x, y, getWidth(), getHeight());
}

abstract int getHeight();

abstract int getWidth();

public void setColor(Color color) {
this.color = color;
}
}[/CODE]
[CODE lang="java" title="LineSegment"]import java.awt.Graphics;

public class LineSegment extends Object2D {
private int x1;
private int y1;

public LineSegment(int x, int y, int x1, int y1) {
super(x, y);
this.x1 = x1;
this.y1 = y1;
}

@Override
protected void drawObject(Graphics g) {
g.drawLine(x, y, x1, y1);
}

@Override
int getHeight() {
return Math.abs(y - y1);
}

@Override
int getWidth() {
return Math.abs(x - x1);
}

}[/CODE]
[CODE lang="java" title="Rectangle"]import java.awt.Graphics;

public class Rectangle extends Object2D {
private int width;
private int height;

public Rectangle(int x, int y, int width, int height) {
super(x, y);
this.width = width;
this.height = height;
}

@Override
protected void drawObject(Graphics g) {
g.fillRect(x, y, width, height);
}

@Override
int getHeight() {
return height;
}

@Override
int getWidth() {
return width;
}

}[/CODE]
[CODE lang="java" title="Circle"]import java.awt.Graphics;

public class Circle extends Object2D {
private int radius;

public Circle(int x, int y, int radius) {
super(x, y);
this.radius = radius;
}

@Override
protected void drawObject(Graphics g) {
int size = radius * 2;
g.fillOval(x, y, size, size);
}

@Override
int getHeight() {
return 2 * radius;
}

@Override
int getWidth() {
return 2 * radius;
}

}
[/CODE]
 
Zuletzt bearbeitet:
Ähnliche Java Themen
  Titel Forum Antworten Datum
P Swing problem mit paintComponent AWT, Swing, JavaFX & SWT 2
L Swing 64bit Problem mit paint/paintComponent AWT, Swing, JavaFX & SWT 7
Z Swing paintcomponent und menbar Problem AWT, Swing, JavaFX & SWT 3
C Performance-Problem beim Überschreiben von paintComponent() AWT, Swing, JavaFX & SWT 2
F Swing Problem mit KeyListener||paintComponent() in JFrame AWT, Swing, JavaFX & SWT 12
P Game of Life - PaintComponent-Problem / Timer? AWT, Swing, JavaFX & SWT 8
T Swing paintComponent problem AWT, Swing, JavaFX & SWT 3
G paintComponent Problem AWT, Swing, JavaFX & SWT 6
L Problem mit paintComponent() under java < 1.6 under Windo AWT, Swing, JavaFX & SWT 3
D performance problem: paintcomponent, alphacomp, bufferedImag AWT, Swing, JavaFX & SWT 10
K Problem repaint()->paintComponent AWT, Swing, JavaFX & SWT 9
B Schon wieder ein Problem mit paintComponent(). AWT, Swing, JavaFX & SWT 2
G Problem mit der Anzeige von jLabel. Unlesbar wenn der Text geändert wird. AWT, Swing, JavaFX & SWT 28
H 2D-Grafik Problem mit Paint AWT, Swing, JavaFX & SWT 1
S Layout - Problem AWT, Swing, JavaFX & SWT 1
Tassos JavaFX/Problem mit der Maussteuerung in Stackpane AWT, Swing, JavaFX & SWT 7
sserio Java Fx - Problem AWT, Swing, JavaFX & SWT 3
A Problem Spiel auf Panel der GUI zu bringen AWT, Swing, JavaFX & SWT 1
A JavaFX Controller Problem AWT, Swing, JavaFX & SWT 1
TheWhiteShadow JavaFX ListView Problem beim Entfernen von Elementen AWT, Swing, JavaFX & SWT 1
E LayoutManager Welcher Layout-Mix löst mein Problem? AWT, Swing, JavaFX & SWT 3
Umb3rus JavaFX Problem mit PropertyValueFactory: can not read from unreadable property AWT, Swing, JavaFX & SWT 1
AmsananKING Java Menü-Problem AWT, Swing, JavaFX & SWT 1
K JavaFX Resizing-Problem beim BorderLayout (Center Component) beim Arbeiten mit mehreren FXMLs AWT, Swing, JavaFX & SWT 2
G Instance OF Problem AWT, Swing, JavaFX & SWT 9
FrittenFritze Ein Problem mit der CSSBox, die Größe wird nicht angepasst AWT, Swing, JavaFX & SWT 5
M Problem mit dem Anzeigen von Frames im Vordergrund AWT, Swing, JavaFX & SWT 5
Badebay Problem mit JButton AWT, Swing, JavaFX & SWT 2
newJavaGeek Grid-Layout problem AWT, Swing, JavaFX & SWT 7
J JavaFX Löschen im Tabelview macht Problem AWT, Swing, JavaFX & SWT 15
JavaTalksToMe JavaFx ExekutorService Problem AWT, Swing, JavaFX & SWT 2
Zrebna Problem bei Eventhandling (Value soll nach jedem erneutem Klick gelöscht werden) AWT, Swing, JavaFX & SWT 4
B Problem mit JavaFX AWT, Swing, JavaFX & SWT 5
J css Problem AWT, Swing, JavaFX & SWT 5
B JavaFX habe mein Problem fett markiert AWT, Swing, JavaFX & SWT 2
A Swing Filter-Problem AWT, Swing, JavaFX & SWT 1
temi JavaFX Problem mit IntelliJ und JavaFx 11 unter XUbuntu AWT, Swing, JavaFX & SWT 3
L Java FX Problem mit Ubuntu 18 und JavaFx AWT, Swing, JavaFX & SWT 27
H JTable TableCellEditor-Problem AWT, Swing, JavaFX & SWT 0
kodela Swing Problem mit Warten-Dialog AWT, Swing, JavaFX & SWT 16
B JavaFx Scene Builder Problem AWT, Swing, JavaFX & SWT 2
B [Problem] Java öffnet Word-Datein nicht AWT, Swing, JavaFX & SWT 14
T DataBinding Problem AWT, Swing, JavaFX & SWT 5
Blender3D Problem mit € Symbol Font Gotham Windows 10 Swing AWT, Swing, JavaFX & SWT 11
T Problem mit JTable Sortierung AWT, Swing, JavaFX & SWT 2
J Problem mit Platfrom run later AWT, Swing, JavaFX & SWT 15
J Problem mit Platfrom run later AWT, Swing, JavaFX & SWT 0
D Swing SwingUtils / Thread Problem AWT, Swing, JavaFX & SWT 3
L JavaFX Problem beim Aufrufen einer Methode AWT, Swing, JavaFX & SWT 5
T Swing Problem mit Datum und FormattedTextField AWT, Swing, JavaFX & SWT 2
S AWT Java print dialog Problem AWT, Swing, JavaFX & SWT 0
olfibits JavaFX Problem mit HTMLEditor AWT, Swing, JavaFX & SWT 0
W SWT hover-background-problem with first column in TreeViewer AWT, Swing, JavaFX & SWT 0
M Problem mit Add JScrollPane AWT, Swing, JavaFX & SWT 25
Mario1409 Swing JTextArea scroll Problem AWT, Swing, JavaFX & SWT 0
N Swing Problem mit loop AWT, Swing, JavaFX & SWT 2
S Swing Problem mit Button und ActionListener AWT, Swing, JavaFX & SWT 5
S Swing & Clean und build Problem AWT, Swing, JavaFX & SWT 12
S JLabel setText() Problem AWT, Swing, JavaFX & SWT 6
I 2D-Grafik Problem beim Ändern der Farbe eine 2d Objekts AWT, Swing, JavaFX & SWT 3
G Swing Splitpane Problem AWT, Swing, JavaFX & SWT 1
F Problem mit der FXML Rectangle Shape AWT, Swing, JavaFX & SWT 2
N JavaFX Stranges Problem mit der Autoscroll-Eigenschaft von Textareas AWT, Swing, JavaFX & SWT 0
E Java FX FXML Problem mit html Scriptausführung AWT, Swing, JavaFX & SWT 2
J JavaFX Intersect Problem mit Shapes AWT, Swing, JavaFX & SWT 10
R JavaFX MediaPlayer AVI-Problem AWT, Swing, JavaFX & SWT 1
M Swing Problem mit ListCellRenderer AWT, Swing, JavaFX & SWT 7
D Problem mit JTable AWT, Swing, JavaFX & SWT 1
F GUI Auflösung ändern - Koordianten und Proportions Problem AWT, Swing, JavaFX & SWT 21
J Problem mit Button darstellung AWT, Swing, JavaFX & SWT 23
M Problem mit Layoutmanagern... Hilfe wäre sehr nett. AWT, Swing, JavaFX & SWT 2
S 2D-Grafik Problem mit Variablen AWT, Swing, JavaFX & SWT 4
7 JavaFX Problem beim Zeichnen eines Dreiecks in einem GUI AWT, Swing, JavaFX & SWT 6
M Swing AttributiveCellTableModel addRow() Problem AWT, Swing, JavaFX & SWT 1
J Swing Problem mit Graphics Methode AWT, Swing, JavaFX & SWT 4
N JavaFX Problem mit table multiple selection AWT, Swing, JavaFX & SWT 5
K CheckBox Problem AWT, Swing, JavaFX & SWT 5
Grevak DisplayMode Problem seit Windows 10 AWT, Swing, JavaFX & SWT 2
S Swing Eigene JComboBox Problem! AWT, Swing, JavaFX & SWT 1
B Swing Problem mit Bildpfad AWT, Swing, JavaFX & SWT 4
N Swing Problem beim Scrollen mit JScrollPane AWT, Swing, JavaFX & SWT 6
V Graphics g - drawOval problem mit background AWT, Swing, JavaFX & SWT 1
C AWT Problem mit Protokol Fenster AWT, Swing, JavaFX & SWT 0
M Swing pack() Problem mit Taskleiste AWT, Swing, JavaFX & SWT 4
N Swing Choice- Problem! AWT, Swing, JavaFX & SWT 8
Q "AWT-EventQueue-0" Exception Problem AWT, Swing, JavaFX & SWT 4
D jButton Problem, ein Rieser Button bedeckt das ganze frame AWT, Swing, JavaFX & SWT 1
A Problem: repaint() - Schleife AWT, Swing, JavaFX & SWT 3
J Anfänger GUI Problem bei der Ausführung eines sehr einfachen Programms AWT, Swing, JavaFX & SWT 2
P AWT Problem mit Platzierung (GridBagLayout) AWT, Swing, JavaFX & SWT 2
N Swing JTree Problem beim erstellen der Knoten AWT, Swing, JavaFX & SWT 0
N Swing CardLayout: Problem beim Wechsel zwischen den JPanels AWT, Swing, JavaFX & SWT 3
A Mini-Menu-Schriften. Ein Problem bei hohen DPI Zahlen AWT, Swing, JavaFX & SWT 2
Z Canvas in Frame einfügen. Problem mit 4-Gewinnt AWT, Swing, JavaFX & SWT 1
C Thread-/ Simulations- Problem AWT, Swing, JavaFX & SWT 18
G Swing Setvisible problem AWT, Swing, JavaFX & SWT 1
J JTabbedPane: close Button Problem AWT, Swing, JavaFX & SWT 2
Tom299 JavaFX -> fxmlLoader -> getResourceAsStream Problem AWT, Swing, JavaFX & SWT 1
T Problem: ComboBox und addItem AWT, Swing, JavaFX & SWT 5
M JTextArea wird nicht aktualisiert (ActionListener-Problem) AWT, Swing, JavaFX & SWT 1

Ähnliche Java Themen

Neue Themen


Oben