... zeichnet erst im zweiten Anlauf

papabaer1707

Aktives Mitglied
Hi,

ich habe in Eclipse ein JFrame gebastelt. Dort sitzt ein JPanel drauf, auf dem ich zeichnen möchte. Gezeichnet wird jeweils mit einem Buttonklick. Nun stört mich, dass beim ersten Klick auf dne Button zwar gezeichnet wird, direkt das Bild gleich wieder gelöscht wird - danach funktioniert es wie gewünscht. Wie kann ich das Abstellen?

Java:
public class DrawFrame extends JFrame implements MouseMotionListener {
	
	static final long serialVersionUID = 1;

	private JPanel contentPane;
	private JTextField tfNumber;
	private JLabel lStatus;
	private JPanel zeichenblatt;
	private JComboBox cbSelect;

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

	public DrawFrame() {
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setBounds(100, 100, 757, 560);
		contentPane = new JPanel();
		contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
		setContentPane(contentPane);
		contentPane.setLayout(null);

		...
		
		zeichenblatt = new JPanel();
		zeichenblatt.setBackground(Color.WHITE);
		zeichenblatt.setBounds(10, 11, 600, 500);
		contentPane.add(zeichenblatt);
		
		zeichenblatt.addMouseMotionListener(this);
	}

	public void mouseDragged(MouseEvent e) {
		// TODO Auto-generated method stub
		
	}

	public void mouseMoved(MouseEvent e) {
		lStatus.setText("Maus: ("+e.getX()+"|"+e.getY()+")");
	}
	
	public void actionPerformed(ActionEvent e) {
		// TODO Auto-generated method stub
		
	}
	
	// Methoden zum Zeichnen
	public void drawOrder() {
		int count = -1;
		
		try
		{
			count = new Integer(tfNumber.getText()).intValue();
		} catch(Exception ex) {}
		
			if( count > 0 )
			{
				for( int j = 0; j < count; j++ )
				{
					switch (cbSelect.getSelectedIndex())
					{
						case 0:
							drawLineParam(zeichenblatt.getGraphics(),
									(int) (Math.random()*zeichenblatt.getWidth()), 
									(int) (Math.random()*zeichenblatt.getHeight()), 
									(int) (Math.random()*zeichenblatt.getWidth()), 
									(int) (Math.random()*zeichenblatt.getHeight()));
							break;
					}
				}
					
			}
			else
				JOptionPane.showMessageDialog(null, "Für die Anzahl der Operationen eine positive Zahl angeben, danke");
	}
}

... und hier wird gezeichnet ...

Java:
public class DrawLib{

	// Setzen eines Pixel simulieren
	public static void setPixel(Graphics g, int x, int y)
	{
		g.setColor(new Color(0,0,0));				// Zeichenfarbe schwarz
		g.drawRect(x, y, 0, 0);						// Pixelsetzen
	}
	
	// Zeichnen einer Linie mit Parametern
	protected static void drawLineParam(Graphics g, int x1, int y1, int x2, int y2)
	{
		int x, y, dx, dy;
		double r, step;
		
		dy = y2 - y1;   							// Hoehenzuwachs
		dx = x2 - x1;   							// Schrittweite
		step = 1.0 / Math.sqrt(dx*dx + dy*dy);   	// Parameterschritt berechnen

		for(r=0.0; r < 1; r+=step)   				// fuer jeden Parameterwert
		{
			x = (int)(x1 + r*dx +0.5);   			// berechne neue x-Koordinate
			y = (int)(y1 + r*dy +0.5);   			// berechne neue y-Koordinate
			setPixel(g,x,y);   						// setze Pixel
		}
		setPixel(g,x2, y2);   						// letztes Pixel am Endpunkt
	}
	
}

Das Zeichnen erscheint im Moment etwas komplitiert, ich möchte aber später erweitern, z. B. mit einer Möglichkeit zum Zoomen.
 
abgesehen davon dass in deinem Code drawOrder() nie aufgerufen wird, bzw. von ... versteckt ist und so zu großen Teilen geraten werden muss, gilt:
getGraphics() ist nunmal grundsätzlich falsch,
wie in jedem minimalen Hello World-JFrame-Zeichnen-Beispiel stehen sollte, ist paintComponent() zu überschreiben usw.,
dann gibts keine Fehler
 
Arrrg, danke für den Hieb - habs verdient 🙁.

Zeichnen in PaintComponent sollte mir eigentlich klar sein. Die ... stehen dort, weil ich die Initialisierung der einzenlen Objekte auf dem Frame ausblenden wollte (war zu viel) - beim Klick auf den Button wird drawOrder aufgerufen.

Damit habe ich aber eigentlich ein neues Problem: Muss ich nun alles, was in drawOrder drinsteht in PaintComponent reinschreiben - das dürfte relativ viel sein. Oder sollte man dann schon mit einem BufferedImage arbeiten?
 
versuche es doch erstmal mit direkten Malen, drawOrder() kann ja auch als Methode bleiben, wird nur von paintComponent mit Graphics-Parameter aufgerufen,
in Buttons repaint();

wenn direktes Zeichnen wegen Dauer/ Flackern oder so ausfällt, dann eben auf BufferedImage malen,
auch dafür kann drawOrder() & Co bleiben, paintComponent könnte sich darum kümmern und das Graphics-Objekt des Bildes als Parameter übergeben,
aber wahrscheinlich lohnt sich dann schon den Code zu ändern, nicht alles neuzumalen


edit:
siehe auch Folgepost, stimme dort zu
 
Zuletzt bearbeitet von einem Moderator:
Muss ich nun alles, was in drawOrder drinsteht in PaintComponent reinschreiben - das dürfte relativ viel sein. Oder sollte man dann schon mit einem BufferedImage arbeiten?
Nein, blos nicht "alles" in die paintComponent. Nach Möglichkeit wird in der paint nur gezeichnet. Solche JOptionPane Aufrufe aber auch die Berechnung der Zufallszahlen gehören da nicht rein.
Am besten die relevanten Daten/Objekt vorab erzeugen und in der paintComponent einfach eine Schleife der zu zeichnenden Objekt durchlaufen und diese zeichnen oder ggf. sich selbst zeichnen lassen.
Eine Möglichkeit ist auch erst alles auf ein BufferdImage zu malen und in der paint nur das Bild zu malen. Wenn allerdings immer das Bild komplett neu "bemalt" wird macht das in den vielen Fällen keinen Sinn. Zusätzlich muss man hier evtl. Größenänderungen der Komponente beachten und ggf. ein neues Image erzeugen.
 
Ich denke, ich werde die Variante BufferedImage verwenden (hatte ich hier in anderem Zusammenhang schonmal erarbeitet). Größenänderungen habe ich nicht vor, lediglich Zeichnen von Grundelementen ...
 
So, ich habe es mal mit einem BufferedImage probiert, welches ich auf das JPanel kopiere ... sind jetzt drei Dateien:
Java:
package draw;

import static draw.DrawLib.drawLineParam;

public class DrawFrame extends JFrame implements MouseMotionListener {
	
	static final long serialVersionUID = 1;


	private JPanel contentPane;
	private JTextField tfNumber;
	private JLabel lStatus;
	private BufferedImage drawImage = new BufferedImage(600,500,BufferedImage.TYPE_INT_RGB);
	private DrawPanel mySheet = new DrawPanel(drawImage);
	private JComboBox cbSelect;

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

	/**
	 * Create the frame.
	 */
	public DrawFrame() {
		setTitle("Computergrafik");
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setBounds(100, 100, 757, 560);
		contentPane = new JPanel();
		contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
		setContentPane(contentPane);
		contentPane.setLayout(null);
		
		cbSelect = new JComboBox();
		cbSelect.setModel(new DefaultComboBoxModel(new String[] {"Linie Parameter"}));
		cbSelect.setFont(new Font("Tahoma", Font.PLAIN, 12));
		cbSelect.setName("cbSelect");
		cbSelect.setBounds(620, 11, 110, 32);
		contentPane.add(cbSelect);
		
		JLabel lblNewLabel = new JLabel("Anzahl:");
		lblNewLabel.setFont(new Font("Tahoma", Font.PLAIN, 12));
		lblNewLabel.setBounds(620, 52, 46, 14);
		contentPane.add(lblNewLabel);
		
		tfNumber = new JTextField();
		tfNumber.setText("1");
		tfNumber.setName("tfNumber");
		tfNumber.setFont(new Font("Tahoma", Font.PLAIN, 12));
		tfNumber.setBounds(676, 49, 54, 20);
		tfNumber.setColumns(10);
		contentPane.add(tfNumber);
		
		JButton btnDraw = new JButton("Zeichnen");
		btnDraw.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				drawOrder();
			}
		});
		btnDraw.setName("btnDraw");
		btnDraw.setBounds(620, 80, 110, 20);
		contentPane.add(btnDraw);
		
		mySheet.setBackground(Color.WHITE);
		mySheet.setBounds(10, 11, 600, 500);
		contentPane.add(mySheet);
		
		lStatus = new JLabel("Maus:");
		lStatus.setName("status");
		lStatus.setFont(new Font("Tahoma", Font.PLAIN, 12));
		lStatus.setBounds(620, 497, 110, 14);
		contentPane.add(lStatus);
		
		mySheet.addMouseMotionListener(this);
	}

	public void mouseDragged(MouseEvent e) {
		// TODO Auto-generated method stub
		
	}

	public void mouseMoved(MouseEvent e) {
		lStatus.setText("Maus: ("+e.getX()+"|"+e.getY()+")");
	}
	
	public void actionPerformed(ActionEvent e) {
		// TODO Auto-generated method stub
		
	}
	
	// Methoden zum Zeichnen
	public void drawOrder() {
		int count = -1;
		
		try
		{
			count = new Integer(tfNumber.getText()).intValue();
		} catch(Exception ex) {}
		
		if( count > 0 )
		{

	        Graphics2D g = drawImage.createGraphics();
	        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
	        g.setColor(new Color(255,255,255));
	        g.fillRect(0, 0, drawImage.getWidth(), drawImage.getHeight());

			for( int j = 0; j < count; j++ )
			{
				switch (cbSelect.getSelectedIndex())
				{
					case 0:
						drawLineParam(g,
								(int) (Math.random()*drawImage.getWidth()), 
								(int) (Math.random()*drawImage.getHeight()), 
								(int) (Math.random()*drawImage.getWidth()), 
								(int) (Math.random()*drawImage.getHeight()));
						break;
				}
			}
			
			mySheet.repaint();
			g.dispose();
		}
		else
			JOptionPane.showMessageDialog(null, "Für die Anzahl der Operationen eine positive Zahl angeben, danke");
	}
}
Java:
package draw;

import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;

public class DrawPanel extends JPanel
{
	static final long serialVersionUID = 1;	
	private BufferedImage drawImage;
	
	DrawPanel(BufferedImage drawImage)
	{
		this.drawImage = drawImage;
		this.setBackground(new Color(255,255,255));
	}
	
    @Override
	public void paintComponent(Graphics g)
	{
        super.paintComponent(g);
        if (drawImage != null) {
            g.drawImage(drawImage, 0, 0, this);
        }
	}
}
Java:
package draw;

import java.awt.Color;
import java.awt.Graphics;

public class DrawLib{

	// Setzen eines Pixel simulieren
	public static void setPixel(Graphics g, int x, int y)
	{
		g.setColor(new Color(0,0,0));				// Zeichenfarbe schwarz
		g.drawRect(x, y, 0, 0);						// Pixelsetzen
	}
	
	// Zeichnen einer Linie mit Parametern
	protected static void drawLineParam(Graphics g, int x1, int y1, int x2, int y2)
	{
		int x, y, dx, dy;
		double r, step;
		
		dy = y2 - y1;   							// Hoehenzuwachs
		dx = x2 - x1;   							// Schrittweite
		step = 1.0 / Math.sqrt(dx*dx + dy*dy);   	// Parameterschritt berechnen

		for(r=0.0; r < 1; r+=step)   				// fuer jeden Parameterwert
		{
			x = (int)(x1 + r*dx +0.5);   			// berechne neue x-Koordinate
			y = (int)(y1 + r*dy +0.5);   			// berechne neue y-Koordinate
			setPixel(g,x,y);   						// setze Pixel
		}
		setPixel(g,x2, y2);   						// letztes Pixel am Endpunkt
	}
	
}
 
Hm, das bisherige Programm hat noch ein Problem: Wenn z. B. 1000 Geraden gezeichnet werden sollen, dann hatte er diese erst alle auf das BufferedImage gezeichnet und dann ausgegeben - so stand es auch im Quelltext. Nun habe ich den repaint-Aufruf in die case-Struktur reingeschrieben:
Java:
				switch (cbSelect.getSelectedIndex())
				{
					case 0:
						drawLineParam(g,
								(int) (Math.random()*drawImage.getWidth()), 
								(int) (Math.random()*drawImage.getHeight()), 
								(int) (Math.random()*drawImage.getWidth()), 
								(int) (Math.random()*drawImage.getHeight()));
						mySheet.repaint();
						break;
				}
... aber er gibt trotzdem alle Geraden zusammen aus ... wie schaffe ich es, dass ich zusehen kann, wie er die nacheinander zeichnet?
 
wie schaffe ich es, dass ich zusehen kann, wie er die nacheinander zeichnet?

Hallo Papa Bär,

versuch's mal mit einem Swing Timer. Hier ist ein funktionierendes Beispiel:

Java:
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import javax.swing.*;

public class Testing {

    private final DrawingPanel drawingPanel;
    private final Random r = new Random();
    private int xL;
    private int yL;

    public Testing() {
        JFrame frame = new JFrame("Test");
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        drawingPanel = new DrawingPanel();
        frame.add(new JScrollPane(drawingPanel));
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
        Timer timer = new Timer(1000, new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                animation();
            }
        });
        timer.start();
    }

    private void animation() {
        int x = r.nextInt(drawingPanel.getWidth());
        int y = r.nextInt(drawingPanel.getHeight());
        drawingPanel.getG2d().drawLine(x, y, xL, yL);
        drawingPanel.repaint(Math.min(x, xL), Math.min(y, yL), Math.abs(x - xL) + 1, Math.abs(y - yL) + 1);
        xL = x;
        yL = y;
    }

    public static void main(final String args[]) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                Testing test = new Testing();
            }
        });
    }

    private class DrawingPanel extends JComponent {

        private Image image;
        private Graphics2D g2d;

        DrawingPanel() {
            super();
            setPreferredSize(new Dimension(300, 300));
        }

        @Override
        public Dimension getPreferredSize() {
            Dimension size = super.getPreferredSize();
            if (image != null) {
                size.width = image.getWidth(this);
                size.height = image.getHeight(this);
            }
            return size;
        }

        @Override
        public void paintComponent(final Graphics g) {
            super.paintComponent(g);
            // initialises the image with the first paint
            // or checks the image size with the current panelsize
            if (image == null || image.getWidth(this) < getSize().width
                    || image.getHeight(this) < getSize().height) {
                resetImage();
            }
            Graphics2D g2 = (Graphics2D) g;
            Rectangle r = g.getClipBounds();
            g2.drawImage(image, r.x, r.y, r.width + r.x, r.height + r.y,
                    r.x, r.y, r.width + r.x, r.height + r.y, this);
        }

        private void resetImage() {
            Image saveImage = image;
            Graphics2D saveG2d = g2d;
            image = createImage(getWidth(), getHeight());
            g2d = (Graphics2D) image.getGraphics();
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON);
            g2d.setBackground(Color.WHITE);
            g2d.clearRect(0, 0, getWidth(), getHeight());
            g2d.setColor(Color.BLACK);
            if (saveG2d != null) {
                g2d.setColor(saveG2d.getColor());
                g2d.drawImage(saveImage, 0, 0, this);
                saveG2d.dispose();
            }
        }

        public Graphics2D getG2d() {
            return g2d;
        }
    }
}
Gruß,
André
 
Hm, ich weiß nicht, ob mir hier ein Timer hilft. IN meinem Beispiel soll eine Gerade auf das Image gezeichnet werden, dann ausgegeben (UND ANGEZEIGT), dann die zweite ... usw. Die nächste Gerade soll also erst gezeichnet werdemn, wenn die erste fertig ist. Ein Timer steuert doch (so weit ich weiß) dan Ablauf von außen.
 
In meinem Beispiel soll eine Gerade auf das Image gezeichnet werden, dann ausgegeben (UND ANGEZEIGT), dann die zweite ... usw. Die nächste Gerade soll also erst gezeichnet werden, wenn die erste fertig ist. Ein Timer steuert doch (so weit ich weiß) den Ablauf von außen.

Damit wir den Ablauf des Zeichnens auf dem Bildschirm mit den Augen verfolgen können, ist unbedingt eine kurze Verzögerung zwischen den einzelnen Schritten nötig, also jeweils eine kurze Wartezeit nach dem Zeichnen jeder einzelnen Linie.

Bei Swing ist es aber so, dass die eigentliche Aktualisierung der Anzeige immer auf dem auf dem Event-Dispatch Thread ausgeführt werden muss, die Wartezeit aber muss in einem anderen Thread stattfinden, weil sonst die Anzeige lahm gelegt wird.

Ein javax.swing.Timer feuert Action Ereignisse nach Ablauf einer bestimmten Verzögerung. Er übernimmt also die Wartezeiten in dem bereits bestehenden Timer-Thread und die GUI-bezogene Aufgabe wird automatisch auf dem Event-Dispatch Thread ausgeführt, nämlich in der actionPerformed-Methode. Dort müssen wir also die jeweils nächste Linie zeichnen und repaint auf der zu aktualisierenden Komponente aufrufen.

Eine andere Möglichkeit besteht darin, einen eigenen Thread zu starten (entweder direkt oder über SwingWorker), den wir nach jedem repaint-Aufruf jeweils schlafen legen (Thread.sleep). Die repaint-Methode stellt zum Glück automatisch sicher, dass die Aktualisierung der Anzeige auf dem Event Dispatch Thread ausgeführt wird. Die meisten anderen Swing Aktualisierungsmethoden haben diese Automatik jedoch nicht. In solchen Fällen ist also Vorsicht geboten, wenn man keinen Swing-Timer benutzt.

Gruß,
André
 
Zuletzt bearbeitet:

Zurück
Oben