I'm trying to play around with JFrame/JPanels repaint(); and so forth, but when I start a thread, and call repaint via run while true, it only spits out a line of System.out.println("as"); which I put in place to check if loop was running.
So the question is:
Why is my drawings disappearing when calling repaint in a loop?
(It seems only a JFrame with the canvas_width/height is showing up, no panels etc.)
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setSize(CANVAS_WIDTH, CANVAS_HEIGHT);
frame.setPreferredSize(new Dimension(CANVAS_WIDTH, CANVAS_HEIGHT));
frame.setVisible(true);
frame.setResizable(false);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel p = new JPanel(new GridLayout());
frame.getContentPane().add(p);
Something s = new Something();
p.add(s);
p.setBackground(Color.black);
frame.pack();
}
And the something class:
public class Something extends JPanel implements Runnable {
public Something(){
Thread t = new Thread();
t.start();
run();
}
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
g.setColor(Color.cyan);
g.fillRect(0, 0, getWidth()-150, getHeight()-100);
g.setColor(Color.BLACK);
g.fillOval(10, 10, 25, 25);
}
#Override
public void run() {
while(true){
repaint();
System.out.println("as");
try {
Thread.sleep(1);
} catch (InterruptedException e){}
}
}
}
Any help regarding the contentpane is appreciated, since, I'm not sure this is done correctly.
Instead of calling Thread.sleep(n) in your Thread, implement a Swing Timer for repeating tasks. That ensures that repaint() is called on the Event Dispatch Thread.
See Concurrency in Swing for more details.
Also, repainting every 1 millisecond is being very optimistic.
Working SSCCE E.G. (Note this version actually changes the co-ords of the resulting paint operations, just so we know something is happening!
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Something extends JPanel {
static final int CANVAS_WIDTH = 400;
static final int CANVAS_HEIGHT = 100;
private int xDelta = 0;
public Something(){
ActionListener animater = new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
repaint();
}
};
Timer t = new Timer(10,animater);
t.start();
}
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
g.setColor(Color.cyan);
g.fillRect(0, 0, getWidth()-(xDelta--), getHeight()-100);
g.setColor(Color.BLACK);
g.fillOval(xDelta, 10, 25, 25);
if (xDelta<0) {
xDelta = CANVAS_WIDTH;
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(CANVAS_WIDTH, CANVAS_HEIGHT);
}
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel p = new JPanel(new GridLayout());
frame.getContentPane().add(p);
Something s = new Something();
p.add(s);
p.setBackground(Color.black);
frame.pack();
frame.setResizable(false);
frame.setVisible(true);
}
}
Starting a Runnable in a new Thread is done with java.lang.Thread.Thread( Runnable ) constructor.
Calling a method of a GUI component outside the Swing event loop is not a good practice, use a Swing timer instead.
Your code becomes:
public static void main(String[] args) {
...
new Timer( 40, new ActionListener() {
public void actionPerformed(ActionEvent evt) {
repaint
}
}).start();
...
}
Related
So, I want to draw a ball whenever the user presses JButton. My problem is that the ball is not visible after I call revalidate() and repaint(). Am I forgetting something?
Here is my code, I have another class for the queue and stack that's why I extended Queueue. My buttons are visible and I know they work when I press them, it's jst that the ball doesn't show up on the screen. Earlier I tried to have my void paintComponent in my ActionListener but it would not work. I then wanted to just call the method but because of the Graphics g parameter it would not work as well. So I saw similar issue where someone suggested to use boolean
public class Main extends Queueue {
static boolean clicked = false;
public void paintComponent(Graphics g) {
if(clicked) {
g.setColor(Color.BLACK);
g.fillOval(60, 60, 15, 15);
}
}
public static void main (String[] args) {
Queueue qq = new Queueue();
JFrame f = new JFrame();
JPanel p = new JPanel();
JButton b1 = new JButton("Queue");
JButton b2 = new JButton("Stack");
JButton b3 = new JButton("Ball");
f.setSize(700, 500);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
p.setBackground(Color.RED);
p.add(b1);
p.add(b2);
p.add(b3);
f.add(p);
f.setVisible(true);
b1.addActionListener(new ActionListener(){
#Override
public void actionPerformed(ActionEvent arg0) {
qq.Q();
}
});
b2.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
qq.S();
}
});
b3.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
clicked = true;
f.revalidate();
f.repaint();
}
});
You had some syntax errors which I corrected. I also trimmed it down to more clearly demonstrate the procedure. Here is a working version that just paints the ball.
public class Main extends JPanel {
static boolean clicked = false;
public static void main(String[] args) {
JFrame f = new JFrame();
Main m = new Main();
JButton b3 = new JButton("Ball");
f.setSize(700, 500);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(m);
m.add(b3);
f.setVisible(true);
b3.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
clicked = true;
f.repaint();
}
});
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
if (clicked) {
g.setColor(Color.BLACK);
g.fillOval(60, 60, 15, 15);
}
}
}
Your class should extend JPanel
Add it to the JFrame.
Add the JButton to Main instance
and in paintComponent call super.paintComponent(g) first.
Check out The Java Tutorials for more information on how to paint.
In response to your comments, the following should work. The main issue is that you need to have the paintComponent inside a JPanel, not as a method in Queueue.
public class Main extends Queueue {
static boolean clicked = false;
public static void main(String[] args) {
JFrame f = new JFrame();
Main m = new Main();
JPanel panel = new JPanel() {
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
if (clicked) {
g.setColor(Color.BLACK);
g.fillOval(60, 60, 15, 15);
}
}
};
JButton b3 = new JButton("Ball");
f.setSize(700, 500);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(panel);
panel.add(b3);
f.setVisible(true);
b3.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
clicked = true;
f.repaint();
}
});
}
}
I was drawing my game on Canvas, all was god, but I changed it into a JPanel, but now its not working correctly, here are the codes, you can just copy them and you'll see where is the problem (I have a menu and after clicking on the button it should create new thread and there i want to draw, the problem in JPanel is that the button is able to see, its blinking and i can press it, in canvas it was fine, there wasn't any button). I solved it, that after clicking on the button I setted him unvisible (button.setVisible(false)), but these codes are just examples and in my game I have more buttons, so its not practical because I need them to see after the game ends. I think I just forgot an important method in JPanel, thx for help, codes:
//Main class representing menu
public class Sandbox extends JFrame{
Panel p = new Panel();
public static void main(String[] args) {
new Sandbox();
}
public Sandbox() {
setLayout(null);
setPreferredSize(new Dimension(200, 200));
setDefaultCloseOperation(EXIT_ON_CLOSE);
setResizable(false);
final JButton but = new JButton("Button");
but.setBounds(0, 0, 50, 50);
but.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
p.start();
}
});
add(p);
add(but);
pack();
setVisible(true);
}
}
//Drawing on Canvas -> working well
public class Panel extends Canvas implements Runnable {
Thread t;
public Panel() {
setSize(new Dimension(200, 200));
setVisible(false);
}
public void start() {
t = new Thread(this);
t.start();
setVisible(true);
}
public void draw() {
BufferStrategy b = getBufferStrategy();
if(b == null) {
createBufferStrategy(3);
return;
}
Graphics g = b.getDrawGraphics();
g.setColor(Color.red);
g.fillRect(0, 0, 200, 200);
g.dispose();
b.show();
}
#Override
public void run() {
while(!t.isInterrupted()) {
try {
draw();
t.sleep(200);
} catch (InterruptedException ex) {}
}
}
}
//Drawing on JPanel -> here i can press the button after first click on it
public class Panel extends JPanel implements Runnable {
Thread t;
public Panel() {
setSize(new Dimension(200, 200));
setVisible(false);
}
public void start() {
t = new Thread(this);
t.start();
setVisible(true);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.red);
g.fillRect(0, 0, 200, 200);
}
#Override
public void run() {
while(!t.isInterrupted()) {
try {
repaint();
t.sleep(200);
} catch (InterruptedException ex) {}
}
}
}
Not sure I understand exactly what you are trying to do but you do have a couple of problems:
the setVisible(true) method should be invoked AFTER you have added all the components to the frame and packed the frame.
by default the content pane of a JFrame uses a BorderLayout. You code is adding two components to the CENTER of the BorderLayout, but a BorderLayout only allows you to add one component to the CENTER, so only the last component added will be displayed.
I am trying to set background color of my screen to green.
My code so far:
package game;
import java.awt.*;
import javax.swing.JFrame;
public class Game extends JFrame {
public static void main(String[] args) {
DisplayMode dm = new DisplayMode(800, 600, 16, DisplayMode.REFRESH_RATE_UNKNOWN);
Game g = new Game();
g.run(dm);
}
public void run(DisplayMode dm) {
setBackground(Color.GREEN);
setForeground(Color.WHITE);
setFont(new Font("arial", Font.PLAIN, 24));
Screen s = new Screen();
try {
s.setFullScreen(dm, this);
try {
Thread.sleep(5000);
} catch (Exception E) {
}
} finally {
s.restoreScreen();
}
}
#Override
public void paint(Graphics g){
g.drawString("Check Screen", 200, 200);
}
}
When I run the program, I get this:
Screen should be green according to line:
setBackground(Color.GREEN);
Why does the background is not being set to green when I run the program?
You need to add a call to super.paint (g); in your paint () method.
#Override
public void paint(Graphics g){
super.paint (g);
g.drawString("Check Screen", 200, 200);
}
This will ensure that the component will draw itself correctly, including the background color, then draw your text.
Generally, whole approach is very bad. Even if it works with getContentPane().setBackground(Color.GREEN) it won't probably work because you are calling a Thread.sleep(5000) on EDT (or you will have issues soon or later). Use proper component for repetitive task's (refreshing of your screen): Swing Timer.
Instead of overriding JFrame's paint method, it's far better to use JPanel and to override it's paintComponent method. So, something like this:
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class Game extends JFrame {
JFrame frame = new JFrame();
public Game() {
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new Panel());
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Game();
}
});
}
class Panel extends JPanel {
Timer timer;
Panel() {
setBackground(Color.BLACK);
setForeground(Color.WHITE);
refreshScreen();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setFont(new Font("arial", Font.PLAIN, 24));
g.drawString("Check Screen", 200, 200);
}
public void refreshScreen() {
timer = new Timer(0, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
repaint();
}
});
timer.setRepeats(true);
//Aprox. 60 FPS
timer.setDelay(17);
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(650, 480);
}
}
}
can someone take a look at my code below and tell me why, when I change the following two statements, I do not see a change on the rectangle that is painted. So if I change:
g.setColor(Color.black);
g.fillRect(l, w, 100, 100);
The program still prints a black rectangle with the same dimensions and in the same position that I first started with even though I change color to yellow or try to change the dimensions or location. I am BlueJ. The following is my full code:
import java.awt.*;
import javax.swing.*;
public class SwingPaintDemo2 extends JComponent {
public static boolean isWall = true;
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
private static void createAndShowGUI() {
//System.out.println("Created GUI on EDT? "+
//SwingUtilities.isEventDispatchThread());
JFrame f = new JFrame("Swing Paint Demo");
JPanel MyPanel = new JPanel();
MyPanel.setBorder(BorderFactory.createEmptyBorder(1000, 1000, 1000, 1000));
MyPanel.setPreferredSize(new Dimension(250, 200));
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new MyPanel());
f.pack();
f.setVisible(true);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
int l = 10;
int w = 10;
g.setColor(Color.black);
g.fillRect(l, w, 100, 100);
}
}
Any advice would be appreciated.
Your SSCCE doesnt compile where is MyPanel class or did you mean new SwingPaintDemo2()?
On the assumption you meant new SwingPaintDemo2():
The code does work just fine but the JFrame is sized very small:
because you dont give it any size and none of its components have a size as they do not have any components added to them, thus we must make the JComponent return a correct size so when we call pack() our JFrame is sized correctly
Solution
override getPreferredSize() of JComponent to return a width and height which fits all drawings.
Some suggestions though:
Dont extend JComponent rather extend JPanel
Here is an example (your code with above fixes implemented):
import java.awt.*;
import javax.swing.*;
public class SwingPaintDemo2 extends JPanel {
public static boolean isWall = true;
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
private static void createAndShowGUI() {
//System.out.println("Created GUI on EDT? "+
//SwingUtilities.isEventDispatchThread());
JFrame f = new JFrame("Swing Paint Demo");
JPanel MyPanel = new JPanel();
MyPanel.setBorder(BorderFactory.createEmptyBorder(1000, 1000, 1000, 1000));
MyPanel.setPreferredSize(new Dimension(250, 200));
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new SwingPaintDemo2());
f.pack();
f.setVisible(true);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
int l = 10;
int w = 10;
g.setColor(Color.black);
g.fillRect(l, w, 100, 100);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(150, 150);
}
}
I've hit a wall (in my brain) trying to update my board on button presses. Am I right in thinking that the GameBoard class is the one that needs to be repaint()ed?
GameBoard.java
public class GameBoard extends Panel {
static Compass compass = new Compass();
private static final long serialVersionUID = 1;
Graphics2D g2d;
static final Dimension WINDOW_SIZE = new Dimension(1150, 800);
public void boardMaker() throws Exception {
JFrame frame = new JFrame("Display image");
JPanel panel = new JPanel();
/* unimportant stuff
.....
*/
//
DieRoll roll = new DieRoll("Roll Dies");
roll.setC(compass);
roll.setG2D(g2d);
//
Button button = new Button("new");
button.setGameBoard(this);
JPanel buttonPanel = new JPanel();
buttonPanel.add(button);
buttonPanel.add(roll);
buttonPanel.setPreferredSize(new Dimension(200,100));
frame.getContentPane().add(buttonPanel, BorderLayout.NORTH);
//
frame.getContentPane().add(panel);
frame.setVisible(true);
}
public void paint(Graphics g) {
// not important I think
}
}
Button.java
public class Button extends JButton implements ActionListener {
private static final long serialVersionUID = 1L;
JPanel panel = new JPanel();
JFrame frame = new JFrame();
Compass c = new Compass();
GameBoard gb = new GameBoard();
Button(String text) {
this.setText(text);
this.addActionListener(this);
}
void setGameBoard(GameBoard gb) {
this.gb = gb;
}
#Override
public void actionPerformed(ActionEvent e) {
gb.g2d.setColor(Color.black);
gb.g2d.fillRect(100, 100, 100, 200);
gb.repaint();
}
}
This gives a null pointer exception. So any idea how to repaint my GameBoard? I'm not mad if I've to rewrite everything because of stupidity! ;)
Thanks
You have the wrong idea about how to draw in Java. Components like Panels draw themselves, and all drawing takes place on the UI thread.
Check out this tutorial: docs.oracle.com/javase/tutorial/2d/index.html
The article Painting in AWT and Swing may offer some perspective on application-triggered painting. The example below illustrates the principle. Note that setForeground() calls repaint() automatically because the foreground color is a bound property, but you can always call it yourself.
import java.awt.*;
import java.awt.event.*;
import java.util.Random;
import javax.swing.*;
public class SwingPaint {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame f = new JFrame();
final GamePanel gp = new GamePanel();
f.add(gp);
f.add(new JButton(new AbstractAction("Update") {
#Override
public void actionPerformed(ActionEvent e) {
gp.update();
}
}), BorderLayout.SOUTH);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
});
}
private static class GamePanel extends JPanel {
private static final Random r = new Random();
public GamePanel() {
this.setForeground(new Color(r.nextInt()));
}
#Override
public Dimension getPreferredSize() {
return new Dimension(320, 240);
}
public void update() {
this.setForeground(new Color(r.nextInt()));
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Dimension size = this.getSize();
int d = Math.min(size.width, size.height) - 10;
int x = (size.width - d) / 2;
int y = (size.height - d) / 2;
g.fillOval(x, y, d, d);
g.setColor(Color.blue);
g.drawOval(x, y, d, d);
}
}
}