Repainting-Thread doesn't repaint Inner-Class JPanel - java

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.

Related

Moving Graphics in JPanel

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();
}
}

i can't see circle moving

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.

JLabel not showing up no matter what I do

I have tried a ton of different things to try to get the JLabel to show but I don't understand why it is not working. I have tried resizing it, though that is not what i want to do, I have tried other classes, but I would prefer to stick with this one, and it is starting to get really frustrating. If you have any ideas please help. But please try to keep them simple and explain very clearly as I am still quite new to java. I have only been going for about three or four months. Here is my code:
package com.thefallenpaladin;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
/**
* Created by darkp_000 on 11/4/2015.
*/
#SuppressWarnings("serial")
public class Game extends JPanel implements KeyListener,MouseListener {
public boolean mainMenu = true;
public int winWidth = 700; //Window Stats
public int winHeight = 600;
public int buttonOneX = 60; // Button Stats
public int buttonOneY = 240;
public int buttonOneW = 100;
public int buttonOneH = 75;
public boolean buttonOne = false;
public int mouseX; // not set because it is set in mouseClicked
public int mouseY;
public static void main(String[] args) {
Game game = new Game();
JFrame window = new JFrame("I hate this");
JLabel onePlayer = new JLabel();
onePlayer.setLocation(0,0/*game.buttonOneX + game.buttonOneX/2,game.buttonOneY + game.buttonOneY/2*/);
window.add(game);
window.setFocusable(true);
window.setResizable(false);
window.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
window.setSize(700,600); //TODO
window.setVisible(true);
game.requestFocusInWindow();
game.add(onePlayer);
game.addKeyListener(game);
game.addMouseListener(game);
window.setLocationRelativeTo(null);
while(true) { // Main Game loop
onePlayer.setText("One Player");
game.repaint();
game.customUpdate();
}
}
public void customUpdate() {
if(mouseX > buttonOneX && mouseX < buttonOneX+buttonOneX && mouseY > buttonOneY && mouseY < buttonOneY+buttonOneY && mainMenu) {
buttonOne = true;
System.out.print("Starting Game");
}
}
public void paint(Graphics g) {
if(mainMenu) {
g.setColor(Color.CYAN); // Set main menu
g.fillRect(0,0,winWidth,winHeight);
g.setColor(Color.GREEN);
g.fillRect(buttonOneX,buttonOneY,buttonOneW,buttonOneH);
}
if(buttonOne) {
mainMenu = false;
g.setColor(Color.GREEN);
g.fillRect(0,0,winWidth,winHeight);
}
}
public void keyTyped(KeyEvent e) {
}
public void keyPressed(KeyEvent e) {
System.out.println(e);
}
public void keyReleased(KeyEvent e) {
}
public void mouseClicked(MouseEvent e) {
}
public void mousePressed(MouseEvent e) {
// System.out.println(e);
mouseX = e.getX();
mouseY = e.getY();
}
public void mouseReleased(MouseEvent e) {
}
public void mouseEntered(MouseEvent e) {
}
public void mouseExited(MouseEvent e) {
}
}
Okay so you've made a couple of basic mistakes...
First, JLabel onePlayer = new JLabel(); creates an empty label, with no size (0x0) and since labels are transparent by default, you'd not see it
Next, you've overridden paint of a top level container (JFrame), but failed to honor the paint chain effectively preventing any of the child components from ever getting painted
public void paint(Graphics g) {
if (mainMenu) {
g.setColor(Color.CYAN); // Set main menu
g.fillRect(0, 0, winWidth, winHeight);
g.setColor(Color.GREEN);
g.fillRect(buttonOneX, buttonOneY, buttonOneW, buttonOneH);
}
if (buttonOne) {
mainMenu = false;
g.setColor(Color.GREEN);
g.fillRect(0, 0, winWidth, winHeight);
}
}
So, if I remove your paint method and change JLabel onePlayer = new JLabel(); to JLabel onePlayer = new JLabel("I'm a label"); I get this output...
Also...
while (true) { // Main Game loop
onePlayer.setText("One Player");
game.repaint();
game.customUpdate();
}
has the potential to try screw up your program, you have no guarantee's in what thread your main method is been called and you should not make assumptions.
Start by creating a custom component, extending from something like JPanel and override it's paintComponent method, place your custom painting there. In fact, you should have a panel for each state of your game (menu, running, settings, etc).
Add these to your frame (probably using a CardLayout to enable you to easily switch between them)
Use either a Thread or Swing Timer as a main game loop, one which you create explicitly.
Have a look at Painting in AWT and Swing, Performing Custom Painting, How to Use CardLayout and How to use Swing Timers for some more details
As a "conceptual" example...
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class AwesomeGame {
public static void main(String[] args) {
new AwesomeGame();
}
public AwesomeGame() {
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 ContentPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public interface NavigationController {
public void letsPlay();
}
public class ContentPane extends JPanel implements NavigationController {
private CardLayout cardLayout;
private GamePane gamePane;
public ContentPane() {
cardLayout = new CardLayout();
setLayout(cardLayout);
add(new MenuPane(this), "TheMenu");
add((gamePane = new GamePane()), "TheGame");
cardLayout.show(this, "TheMenu");
}
#Override
public void letsPlay() {
cardLayout.show(this, "TheGame");
gamePane.play();
}
}
public class MenuPane extends JPanel {
public MenuPane(NavigationController navigationController) {
JLabel label = new JLabel("My Super Dupa Awesome Game!");
label.setFont(label.getFont().deriveFont(Font.BOLD, 48));
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
add(label, gbc);
JButton play = new JButton("Play Now!");
play.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
navigationController.letsPlay();
}
});
add(play, gbc);
setBackground(Color.GREEN);
}
}
public class GamePane extends JPanel {
public GamePane() {
setBackground(Color.BLUE);
}
public void play() {
Timer timer = new Timer(500, new ActionListener() {
int count;
#Override
public void actionPerformed(ActionEvent e) {
count++;
if (count % 2 == 0) {
setForeground(Color.BLACK);
} else {
setForeground(Color.RED);
}
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
String text = "I bet you're blowen away by it's awesomness!";
FontMetrics fm = g2d.getFontMetrics();
int x = (getWidth() - fm.stringWidth(text)) / 2;
int y = ((getHeight() - fm.getHeight()) / 2) + fm.getAscent();
g2d.drawString(text, x, y);
g2d.dispose();
}
}
}

How does repaint() method behave inside an infinite for loop?

I have this code,
import java.awt.*;
import java.applet.*;
public class FirstApplet extends Applet
{
int len;
char ch;
String msg="Hello World ";
public void init()
{
setBackground(Color.CYAN);
setForeground(Color.WHITE);
}
public void start()
{
System.out.println("Inside Start");
repaint();
}
public void paint(Graphics g)
{
System.out.println("Inside paint");
g.drawString(msg,200,200);
}
}
It outputs a CYAN coloured background with Hello World on it.And on the console(cmd),it outputs-
Inside Start
Inside paint
Now if I modify the code to this-
import java.awt.*;
import java.applet.*;
public class FirstApplet extends Applet
{
String msg="Hello World ";
int len;
char ch;
public void init()
{
setBackground(Color.CYAN);
setForeground(Color.WHITE);
}
public void start()
{
System.out.println("Inside Start");
for(;;)
{
repaint();
}
}
public void paint(Graphics g)
{
System.out.println("Inside paint");
g.drawString(msg,200,200);
}
}
It outputs a white coloured screen with no text on it,and on the console it just outputs-
Inside Start
I am unable to understand the output of second program,Although I am calling the repaint() inside the for loop every time yet why the colour of the applet window is not changing to CYAN colour and why its not printing "Inside paint" on the console?Can somebody please help me out.
You're tying up the GUI's event thread with your infinite loop, so that although repaint() is being called, the GUI's event thread is unable to act on it. Consider using a Swing Timer or a background thread instead.
For example, and continuing with your 1890's Applet example:
import java.applet.Applet;
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.*;
public class PaintEg extends Applet {
String msg = "Hello World ";
int len;
char ch;
public void init() {
setBackground(Color.CYAN);
setForeground(Color.WHITE);
}
public void start() {
System.out.println("Inside Start");
new Thread(new Runnable() {
public void run() {
for (;;) {
repaint();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
public void paint(Graphics g) {
System.out.println("Inside paint");
g.drawString(msg, 10, 20);
}
}
Better maybe is a Swing example that uses a Swing Timer and performs basic animation:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class PaintEg2 extends JPanel {
private static final int PREF_W = 400;
private static final int PREF_H = PREF_W;
private static final int TIMER_DELAY = 30;
private String msg = "Hello World ";
private int msgX = 0;
private int msgY = 0;
public PaintEg2() {
setBackground(Color.CYAN);
setForeground(Color.WHITE);
setFont(new Font(Font.SANS_SERIF, Font.BOLD, 20));
new Timer(TIMER_DELAY, new TimerListener()).start();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawString(msg, msgX, msgY);
}
#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) {
msgX++;
msgY++;
repaint();
}
}
private static void createAndShowGui() {
PaintEg2 mainPanel = new PaintEg2();
JFrame frame = new JFrame("PaintEg2");
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();
}
});
}
}
Note that in your code repaint() is being called and is being executed, but the paint manager is unable to act on this because it does so on the GUI's event thread. If the GUI's event thread is tied up, no painting can be done.
For more on how painting is done in Swing and AWT, please read Painting in AWT and Swing

Repaint JPanel from inside loop

Is it possible to repaint a JPanel from within a loop in another object? I have a JFrame that consists of a JPanel (DrawPanel) and a SA object. I would like to update/repaint the JPanel during the while loop in this SA object. I started a new thread, but still panel.repaint() does not execute.
public class Mainform extends JFrame {
private DrawPanel DrawPanel;
public static void main(String[] args) {
DrawPanel panel = new DrawPanel();
SA sa = new SA(panel);
Thread t = new Thread(sa);
t.start();
//...
}
}
public class DrawPanel extends JPanel implements MouseMotionListener, MouseListener {
public DrawPanel() {
super();
setBackground(Color.WHITE);
addMouseWheelListener(this);
addMouseListener(this);
addMouseMotionListener(this);
}
//...
}
public class SA implements Runnable {
private DrawPanel panel;
public SA(DrawPanel p) {
this.panel = p;
init();
}
public void run() {
while (true) {
//...
panel.repaint();
}
}
}
EDIT: run is public
The basic answer is "yes".
This assumes that the component you are trying to repaint is
Added to a container
That container is attached to some kind of native peer (ie a window)
That window is visible.
The RepaintManager is generally smart enough to know not to waste time painting something that isn't displayable.
The following example is rather basic, but will increment a counter within the paintComponent of a JPanel each time it is called. The Runnable, which is attached to a Thread, will update every second...
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class RepaintTest {
public static void main(String[] args) {
new RepaintTest();
}
public RepaintTest() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
TestPane tp = new TestPane();
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(tp);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
Thread thread = new Thread(new Repainter(tp));
thread.setDaemon(true);
thread.start();
}
});
}
public class Repainter implements Runnable {
private JPanel panel;
public Repainter(JPanel panel) {
this.panel = panel;
}
#Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
}
panel.repaint();
}
}
}
public class TestPane extends JPanel {
private int repaints = 0;
public TestPane() {
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics g2d = (Graphics2D) g.create();
repaints++;
FontMetrics fm = g2d.getFontMetrics();
String text = Integer.toString(repaints);
int x = (getWidth() - fm.stringWidth(text)) / 2;
int y = ((getHeight() - fm.getHeight()) / 2) + fm.getAscent();
g2d.drawString(text, x, y);
g2d.dispose();
}
}
}

Categories