I'll like to make an Oval move from one place to the other in a JPanel when a button is clicked. This is the code I came up with. When I click the button however it all happens at once without visible movement the slow from the start to finish seen. The Oval just appears in a new location.
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
import javax.swing.JPanel;
public class testtest implements ActionListener{
JButton button;
MyDrawPanel panel;
int x = 0;
int y = 0;
public static void main(String[]args){
testtest test = new testtest();
test.go();
}
public void go(){
JFrame frame = new JFrame("Balloon Balls");
panel = new MyDrawPanel();
button = new JButton("Restart");
button.addActionListener(this);
panel.add(button);
frame.setSize(300, 300);
frame.add(panel);
frame.setVisible(true);
}
#Override
public void actionPerformed (ActionEvent e){
for(int i=0;i<130;i++){
x++;
y++;
panel.repaint();
try {
Thread.sleep(100);
} catch(Exception ex) { }
}
}
class MyDrawPanel extends JPanel{
#Override
public void paintComponent(Graphics g){
g.fillOval(x, y, 30, 30);
g.setColor(Color.BLACK);
}
}
}
Swing is single thread AND not thread safe.
Using Thread.sleep(100) within the ActionListener is blocking the Event Dispatching Thread, preventing anything from been painted. A new paint pass won't occur until after the actionPerformed method exists.
See Concurrency in Swing for more details.
Swing is also not thread safe, this means you should never make changes to the UI from outside the context of the EDT.
The easiest solution is to make use of a Swing Timer, which will allow to establish regularly timed callbacks, which are executed within the Event Dispatching Thread, but which won't block the EDT.
You're also missing one of the important concepts of OO, encapsulation. The x/y properties should actually be managed by the MyDrawPanel, not testtest
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.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class testtest implements ActionListener {
JButton button;
MyDrawPanel panel;
public static void main(String[] args) {
testtest test = new testtest();
test.go();
}
public void go() {
JFrame frame = new JFrame("Balloon Balls");
panel = new MyDrawPanel();
button = new JButton("Restart");
button.addActionListener(this);
panel.add(button);
frame.add(panel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private Timer timer;
public void actionPerformed(ActionEvent e) {
if (timer != null) {
return;
}
timer = new Timer(100, new ActionListener() {
#Override
public void actionPerformed(ActionEvent evt) {
if (panel.update()) {
timer.stop();
timer = null;
}
}
});
timer.start();
}
class MyDrawPanel extends JPanel {
private int xPosy = 0;
private int yPosy = 0;
#Override
public Dimension getPreferredSize() {
return new Dimension(300, 300);
}
public boolean update() {
xPosy++;
yPosy++;
repaint();
return xPosy > getWidth() || yPosy > getHeight();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.fillOval(xPosy, yPosy, 30, 30);
g.setColor(Color.BLACK);
}
}
}
paintComponent does just that, it paints the panel. Initially the panel paints the oval at the start x y. You push the button and the window is erased, and repainted at the new XY.
Movement is a concept you'll need to teach the computer. If we update the panel multiple times a second and slowly move the x y, we would make an illusion of movement.
Make a timer that refreshes every 10ms. Each time it refreshes, slightly increment the x and y values and repaint the panel.
In addition to the explanation about Swing thread issues in MadProgrammer's answer I would recommend separating the gui from its control by implementing the MVC Pattern.
This offers better encapsulation, better separation of responsibilities, and makes it easier to use threads for off-edt processing.
Have a model that holds all the information that the view (gui) needs:
/*
* The model contains the information for the view and information from the view
* The model is independent of the user interface.
* It notifies Listener on changes.
*/
class Model {
private Listener listener;
private int x = 0, y = 0;
synchronized int getX() {return x;}
synchronized void setX(int x) { this.x = x; }
synchronized int getY() {return y;}
synchronized void setY(int y) { this.y = y; }
void setListener(Listener listener){
this.listener = listener;
}
//notify listener when changed
void notifyListener(){
if(listener != null) {
listener.onChange();
}
}
}
In this case synchronization was added to allow the model to be used by threads.
Listener is defined by :
/*
* A simple interface used to link View and Model
*/
interface Listener {
void onChange();
}
View is just that. It implements Listener so it can listen to Model changes:
/*
* View is just that: a dumb as possible display
*/
public class View implements Listener{
private final JButton button;
private final MyDrawPanel panel;
private final Model model;
public View(Model model) {
this.model = model;
panel = new MyDrawPanel();
button = new JButton("Restart");
panel.add(button);
}
public void go(){
JFrame frame = new JFrame("Balloon Balls");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300, 300);
frame.add(panel);
frame.setVisible(true);
}
class MyDrawPanel extends JPanel{
#Override
public void paintComponent(Graphics g){
super.paintComponent(g); //always call super
g.fillOval(model.getX(), model.getY(), 30, 30);
g.setColor(Color.BLACK);
}
}
#Override
public void onChange() {
panel.repaint();
}
void addActionListener(ActionListener listener){
button.addActionListener(listener);
}
}
Putting it all together: see the following mvce : it adds a controller that controls the model and view.
For convenience and simplicity, the following code can be copy-pasted into one file called View.java, and run.
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
/*
* View is just that: a dumb as possible display
*/
public class View implements Listener{
private final JButton button;
private final MyDrawPanel panel;
private final Model model;
public View(Model model) {
this.model = model;
panel = new MyDrawPanel();
button = new JButton("Restart");
panel.add(button);
}
public void go(){
JFrame frame = new JFrame("Balloon Balls");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300, 300);
frame.add(panel);
frame.setVisible(true);
}
class MyDrawPanel extends JPanel{
#Override
public void paintComponent(Graphics g){
super.paintComponent(g); //always call super
g.fillOval(model.getX(), model.getY(), 30, 30);
g.setColor(Color.BLACK);
}
}
#Override
public void onChange() {
panel.repaint();
}
void addActionListener(ActionListener listener){
button.addActionListener(listener);
}
public static void main(String[]args){
new Controller();
}
}
/*
* A simple interface used to link View and Model
*/
interface Listener {
void onChange();
}
/*
* The model contains the information for the view and information from the view
* The model is independent of the user interface.
* It notifies Listener on changes.
*/
class Model {
private Listener listener;
private int x = 0, y = 0;
synchronized int getX() {return x;}
synchronized void setX(int x) { this.x = x; }
synchronized int getY() {return y;}
synchronized void setY(int y) { this.y = y; }
void setListener(Listener listener){
this.listener = listener;
}
//notify listener when changed
void notifyListener(){
if(listener != null) {
listener.onChange();
}
}
}
/*
* The controller "wires" the view and model, and does the processing.
*/
class Controller implements ActionListener{
private final Model model;
private final View view;
public Controller() {
model = new Model();
view = new View(model);
model.setListener(view);
view.addActionListener(this);
view.go();
}
#Override
public void actionPerformed (ActionEvent e){
new Thread(()->{
for(int i=0;i<130;i++){
model.setX(model.getX()+1);
model.setY(model.getY()+1);
model.notifyListener();
System.out.println(model.getX()+" - "+ model.getY());
try {
Thread.sleep(100);
} catch(Exception ex) { }
}
}).start();
}
}
Related
I want to make a little rain program in swing, but for some reason I cannot repaint the panel from another class. I tried using an inner class for the panel this time, but it doesn't seem to work with repainting it from another class/thread. Does someone know why?
sscce:
import javax.swing.JPanel;
import javax.swing.Timer;
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.JFrame;
public class UI extends JFrame {
public static void main(String[] args) {
UI myProgram = new UI();
myProgram.setVisible(true);
}
public UI() {
this.setSize(new Dimension(500,300));
this.setBackground(Color.WHITE);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
UserPanel p = new UserPanel(this);
}
public class UserPanel extends JPanel implements ActionListener {
private Timer time = new Timer(1, this);
private UI myFrame;
public UserPanel(UI myFrame) {
this.myFrame = myFrame;
this.setSize(myFrame.getSize());
time.start();
}
#Override
public void actionPerformed(ActionEvent e) {
repaint();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
System.out.println("painting");
g.setColor(Color.BLACK);
g.fillRect(this.getWidth()/2, this.getHeight()/2, 50,50);
}
}
}
UI Class (with inner class JPanel):
package Rain;
import javax.swing.JPanel;
import javax.swing.Timer;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.JFrame;
public class UI extends JFrame {
public UI() {
this.setSize(new Dimension(500,300));
this.setBackground(Color.WHITE);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
UserPanel p = new UserPanel(this);
}
private class UserPanel extends JPanel implements ActionListener {
private Timer time = new Timer(1, this);
private UI myFrame;
private ArrayList<Raindrop> rain = new ArrayList<Raindrop>();
private static final int AMOUNT = 50;
private Random rand = new Random();
public UserPanel(UI myFrame) {
this.myFrame = myFrame;
this.setSize(myFrame.getSize());
for(int i = 0; i < AMOUNT; i++) {
createRain();
}
new Painter(this);
time.start();
}
public void createRain() {
float distance = rand.nextFloat() * 90 + 10;
int x = rand.nextInt(this.getWidth());
int y = 100;
rain.add(new Raindrop(distance,x,y));
}
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("tick");
for(Raindrop r : rain) {
r.fall();
}
}
public void paintComponent(Graphics g) {
System.out.println("painting");
g.setColor(this.getBackground());
g.fillRect(0,0,this.getWidth(),this.getHeight());
for(Raindrop r : rain) {
r.draw(g);
}
}
}
}
Painter:
package Rain;
import javax.swing.JPanel;
public class Painter extends Thread {
private JPanel p;
public Painter(JPanel p) {
this.p = p;
this.start();
}
public void run() {
while(true) {
System.out.println("trying to paint..");
p.repaint();
}
}
}
Console Output:
trying to paint..
tick
trying to paint..
tick
...
Expected Output:
trying to paint..
painting
tick
trying to paint..
...
The thread does work but it never calls the paintComponent(Graphics g) function in the panel
All Swing applications must run on their own thread, called EDT. (Hopefully, you start your application by calling SwingUtilities#invokelater method). So, repainting a component outside of Event Dispatch Thread is really bad bad (bad) idea. Instead of creating new Thread, repaint the component inside javax.swing.Timer's action listener since it will run in EDT.
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("tick");
for(Raindrop r : rain) {
r.fall();
}
repaint(); //repaint in EDT
}
Also, when you #Override paintComponent method, always start by calling super.paintComponent(g);
public void paintComponent(Graphics g) {
super.paintComponent(g);//let component get painted normally
System.out.println("painting");
g.setColor(this.getBackground());
g.fillRect(0,0,this.getWidth(),this.getHeight());
for(Raindrop r : rain) {
r.draw(g);
}
}
UPDATE after your SSCCE
In order a component to get painted, it must have a parent. You UserPanel p = new UserPanel(this); but you never add it to the frame:
UserPanel p = new UserPanel(this);
getContentPane().setLayout(new BorderLayout());
getContentPane().add(p);
The complete SSCCE:
public class UI extends JFrame {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> { //Run in EDT
UI myProgram = new UI();
myProgram.setVisible(true);
});
}
public UI() {
super("title");//call super for frame
this.setSize(new Dimension(500, 300));
this.setBackground(Color.WHITE);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
UserPanel p = new UserPanel(this);
//Use border layout to make p fit the whole frame
getContentPane().setLayout(new BorderLayout());
getContentPane().add(p, BorderLayout.CENTER);
}
public class UserPanel extends JPanel implements ActionListener {
private Timer time = new Timer(1, this);
private UI myFrame;
public UserPanel(UI myFrame) {
this.myFrame = myFrame;
this.setSize(myFrame.getSize());
time.start();
}
#Override
public void actionPerformed(ActionEvent e) {
repaint();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
System.out.println("painting");
g.setColor(Color.BLACK);
g.fillRect(this.getWidth() / 2, this.getHeight() / 2, 50, 50);
}
}
}
Don't ignore the SwingUtilities.invokeLater.
While using Swing in java, I am trying to move a circle slowly from a starting position to an end position when clicking a button. However, I can't see the circle moving. It just moves from start to end in an instant.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class MyApp {
private int x = 10;
private int y = 10;
private JFrame f;
private MyDraw m;
private JButton b;
public void go() {
f = new JFrame("Moving circle");
b = new JButton("click me to move circle");
m = new MyDraw();
f.add(BorderLayout.SOUTH, b);
f.add(BorderLayout.CENTER, m);
f.setSize(500, 500);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
b.addActionListener(new Bute());
}
public static void main(String[] args) {
MyApp m = new MyApp();
m.go();
}
private class Bute implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
for (int i = 0; i < 150; i++) {
++x;
++y;
m.repaint();
Thread.sleep(50);
}
}
}
private class MyDraw extends JPanel {
#Override
public void paintComponent(Graphics g) {
g.setColor(Color.white);
g.fillRect(0, 0, 500, 500);
g.setColor(Color.red);
g.fillOval(x, y, 40, 40);
}
}
}
I think the problem is with the action listener because when I'm doing it without using button it is working. Any suggestions?
As Andrew Thompson said, calling Thread.sleep() without defining a second thread freezes everything, so the solution is to define and run another thread like so:
class Bute implements ActionListener, Runnable {
//let class implement Runnable interface
Thread t; // define 2nd thread
public void actionPerformed(ActionEvent e) {
t = new Thread(this); //start a new thread
t.start();
}
#Override //override our thread's run() method to do what we want
public void run() { //this is after some java-internal init stuff called by start()
//b.setEnabled(false);
for (int i = 0; i < 150; i++) {
x++;
y++;
m.repaint();
try {
Thread.sleep(50); //let the 2nd thread sleep
} catch (InterruptedException iEx) {
iEx.printStackTrace();
}
}
//b.setEnabled(true);
}
}
The only problem with this solution is that pressing the button multiple times will speed up the circle, but this can be fixed by making the button unclickable during the animation via b.setEnabled(true/false). Not the best solution but it works.
As said in the comments and another answer, don't block the EDT. Thead.sleep(...) will block it, so you have two options:
Create and manage your own (new) thread.
Use a Swing Timer
In this answer I'll be using a Swing Timer, since it's easier to use. I also changed the paintComponent method to use the Shape API and change the button text to start and stop accordingly as well as reusing the same ActionListener for the button and the timer:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class MovingCircle {
private JFrame frame;
private CustomCircle circle;
private Timer timer;
private JButton button;
public static void main(String[] args) {
SwingUtilities.invokeLater(new MovingCircle()::createAndShowGui);
}
private void createAndShowGui() {
frame = new JFrame(this.getClass().getSimpleName());
circle = new CustomCircle(Color.RED);
timer = new Timer(100, listener);
button = new JButton("Start");
button.addActionListener(listener);
circle.setBackground(Color.WHITE);
frame.add(circle);
frame.add(button, BorderLayout.SOUTH);
frame.pack();
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
private ActionListener listener = (e -> {
if (!timer.isRunning()) {
timer.start();
button.setText("Stop");
} else {
if (e.getSource().equals(button)) {
timer.stop();
button.setText("Start");
}
}
circle.move(1, 1);
});
#SuppressWarnings("serial")
class CustomCircle extends JPanel {
private Color color;
private int circleX;
private int circleY;
public CustomCircle(Color color) {
this.color = color;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(color);
g2d.fill(new Ellipse2D.Double(circleX, circleY, 50, 50));
}
#Override
public Dimension preferredSize() {
return new Dimension(100, 100);
}
public void move(int xGap, int yGap) {
circleX += xGap;
circleY += yGap;
revalidate();
repaint();
}
public int getCircleX() {
return circleX;
}
public void setCircleX(int circleX) {
this.circleX = circleX;
}
public int getCircleY() {
return circleY;
}
public void setCircleY(int circleY) {
this.circleY = circleY;
}
}
}
I'm sorry, I can't post a GIF as I wanted but this example runs as expected.
I have a main class that holds a Array List of objects and has a Swing timer that repaints a separate JFrame Class. Despite the repaint method being called, the screen isn't being updated.
What's supposed to happen:
When any key is pressed, the x location of two objects are updated. A swing timer that ticks every half second calls the repaint method. The images are then redrawn in their updated locations.
From what I can tell the location of the images are being updated, as when I minimize and reopen the JFrame window, the images have moved.
I've tried changing the intervals at which the timer runs, moving the swing timer to the JFrame class, putting the repaint(); in a thread. However I still don't know why repaint(); isn't updating the screen.
Here's a sketch of my code:
JFrame class
public class testingGround extends JPanel {
private Image image;
private qwq getter = new qwq();
protected Keyboard keyboard = new Keyboard(); //KeyListener, imported from Keyboard class
private JFrame frame = new JFrame(); //JFrame variable
public void createGUI(){
testingGround panel = new testingGround(); // Creating a new JPanel, for objects to be drawn on
JFrame frame = new JFrame("Game"); //Creating a new JFrame called frame
frame.setSize(600, 600); //Setting the size of the JFrame frame to be 600x600
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);// Setting so the frame exits on close
frame.setVisible(true);
frame.add(panel);// Adding the JPanel panel to the JFrame frame, so it's visible
frame.addKeyListener(keyboard); //Adding a KeyListener to detect users input in the JFrame frame
}
public Frame getFrame() { //Constructor class to get JFrame in other classes
return frame;
}
public void initComponents(){
ImageIcon imageIcon = new ImageIcon("C:/Users/destr/workspace/GameWIP3/bin/A2.png");
image =imageIcon.getImage();
}
protected void paintComponent (Graphics g){//Drawing
super.paintComponent(g);
initComponents();
for (int i=0;i<getter.getFListSize();i++)
{
System.out.print("paint called");
g.drawImage(image, getter.getFighter(i).getFighterX(), getter.getFighter(i).getFighterY(), null);
}
}
}
Keyboard Class
public class Keyboard implements KeyListener {
private qwq getter = new qwq();
#Override
public void keyPressed(KeyEvent e) {
getter.getFighter(0).setFighterX(500);;
getter.getFighter(1).setFighterX(200);;
System.out.print("Updated the x value of two");
}
Main Class
public class qwq implements ActionListener {
private static ArrayList<Fighter> FighterList = new ArrayList<Fighter>(); // ArrayList // of // Fighters
Timer timer = new Timer(500, this);
private static testingGround GUI = new testingGround();
public qwq() {
timer.start();
}
public Fighter getFighter(int i) {
return FighterList.get(i); // To retrieve a fighter from the Array List
}
public void addFighter(Fighter i) {
FighterList.add(i); // To add a fighter to the Array list
}
public int getFListSize() {
return FighterList.size();
}
public static void main(String[] args) {
GUI.createGUI();
GUI.getFrame();
FighterList.add(new Fighter(0, 0));
FighterList.add(new Fighter(20, 50));
}
#Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == timer) {
GUI.repaint();
}
}
}
Object Class
public class Fighter {
private int fighterX;
private int fighterY;
public Fighter ( int fighterX, int fighterY) {
this.setFighterX(fighterX);
this.setFighterY (fighterY);
}
public int getFighterX (){
return fighterX; //method to get x coordinate of a fighter
}
public int getFighterY (){
return fighterY;//method to get y coordinate of a fighter
}
public void setFighterX(int fighterX) {
this.fighterX = fighterX; //method to set the x coordinate of a fighter
}
public void setFighterY(int fighterY) {
this.fighterY = fighterY; //method to set the y coordinate of a fighter
}
}
You are creating multiple instances of qwq, in testingGround and Keyboard, these having nothing to do with each other and only mean that you are now creating a new Timer each time you create a new instance of it
In your testingGround#createGUI you create a new instance of testingGround, which gets added to a new instance of a JFrame, so the reference that qwq has to testingGround has nothing to do with the instance which is actually on the screen!? Further more, you create a new instance of JFrame in createGUI which has no relationship to the instance of JFrame you return from getFrame!?
OO is all about responsibilities, whose responsible for what. For example, you testingGround class shouldn't be making frames, it's not it's responsibility. It should be responsible for rendering the current state of the game.
For example...
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Qwq {
public static void main(String[] args) {
new Qwq();
}
public Qwq() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestingGround());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class Keyboard extends KeyAdapter {
private List<Fighter> fighterList;
public Keyboard(List<Fighter> fighterList) {
this.fighterList = fighterList;
}
#Override
public void keyPressed(KeyEvent e) {
fighterList.get(0).setFighterX(500);
fighterList.get(1).setFighterX(200);
System.out.print("Updated the x value of two");
}
}
public class TestingGround extends JPanel {
private ArrayList<Fighter> fighterList = new ArrayList<>();
private Image image;
private Keyboard keyboard;
public TestingGround() {
loadImages();
addKeyListener(new Keyboard(Collections.unmodifiableList(fighterList)));
Timer timer = new Timer(500, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
repaint();
}
});
timer.start();
}
public void loadImages() {
ImageIcon imageIcon = new ImageIcon("C:/Users/destr/workspace/GameWIP3/bin/A2.png");
image = imageIcon.getImage();
}
protected void paintComponent(Graphics g) {//Drawing
super.paintComponent(g);
for (Fighter fighter : fighterList) {
System.out.print("testing");
g.drawImage(image, fighter.getFighterX(), fighter.getFighterY(), null);
}
}
}
public static class Fighter {
private int fighterX;
private int fighterY;
public Fighter(int fighterX, int fighterY) {
this.setFighterX(fighterX);
this.setFighterY(fighterY);
}
public int getFighterX() {
return fighterX; //method to get x coordinate of a fighter
}
public int getFighterY() {
return fighterY;//method to get y coordinate of a fighter
}
public void setFighterX(int fighterX) {
this.fighterX = fighterX; //method to set the x coordinate of a fighter
}
public void setFighterY(int fighterY) {
this.fighterY = fighterY; //method to set the y coordinate of a fighter
}
}
}
Now, it would easy to have the Timer separate, but I was to lazy
I made the applet Bouncing Ball and in the class Ball.java I made inner class TimerListener with method repaint(), and when I run the applet, instead of repaint the ball, java paint the ball again and again(not delete, and then paint).
here is my code for the class Ball.java
import javax.swing.Timer;
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
public class Ball extends JPanel {
private int delay = 10;
Timer timer=new Timer(delay, new TimerListener());
private int x=0;
private int y=0;
private int dx=20;
private int dy=20;
private int radius=5;
private class TimerListener implements ActionListener{
public void actionPerformed(ActionEvent e) {
repaint();
}
}
public void paintComponent(Graphics g){
g.setColor(Color.red);
if(x<radius) dx=Math.abs(dx);
if(x>(getWidth()-radius)) dx=-Math.abs(dx);
if(y>(getHeight()-radius)) dy=-Math.abs(dy);
if(y<radius) dy=Math.abs(dy);
x+=dx;
y+=dy;
g.fillOval(x-radius, y-radius, radius*2, radius*2);
}
public void suspend(){
timer.stop();
}
public void resume(){
timer.start();
}
public void setDelay(int delay){
this.delay=delay;
timer.setDelay(delay);
}
}
here is my code for class BallControl.java
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
public class BallControl extends JPanel{
private Ball ball = new Ball();
private JButton jbtSuspend = new JButton("Suspend");
private JButton jbtResume = new JButton("Resume");
private JScrollBar jsbDelay = new JScrollBar();
public BallControl(){
JPanel panel = new JPanel();
panel.add(jbtSuspend);
panel.add(jbtResume);
//ball.setBorder(new javax.swing.border.LineBorder(Color.red));
jsbDelay.setOrientation(JScrollBar.HORIZONTAL);
ball.setDelay(jsbDelay.getMaximum());
setLayout(new BorderLayout());
add(jsbDelay, BorderLayout.NORTH);
add(ball, BorderLayout.CENTER);
add(panel, BorderLayout.SOUTH);
jbtSuspend.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
ball.suspend();
}
});
jbtResume.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
ball.resume();
}
});
jsbDelay.addAdjustmentListener(new AdjustmentListener() {
public void adjustmentValueChanged(AdjustmentEvent e) {
ball.setDelay(jsbDelay.getMaximum() - e.getValue());
}
});
}
}
Isn't the Timer supposed to also change the Ball object's position? In other words, isn't it supposed to change its x and y values? i.e.,
private class TimerListener implements ActionListener{
public void actionPerformed(ActionEvent e) {
// first change x and y here *****
repaint();
}
}
Else, how is the ball supposed to move much less bounce?
You seem to have this change position code in your paintComponent(...) method, and that is not good since you don't have full control over when or even if this method gets called. For this reason, program logic and code that changes this object's state does not belong inside of that method.
Also, your paintComponent(...) method override needs a call to to the super's paintComponent(...) method on its first line so that the old ball can be erased before a new ball is drawn.
I have a simple class that draws a line when mouse dragging or a dot when mouse pressing(releasing).
When I minimize the application and then restore it, the content of the window disappears except the last dot (pixel). I understand that the method super.paint(g) repaints the background every time the window changes, but the result seems to be the same whether I use it or not. The difference between the two of them is that when I don't use it there's more than a pixel painted on the window, but not all my painting. How can I fix this?
Here is the class.
package painting;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import javax.swing.JFrame;
import javax.swing.JPanel;
class CustomCanvas extends Canvas{
Point oldLocation= new Point(10, 10);
Point location= new Point(10, 10);
Dimension dimension = new Dimension(2, 2);
CustomCanvas(Dimension dimension){
this.dimension = dimension;
this.init();
addListeners();
}
private void init(){
oldLocation= new Point(0, 0);
location= new Point(0, 0);
}
public void paintLine(){
if ((location.x!=oldLocation.x) || (location.y!=oldLocation.y)) {
repaint(location.x,location.y,1,1);
}
}
private void addListeners(){
addMouseListener(new MouseAdapter(){
#Override
public void mousePressed(MouseEvent me){
oldLocation = location;
location = new Point(me.getX(), me.getY());
paintLine();
}
#Override
public void mouseReleased(MouseEvent me){
oldLocation = location;
location = new Point(me.getX(), me.getY());
paintLine();
}
});
addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseDragged(MouseEvent me){
oldLocation = location;
location = new Point(me.getX(), me.getY());
paintLine();
}
});
}
#Override
public void paint(Graphics g){
super.paint(g);
g.setColor(Color.red);
g.drawLine(location.x, location.y, oldLocation.x, oldLocation.y);
}
#Override
public Dimension getMinimumSize() {
return dimension;
}
#Override
public Dimension getPreferredSize() {
return dimension;
}
}
class CustomFrame extends JPanel {
JPanel displayPanel = new JPanel(new BorderLayout());
CustomCanvas canvas = new CustomCanvas(new Dimension(200, 200));
public CustomFrame(String titlu) {
canvas.setBackground(Color.white);
displayPanel.add(canvas, BorderLayout.CENTER);
this.add(displayPanel);
}
}
public class CustomCanvasFrame {
public static void main(String args[]) {
CustomFrame panel = new CustomFrame("Test Paint");
JFrame f = new JFrame();
f.add(panel);
f.pack();
SwingConsole.run(f, 700, 700);
}
}
You are not storing the state of the points you are drawing. When the panel is repainted, it only has information for the last point it drew.
Response to comment:
You would need to have a collection of Points, for instance ArrayList<Point> location = new ArrayList<Point>();
Then, in your listeners: location.add(new Point(me.getX(), me.getY()));
Finally, in paintLine():
for (Point location : locations) {
repaint(location.x,location.y,1,1);
}
The collection locations is usually referred to as a Display List. Most graphics programs use them.
Response to comment:
Yes, I expect so. I just tossed off an idea based on your code to give you a starting point. It is almost certainly a bad idea to do exactly as I have described.
Doesn't that mean I will draw all the points(instead of one) everytime I press or drag the mouse?
Yes, but #Dave's approach is perfectly satisfactory for thousands of nodes, as may be seen in GraphPanel. Beyond that, consider the flyweight pattern, as used by JTable renderers and illustrated here.
Addendum: Focusing on your AWTPainting questions, the variation below may illustrate the difference between System- and App-triggered Painting. As the mouse is dragged, repaint() invokes update(), which calls paint(); this is app-triggered. As you resize the window, only paint() is called (no red numbers are drawn); this is system-triggered. Note that there is a flicker when the mouse is released after resizing.
Flickering typically occurs when the entire component's background is cleared and redrawn:
4. If the component did not override update(), the default implementation of update() clears the component's background (if it's not a lightweight component) and simply calls paint().
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Panel;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.List;
public class AWTPainting {
public static void main(String args[]) {
CustomPanel panel = new CustomPanel();
Frame f = new Frame();
f.addWindowListener(new WindowAdapter() {
#Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
f.add(panel);
f.pack();
f.setVisible(true);
}
}
class CustomPanel extends Panel {
public CustomPanel() {
this.add(new CustomCanvas(new Dimension(320, 240)));
}
}
class CustomCanvas extends Canvas {
private MouseAdapter handler = new MouseHandler();
private List<Point> locations = new ArrayList<Point>();
private Point sentinel = new Point();
private Dimension dimension;
CustomCanvas(Dimension dimension) {
this.dimension = dimension;
this.setBackground(Color.white);
this.addMouseListener(handler);
this.addMouseMotionListener(handler);
this.locations.add(sentinel);
}
#Override
public void paint(Graphics g) {
g.setColor(Color.blue);
Point p1 = locations.get(0);
for (Point p2 : locations.subList(1, locations.size())) {
g.drawLine(p1.x, p1.y, p2.x, p2.y);
p1 = p2;
}
}
#Override
public void update(Graphics g) {
paint(g);
g.clearRect(0, getHeight() - 24, 50, 20); // to background
g.setColor(Color.red);
g.drawString(String.valueOf(locations.size()), 8, getHeight() - 8);
}
private class MouseHandler extends MouseAdapter {
#Override
public void mousePressed(MouseEvent e) {
if (locations.get(0) == sentinel) { // reference identity
locations.set(0, new Point(e.getX(), e.getY()));
}
}
#Override
public void mouseDragged(MouseEvent e) {
locations.add(new Point(e.getX(), e.getY()));
repaint();
}
}
#Override
public Dimension getPreferredSize() {
return dimension;
}
}
#Andrew, #Dave, #trashgod Hi,
I did some research on this and, finally, this is what I've got. Please correct me if I'm wrong. You cannot override paint() so you call repaint() everytime you need to do app-triggered painting.
Repaint() calls update() which its default behavior is to call paint().
update() is used for incremental painting; that explains the flickering screen when paint() was doing all the work, which practically means it was painting the whole image at every step.
However, my question is if I add "locationsAdded = 0" in the update method that means everytime I drag the mouse I paint the whole image(like in paint), so why doesn't it blink like before?
I've also read something about painting in swing and I didn't understand why update() is never invoked for swing. Can you explain me why?
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
class CustomCanvas extends Canvas{
ArrayList<Point> locations;
int locationsAdded;
Point oldLocation;
Point location;
Dimension dimension;
CustomCanvas(Dimension dimension){
locations = new ArrayList<>();
this.dimension = dimension;
this.init();
addListeners();
}
private void init(){
oldLocation= new Point(0, 0);
location= new Point(0, 0);
}
public void paintLine(Graphics g, int x){
Point p1 = (Point)locations.get(x);
Point p2 = (Point)locations.get(x+1);
g.drawLine(p1.x, p1.y, p2.x, p2.y);
locationsAdded++;
}
#Override
public void paint(Graphics g){
locationsAdded = 0;
g.setColor(Color.red);
for(int i = locationsAdded; i < locations.size()-1; i++){
paintLine(g, i);
}
}
public void update(Graphics g) {
//locationsAdded = 0;
for (int i = locationsAdded; i < locations.size()-1; i++) {
paintLine(g, i);
}
}
private void addListeners(){
addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseDragged(MouseEvent me){
oldLocation = location;
location = new Point(me.getX(), me.getY());
locations.add(location);
repaint();
}
});
}
#Override
public Dimension getMinimumSize() {
return dimension;
}
#Override
public Dimension getPreferredSize() {
return dimension;
}
}
class CustomFrame extends Panel {
Panel displayPanel = new Panel(new BorderLayout());
CustomCanvas canvas = new CustomCanvas(new Dimension(700, 700));
public CustomFrame(String titlu) {
canvas.setBackground(Color.white);
displayPanel.add(canvas, BorderLayout.CENTER);
this.add(displayPanel);
}
}
public class AWTPainting {
public static void main(String args[]) {
CustomFrame panel = new CustomFrame("Test Paint");
Frame f = new Frame();
f.add(panel);
f.pack();
f.setSize(700,700);
f.show();
}
}
set your layout to Null layout