As far as I can see, drawing in JFrame works like this:
Extends a JPanel and override its paint(Graphics g) method
add(panel) it to your JFrame
Is it possible to
Draw a line
Display that line
Then later add a second line
Show the new resulting image
...
Yes, you need to use
repaint()
every time after you drew something new, to display the modifications on the screen. On a repaint, the whole screen is redrawn, therefore you need to ensure you draw again everything you want to keep.
With respect to preserving previous content, this question might help you out.
AuroMetal pointed me to this tutorial and I extracted, that JPanel has no idea, what it has already drawn, so you have to maintain an ArrayList of everything to be drawn and cycle it through on every repaint():
import java.awt.Graphics;
import java.util.ArrayList;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Canvas extends JPanel implements LinePrinter {
private static final long serialVersionUID = 1L;
private ArrayList<Line> lines = new ArrayList<Line>();
public Canvas(String title) {
// Just generating a JFrame to display this JPanel
JFrame frame = new JFrame(title);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 425);
frame.setVisible(true);
frame.add(this);
}
#Override
public void addLine(Line line) {
lines.add(line);
this.repaint();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Line line : lines) {
g.drawLine(line.A.x, line.A.y, line.B.x, line.B.y);
}
}
}
where my custom Line class is
public class Line {
public final Point A, B;
public Line(Point A, Point B) {
this.A = A;
this.B = B;
}
}
and my custom Point class is
public class Point {
public final int x;
public final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
Related
I'm quite new to swing, and I'm having an issue with graphics not showing up in my JFrame. What I should be seeing is a blue rectangle slowly moving downwards through the frame, and behind it is a plain white background. However, when I run my main class, all I see is a plain JFrame. This is my code:
Execute class
public class Execute {
public static void main (String[ ] args) {
GUI gui = new GUI();
gui.createFrame(800,600);
ElevatorOne e1 = new ElevatorOne();
e1.addElevatorOne();
}
}
ElevatorOne class (Where the graphics should be initialized and added)
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JPanel;
public class ElevatorOne extends GUI{
int y = 100;
public void addElevatorOne() {
drawElevatorOne drawE1 = new drawElevatorOne();
frame.getContentPane().add(drawE1);
for(int i = 0; i < 130; i++) {
y++;
drawE1.repaint();
try {
Thread.sleep(50);
} catch (Exception ex) { }
}
}
#SuppressWarnings("serial")
class drawElevatorOne extends JPanel{
public void paintComponent(Graphics g) {
g.setColor(Color.WHITE);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(Color.BLUE);
g.drawRect(200,y,40,60);
}
}
}
And finally, my GUI class (where frame is created)
import javax.swing.JFrame;
public class GUI {
JFrame frame = new JFrame();
public void createFrame(int x, int y) {
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
frame.setResizable(false);
frame.setSize(x, y);
}
}
While you've got an accepted answer, I do take issue with that answer and feel impelled to add my two cents:
I see no purpose to your having a GUI class and then having Elevator1 extend it. If you want Elevator1 to use a JFrame, then have it create a JFrame as there really is no need or benefit from your inheritance.
Myself, I'd have Elevator1 extend JPanel and then have it draw in its own paintComponent method, thus eliminating the need for drawElevatorOne inner class (which should be named DrawElevatorOne to adhere to Java naming conventions).
You are using Thread.sleep in a Swing GUI which is extremely risky to do. The only reason this works is because it is being called in the main thread. If your code were properly created and set up to start and create GUI components in the Swing event thread, this would and should fail since it would put the Swing event thread to sleep. Don't do this, don't call Thread.sleep in a method that has any risk of being called in the Swing event thread.
Instead use a Swing Timer to manage your delay.
Don't forget to (almost) always call the super.paintComponent(g) method within your oeverride. To not do this breaks the Swing painting chain and risks significant hard to debug side effects.
For example:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class ElevatorTest {
private static final int PREF_W = 800;
private static final int PREF_H = 600;
private static void createAndShowGui() {
MyElevator mainPanel = new MyElevator(PREF_W, PREF_H);
JFrame frame = new JFrame("Elevator Test");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
// start everything on the Swing event thread
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
class MyElevator extends JPanel {
private static final Color BACKGROUND = Color.white;
private static final int ELEVATOR_X = 200;
private static final int ELEVATOR_W = 40;
private static final int ELEVATOR_H = 60;
private static final int TIMER_DELAY = 50;
public static final int MAX_ELEVATOR_Y = 130;
private static final Color ELEVATOR_COLOR = Color.blue;
private int prefW;
private int prefH;
private int elevatorY = 0;
public MyElevator(int prefW, int prefH) {
this.prefW = prefW;
this.prefH = prefH;
setBackground(BACKGROUND);
new Timer(TIMER_DELAY, new TimerListener()).start();
}
#Override
protected void paintComponent(Graphics g) {
// Don't forget to call the super method
super.paintComponent(g);
g.setColor(ELEVATOR_COLOR);
g.fillRect(ELEVATOR_X, elevatorY, ELEVATOR_W, ELEVATOR_H);
}
// to help size our GUI properly
#Override
public Dimension getPreferredSize() {
Dimension superSz = super.getPreferredSize();
if (isPreferredSizeSet()) {
return superSz;
}
int w = Math.max(superSz.width, prefW);
int h = Math.max(superSz.height, prefH);
return new Dimension(w, h);
}
private class TimerListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
if (elevatorY >= MAX_ELEVATOR_Y) {
// if elevator at max, stop thimer
((Timer) e.getSource()).stop();
} else {
// advance elevator and draw it
elevatorY++;
repaint();
}
}
}
}
Your ElevatorOne class extends GUI. Therefore it inherits all of the functionality of GUI, yet you create both of them. This is probably what you intended to do:
edit: I ran this code and indeed there is a box moving as you specify.
public class Execute {
public static void main (String[ ] args) {
// GUI gui = new GUI();
// gui.createFrame(800,600);
ElevatorOne e1 = new ElevatorOne();
e1.createFrame(800, 600);
e1.addElevatorOne();
}
}
I am trying to learn the paint method and get a ball to move across the frame. here is my code so far. w=.
I currently have two classes One is the main and one for the ball.
this is the main class
import java.awt.;
import javax.swing.;
public class PaintTest extends JPanel {
int x = 0;
int y = 0;
public void moveBall(){
x = x + 1;
y = y + 1;
}
public static void main(String[] args){
JFrame frame = new JFrame();
frame.setSize(500,500);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
Ball ball = new Ball(x,y);
while(true){
ball.moveBall();
repaint();
}
}
protected void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g.setColor(Color.magenta);
g.drawLine(0,100,500,100);
g.drawLine(0,101,500,101);
g.drawLine(0,102,500,102);
g.drawLine(0,103,500,103);
g2.fillOval(x,y,35,35);
}
}
and here is the ball class
public class Ball {
int x,y;
public Ball(int x, int y){
this.x = x;
this.y = y;
}
}
now when i compile I get an error saying cannot find symbol ball in class PaintTest even though I am calling it from the class Ball. I am aware of the repaint error as i do not know what to put in front of it.
Draw in a JPanel
In its paintComponent method not in its paint method -- this gives you double buffering.
Call the super's paintComponent method in your override. This allows the JPanel to do housekeeping drawing including erasing the oval image at its old position.
Don't use a while (true) loop as this can cause serious Swing threading issues. Use a Swing Timer instead.
In the Swing Timer, increment your animation variables and then call repaint(). This will tell Swing to repaint the component which will re-draw the oval in the new location.
Don't guess at this stuff as that leads to frustration since Swing graphics coding is a different beast. Instead check the tutorials. You can find links to the Swing tutorials and to other Swing resources here: Swing Info. Also check out Performing Custom Painting with Swing.
Graphics2D goodies: RenderingHints can be used to smooth out your image jaggies.
More Graphics2D goodies: Stroke can be used to draw thicker lines when needed.
For example:
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
#SuppressWarnings("serial")
public class PaintTest extends JPanel {
private static final int PREF_W = 600;
private static final int PREF_H = PREF_W;
private static final int TIMER_DELAY = 20;
private static final Stroke STROKE = new BasicStroke(5f);
private int x;
private int y;
public PaintTest() {
new Timer(TIMER_DELAY, new TimerListener()).start();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
// to smooth graphics
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(Color.magenta);
Stroke initialStroke = g2.getStroke();
g2.setStroke(STROKE);
g.drawLine(0, 100, 500, 100);
g2.setStroke(initialStroke);
g2.fillOval(x, y, 35, 35);
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
private class TimerListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
x++;
y++;
repaint();
}
}
private static void createAndShowGui() {
PaintTest mainPanel = new PaintTest();
JFrame frame = new JFrame("PaintTest");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
You have to put the paintComponent method in a JPanel. You can do it by using something like this.
JPanel panel = new JPanel(){
#Overide
public void paintComponent(Graphics g){
super.paint();
// Draw Stuff Here
}
};
The reason you are not getting the ball to move across the frame is that you are not calling the repaint method. You should do so on a thread.
Thread th = new Thread(new Runnable(){
#Overide
public void run(){
while(frame.isVisible()){
ball.moveBall();
panel.repaint();
try{Thread.sleep(5);}catch(Exception e){e.printStackTrace();}
}
}
});
Also, why are you making ball a instance of the PaintTest class? To get only one frame and ball you would want to add a class named Ball and use that to make an instance:
public class Ball{
int x, y;
public Ball(int x, int y){
this.x = x;
this.y = y;
}
}
That is why you were getting 2 frames.
Then you would want to get rid of the x and y variables in the main class. To make an instance using this class you would do:
Ball ball = new Ball(x, y);
Then to paint the ball in the paintComponent method you would do:
g.fillOval(ball.x, ball.y, 35, 35);
You didn't call the repaint(); method.
You don't need the y + 1 part.
Instead of using the while(true) loop, you should use a for loop.
You didn't call the super.paint() method.
You didn't use any Thread.sleep(), which made the ball move across instantaneously.
Here is the code:
import java.awt.*;
import javax.swing.*;
public class PaintTest extends JFrame {
int x = 8;
int y = 30;
public void moveBall(){
x = x + 1;
//y = y + 1;
try{
Thread.sleep(500);
} catch(InterruptedException e){
}
repaint();
}
public static void main(String[] args){
PaintTest frame1 = new PaintTest();
PaintTest ball = new PaintTest();
for(int i = 0; i<100; i++){
//while(true){
ball.moveBall();
}
}
public PaintTest() {
super("Paint Test");
setSize(500,500);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
}
public void paint(Graphics g){
Graphics2D g2 = (Graphics2D) g;
super.paint(g);
super.paint(g2);
g.setColor(Color.magenta);
g.drawLine(0,100,500,100);
g.drawLine(0,101,500,101);
g.drawLine(0,102,500,102);
g.drawLine(0,103,500,103);
g.fillOval(x,y,35,35);
}
}
This code will make the ball move across the screen VERY slowly. If you want to speed it up, change the number of miliseconds in the Thread.sleep(miliseconds) part to a smaller number of miliseconds.
I need to get a mirror of JLabel or JTextArea.
http://www.subirimagenes.net/i/150305101716451074.jpg
If I use a JTextArea, I'll need the letters are complety mirrored.
If I use a JLabel, I'll need format and the mirrored letters.
The example was created on Photoshop.
My idea is using graphics(), but I don't have idea how to do it.
Here's the bad news: It's not as straight-forward as we might wish, there's a limitation. In Swing, graphics transformations are applied only on the paint operation, not the general layout and event process. Therefore, in Swing the mirrored component is basically "unusable", it cannot be used for anything else than displaying the mirror image of the primary component. Coordinates of mouse clicks etc. will be wrong.
Therefore, this is all tricky stuff, a bit hackish.
There are multiple ways how you can do that.
One possibility is to use two views on one model and tell the Graphics of one of the views to flip horizontally.
Here's an example how to do so which demonstrates a flipped JEditorPane:
import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;
public class MirrorText {
public static void main(final String... args) {
SwingUtilities.invokeLater(MirrorText::setupUI);
}
public static void setupUI() {
final JEditorPane editor = new JEditorPane("text/html", "<h1>Mirrored</h1><p>This is mirrored text.</p>");
final JEditorPane mirroredEditor = new JEditorPane("text/html", "") {
protected Graphics getComponentGraphics(final Graphics g) {
return horizontalFlip(super.getComponentGraphics(g), getWidth());
}
};
mirroredEditor.setDocument(editor.getDocument());
final JFrame frame = new JFrame("mirrored label");
final JPanel mirrorPanel = new JPanel(new GridLayout(1, 2));
mirrorPanel.add(new JScrollPane(editor));
mirrorPanel.add(new JScrollPane(mirroredEditor));
frame.add(mirrorPanel);
frame.pack();
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
frame.setVisible(true);
}
public static Graphics horizontalFlip(final Graphics g, final int width) {
final Graphics2D g2d = (Graphics2D) g;
final AffineTransform tx = g2d.getTransform();
tx.scale(-1.0, 1.0);
tx.translate(-width, 0);
g2d.setTransform(tx);
return g2d;
}
}
The advantage of this solution is that the mirror entirely the original component's MVC and observers because it is the same type of component (View/Controller) on the very same model.
The disadvantage of this solution is that you have create the mirror component in a way that is very specific to the component that is mirrored.
Another possibility is to create a decorator JComponent Mirror which can mirror an arbitrary other JComponent. This is a bit tricky, as in Java, decorators cannot override methods of the decorated object, and the Mirror needs to be updated (repainted) as well whenever the original component is updated (repainted).
Here's an incomplete example using a Mirror which hooks into the corresponding events. Incomplete because it only hooks into DocumentEvent but should also hook onto other events as well, like CaretEvent. It would be nice if Swing would have something like a PaintEvent. As far as I am aware of, it hasn't. (Well, in fact it has, but there's no corresponding PaintListener and addPaintListener().)
Also incomplete because the Mirror doesn't observe the original component's attributes like size. It only works because the GridLayout on the MirrorPanel keeps the mirror size in sync with the original component.
import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;
import javax.swing.event.*;
public class MirrorText {
public static void main(final String... args) {
SwingUtilities.invokeLater(MirrorText::setupUI);
}
public static void setupUI() {
final JEditorPane editor = new JEditorPane("text/html", "<h1>Mirrored</h1><p>This is mirrored text.</p>");
final MirrorPanel mirrorPanel = new MirrorPanel(new JScrollPane(editor));
editor.getDocument().addDocumentListener(new DocumentListener() {
public void changedUpdate(final DocumentEvent e) { mirrorPanel.updateMirror(); }
public void insertUpdate(final DocumentEvent e) { mirrorPanel.updateMirror(); }
public void removeUpdate(final DocumentEvent e) { mirrorPanel.updateMirror(); }
});
final JFrame frame = new JFrame("mirrored label");
frame.add(mirrorPanel);
frame.pack();
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
frame.setVisible(true);
}
}
class MirrorPanel extends JPanel {
public MirrorPanel(final JComponent c) {
super(new GridLayout(1, 2));
add(c);
add(new Mirror(c));
}
public void updateMirror() {
repaint();
}
}
class Mirror extends JComponent {
private final JComponent mirroredComponent;
public Mirror(final JComponent mirroredComponent) {
this.mirroredComponent = mirroredComponent;
}
public static Graphics horizontalFlip(final Graphics g, final int width) {
final Graphics2D g2d = (Graphics2D) g;
final AffineTransform tx = g2d.getTransform();
tx.scale(-1.0, 1.0);
tx.translate(-width, 0);
g2d.setTransform(tx);
return g2d;
}
public void paint(final Graphics g) {
mirroredComponent.paint(horizontalFlip(g, mirroredComponent.getWidth()));
}
}
There probably are more possibilities as well. For example, one could override the mirrored component's paint() method to update the mirror component as well. That would get rid of getting notified, but it would lead to unnecessary paint() calls in case painting isn't done due to content change but due to buffer destruction (i.e. other window moved away).
Here's one way to create a mirror image.
Basically, you print the contents of the JTextArea on a BufferedImage. Then you reverse the pixels of the BufferedImage on the X axis.
package com.ggl.testing;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
public class MirrorImage implements Runnable {
private JFrame frame;
public static void main(String[] args) {
SwingUtilities.invokeLater(new MirrorImage());
}
#Override
public void run() {
frame = new JFrame("Mirror Image Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel mainPanel = new JPanel();
mainPanel.setLayout(new FlowLayout());
JPanel textPanel = new JPanel();
JTextArea textArea = new JTextArea(15, 30);
textPanel.add(textArea);
mainPanel.add(textPanel);
MirrorPanel mirrorPanel = new MirrorPanel();
mirrorPanel.setPreferredSize(textPanel.getPreferredSize());
mainPanel.add(mirrorPanel);
TextListener listener = new TextListener(textArea, mirrorPanel);
textArea.getDocument().addDocumentListener(listener);
frame.add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
listener.createImage(textArea);
frame.setVisible(true);
}
private class MirrorPanel extends JPanel {
private static final long serialVersionUID = 2496058019297247364L;
private Image image;
public void setImage(Image image) {
this.image = image;
repaint();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, (getWidth() - image.getWidth(this)) / 2,
(getHeight() - image.getHeight(this)) / 2, this);
}
}
private class TextListener implements DocumentListener {
private JTextArea textArea;
private MirrorPanel mirrorPanel;
public TextListener(JTextArea textArea, MirrorPanel mirrorPanel) {
this.textArea = textArea;
this.mirrorPanel = mirrorPanel;
}
#Override
public void insertUpdate(DocumentEvent event) {
createImage(textArea);
}
#Override
public void removeUpdate(DocumentEvent event) {
createImage(textArea);
}
#Override
public void changedUpdate(DocumentEvent event) {
createImage(textArea);
}
public void createImage(JTextArea textArea) {
BufferedImage img = new BufferedImage(textArea.getWidth(),
textArea.getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = img.createGraphics();
textArea.printAll(g2d);
g2d.dispose();
createMirrorImage(img);
mirrorPanel.setImage(img);
}
private void createMirrorImage(BufferedImage img) {
int width = img.getWidth();
int height = img.getHeight();
int[][] pixels = new int[width][height];
for (int i = width - 1; i >= 0; i--) {
int j = width - i - 1;
for (int k = 0; k < height; k++) {
pixels[j][k] = img.getRGB(i, k);
}
}
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
img.setRGB(i, j, pixels[i][j]);
}
}
}
}
}
I'm trying to draw two lines in a Canvas in Java, calling two methods separately, but when I draw the second line, the first one disapears (Java clears the screen). How can I avoid that? I want to see the two lines. I've seen paint tutorials (how to make a program like the Paint on Windows) where the user uses the mouse to draw lines and when one line is drawn, the other do not disappear. They just call the paint method and it does not clear the screen.
I'll be grateful if anyone can help me.
Thanks.
View Class
import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JFrame;
public class CircuitTracePlotView extends JFrame {
private CircuitTracePlot circuitTracePlot;
public CircuitTracePlotView() {
this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
this.getContentPane().add(circuitTracePlot = new CircuitTracePlot(), BorderLayout.CENTER);
this.pack();
this.setSize(250,250);
this.setLocationRelativeTo(null);
this.setVisible(true);
circuitTracePlot.drawLine();
circuitTracePlot.drawOval();
}
}
class CircuitTracePlot extends Canvas {
private final static short LINE = 1;
private final static short OVAL = 2;
private int paintType;
private int x1;
private int y1;
private int x2;
private int y2;
public CircuitTracePlot() {
this.setSize(250,250);
this.setBackground(Color.WHITE);
}
private void setPaintType(int paintType) {
this.paintType = paintType;
}
private int getPaintType() {
return this.paintType;
}
public void drawLine() {
this.setPaintType(LINE);
this.paint(this.getGraphics());
}
public void drawOval() {
this.setPaintType(OVAL);
this.paint(this.getGraphics());
}
public void repaint() {
this.update(this.getGraphics());
}
public void update(Graphics g) {
this.paint(g);
}
public void paint(Graphics g) {
switch (paintType) {
case LINE:
this.getGraphics().drawLine(10, 10, 30, 30);
case OVAL:
this.getGraphics().drawLine(10, 20, 30, 30);
}
}
}
Main class
import javax.swing.SwingUtilities;
import view.CircuitTracePlotView;
public class Main {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
CircuitTracePlotView cr = new CircuitTracePlotView();
}
});
}
}
You almost never should call paint(...) directly. I can count the times that I've needed to do this on one hand.
Do not get a Graphics object by calling getGraphics() on a component as that will return a non-durable Graphics object. Instead either draw in a BufferedImage and display that in the paint method or draw in the paint method (if AWT).
Since this is a Swing GUI, don't use an AWT component to draw in. Use a JPanel and override the paintComponent(...) method, not the paint(...) method. Otherwise you lose all benefits of Swing graphics including automatic double buffering.
The super.paintComponent(g) method should be called in the paintComponent(Graphics g) override, often as the first method call inside of this method. This lets the component do its own housekeeping painting, including erasing drawings that need to be erased.
Read the tutorials on Swing graphics as most of this is all well explained there. For e.g., please have a look here:
Lesson: Performing Custom Painting
Painting in AWT and Swing
Edit
To have your images persist, I suggest that you draw to a BufferedImage and then display that Image in your JPanel's paintComponent(...) method.
Or another option is to create a Collection of Shape objects, perhaps an ArrayList<Shape> and fill it with the Shapes you'd like to draw, and then in the paintComponent(...) method cast the Graphics object to a Graphics2D object and iterate through the Shape collection drawing each shape with g2d.draw(shape) as you iterate.
Since Trash posted his code,...
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class CircuitTracePlot2 extends JPanel {
private static final int PREF_W = 250;
private static final int PREF_H = PREF_W;
private int drawWidth = 160;
private int drawHeight = drawWidth;
private int drawX = 10;
private int drawY = 10;
private PaintType paintType = PaintType.LINE;
public CircuitTracePlot2() {
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
public void setPaintType(PaintType paintType) {
this.paintType = paintType;
repaint();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (paintType == null) {
return;
}
switch (paintType) {
case LINE:
g.drawLine(drawX, drawY, drawWidth, drawHeight);
break;
case OVAL:
g.drawOval(drawX, drawY, drawWidth, drawHeight);
break;
case SQUARE:
g.drawRect(drawX, drawY, drawWidth, drawHeight);
default:
break;
}
}
private static void createAndShowGui() {
final CircuitTracePlot2 circuitTracePlot = new CircuitTracePlot2();
JFrame frame = new JFrame("CircuitTracePlot2");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(circuitTracePlot);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
int timerDelay = 2 * 1000;
new Timer(timerDelay , new ActionListener() {
private int paintTypeIndex = 0;
#Override
public void actionPerformed(ActionEvent arg0) {
paintTypeIndex++;
paintTypeIndex %= PaintType.values().length;
circuitTracePlot.setPaintType(PaintType.values()[paintTypeIndex]);
}
}).start();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
enum PaintType {
LINE, OVAL, SQUARE;
}
Here's a variation on your program that implements much of #Hovercraft's helpful advice. Try commenting out the call to setPaintType() to see the effect.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
/** #see http://stackoverflow.com/a/15854246/230513 */
public class Main {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
CircuitTracePlotView cr = new CircuitTracePlotView();
}
});
}
private static class CircuitTracePlotView extends JFrame {
private CircuitTracePlot plot = new CircuitTracePlot();
public CircuitTracePlotView() {
this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
plot.setPaintType(CircuitTracePlot.OVAL);
this.add(plot, BorderLayout.CENTER);
this.pack();
this.setLocationRelativeTo(null);
this.setVisible(true);
}
}
private static class CircuitTracePlot extends JPanel {
public final static short LINE = 1;
public final static short OVAL = 2;
private int paintType;
public CircuitTracePlot() {
this.setBackground(Color.WHITE);
}
public void setPaintType(int paintType) {
this.paintType = paintType;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
switch (paintType) {
case LINE:
g.drawLine(10, 10, 30, 30);
case OVAL:
g.drawOval(10, 20, 30, 30);
default:
g.drawString("Huh?", 5, 16);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(250, 250);
}
}
}
I have asked a question regarding custom widget but confused to whether I need it and how should proceed.
I have currently this class
public class GUIEdge {
public Node node1;
public Node node2;
public int weight;
public Color color;
public GUIEdge(Node node1, Node node2 , int cost) {
this.node1 = node1;
this.node2 = node2;
this.weight = cost;
this.color = Color.darkGray;
}
public void draw(Graphics g) {
Point p1 = node1.getLocation();
Point p2 = node2.getLocation();
g.setColor(this.color);
((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
g.drawLine(p1.x, p1.y, p2.x, p2.y);
}
}
Currently this draws an edge between two points but now I want that a label for the cost also gets created along with it.
I have already added handling for dragging of node and edges so what is the best way to create the label
Do I need to make a custom widget for that ? Could anyone explain that suppose making a component by extending from JComponent then I'll call it by g.mixed() where mixed is that new widget...?
Tool tips are certainly worth a look. Other choices include drawString(), translate(), or TextLayout. There are many examples available.
Addendum: The example below shows both drawString() and setToolTipText(), as suggested by #Catalina Island. For simplicity, the endpoints are relative to the component's size, so you can see the result of resizing the window.
Addendum: This use of setToolTipText() merely demonstrates the approach. As #camickr notes here, you should override getToolTipText(MouseEvent) and update the tip when the mouse is over the line or when the line is selected.
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Point;
import javax.swing.JComponent;
import javax.swing.JFrame;
/** #see https://stackoverflow.com/questions/5394364 */
public class LabeledEdge extends JComponent {
private static final int N = 20;
private Point n1, n2;
public LabeledEdge(int w, int h) {
this.setPreferredSize(new Dimension(w, h));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
this.n1 = new Point(N, N);
this.n2 = new Point(getWidth() - N, getHeight() - N);
g.drawLine(n1.x, n1.y, n2.x, n2.y);
double d = n1.distance(n2);
this.setToolTipText(String.valueOf(d));
g.drawString(String.valueOf((int) d),
(n1.x + n2.x) / 2, (n1.y + n2.y) / 2);
}
private static void display() {
JFrame f = new JFrame("EdgeLabel");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new LabeledEdge(320, 240));
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
display();
}
});
}
}
I think you can use GUIEdge extends JComponent. That way you'd get tool tip labels automatically.