I was given some demo code during class that worked on the Windows computer in the lab but doesn't work the same way on my 2010 MacBook, using Sierra.
Making the changes suggested in Java Graphics Not Displaying In OS X didn't fix my problem. I've also tried resizing the window, which changes the animation a bit -- it pops up intermittently after the resize. If I increase the Thread.sleep() time and resize, then the animation improves, but it still is choppy.
Why doesn't the code work on my MacBook and how can I get it to work?
The original code (which works on Windows 10 but not my Mac):
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class GraphicsTestA extends JFrame{
int x1 = 60;
int x2 = 150;
public static void main(String [] args){
GraphicsTestA gta = new GraphicsTestA();
gta.animate();
}
private void animate()
{
while(true)
{
for(int i=0; i <100; i ++)
{
x1 = x1 + 1;
x2 = x2 - 1;
try
{
Thread.sleep(100);
}
catch(Exception ex)
{}
repaint();
}
x1 = 60;
x2 = 150;
}
}
public GraphicsTestA() {
setSize(200,200);
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setVisible(true);
this.validate();
}
public void paint(Graphics g) {
super.paint(g);
g.drawString("Hello World",80,80);
g.drawLine(x1,60,x2,150);
g.drawRect(100,40,30,30);
}
}
So a number of issues:
Extending from JFrame
It's generally discouraged to extend directly from a top level container like JFrame, as you rarely add any new re-usable functionality to it and it locks you into a single use case.
It's also a compound component, that is, there are a number of components which reside on it, which one of the main causes of issues for you.
Instead, start with something like JPanel and then add it to any other container you need.
Overriding paint
As a general rule, you should avoid overriding paint, it's to low level for most situations, and in the case of top level containers, just messes things up. Instead, start with paintComponent
See Performing Custom Painting and Painting in AWT and Swing for more details
Thread violations
As has already been pointed out, Swing is not thread safe AND it's single threaded.
This means that you should never update the UI or any values the UI relies on from outside the Event Dispatching Thread. It also means you should never perform any long running or blocking operations inside the context of the EDT either.
In simple cases, a Swing Timer is more then powerful enough to do the job. See How to use Swing Timers for more details
Putting it all together...
So, putting all that together might end up looking something like...
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import static javax.swing.JFrame.EXIT_ON_CLOSE;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class GraphicsTestA {
public static void main(String[] args) {
new GraphicsTestA();
}
public GraphicsTestA() {
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 TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
int x1 = 60;
int x2 = 150;
int loops = 0;
public TestPane() {
Timer timer = new Timer(100, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
x1 = x1 + 1;
x2 = x2 - 1;
loops++;
if (loops > 100) {
x1 = 60;
x2 = 150;
((Timer) e.getSource()).stop();
}
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.drawString("Hello World", 80, 80);
g2d.drawLine(x1, 60, x2, 150);
g2d.drawRect(100, 40, 30, 30);
g2d.dispose();
}
}
}
In swing, you're not supposed to invoke swing things outside of the swing thread. Windows lets you get away with some slop, but Java on the mac is super picky about it.
In this code, the constructor makes swing calls, but it's invoked from the main thread. You can try to split that out so the constructor is called by the Swing thread, but then there are problems with the animate method.
The animate method shouldn't be called by the Swing thread because it's calling sleep(), and pausing the swing thread is a bad idea™. However, it's also calling repaint(), which is a swing call and that needs to be called by the Swing thread.
I suggest you use EventQueue.invokeLater to construct your window, and then figure out how to use Swing Workers or some other appropriate mechanism to do your animation.
Related
Here is a piece of code :
import java.awt.Color;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
#SuppressWarnings("serial")
public class QuitButton extends JPanel implements ActionListener
{
static JButton button = new JButton("Panic");
Color[] colors = new Color[9];
boolean pressed = false;
public QuitButton()
{
button.addActionListener(this);
colors[0] = Color.RED;
colors[1] = Color.BLUE;
colors[2] = Color.GREEN;
colors[3] = Color.YELLOW;
colors[4] = Color.BLACK;
colors[5] = Color.PINK;
colors[6] = Color.MAGENTA;
colors[7] = Color.ORANGE;
colors[8] = Color.CYAN;
}
#Override
public void actionPerformed(ActionEvent e)
{
pressed = true;
}
public static void main(String args[])
{
JFrame frame = new JFrame("Do NOT Panic!!");
QuitButton qb = new QuitButton();
frame.add(qb);
frame.add(button);
frame.setLayout(new FlowLayout());
frame.setSize(400, 400);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
//frame.pack();
button.requestFocus();
qb.gameLoop();
}
public void gameLoop()
{
while (true)
{
repaint();
try
{
Thread.sleep(200);
} catch (Exception e)
{
}
}
}
public void paint(Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
if (pressed == false)
{
super.paint(g2d);
g2d.setColor(Color.GRAY);
g2d.fillRect(0, 0, 400, 400);
} else
{
super.paint(g2d);
Random r = new Random();
int min = 0;
int max = 8;
int index = r.nextInt(max - min) + min;
g2d.setColor(colors[index]);
g2d.fillRect(0, 0, 400, 400);
}
}
The purpose of this program: The rectangle should be grey before but when I click the panic button colours should start changing.
Please don't get confused with the name of the class which is QuitButton.
But my rectangle is not occupying the entire window. Instead I am getting a teeny tiny rectangle like this : http://g.recordit.co/xJAMiQu6fM.gif
I think it is because of the layout I am using and I haven't specified anywhere that the button will be on top. Probably that's why they are coming side by side. I am new to GUI creation and thank you for your help.
You seem to be making some guesses on how to do this, which is not a good way to learn to use a library. Your first step should be to check the relevant tutorials on this, most of which will be found here: Swing Info Since this appears to be homework, I'm not going to give you a code solution but rather suggestions on how to improve:
Override paintComponent, not paint since the latter gives double buffering and is less risky (less painting of borders and child component problems)
In your paintComponent override, be sure to call the super's paintComponent method first to clear "dirty" pixels.
Use a Swing Timer, not a while loop for your game loop. This will prevent your while loop from freezing the Swing event thread, a problem that can freeze your program. Google the tutorial as it is quite helpful.
Do your randomization within the ActionListener's code (here likely the ActionListener for your Swing Timer), not within the painting code. The painting code should not change the state of the object but rather should only display the object's state.
FlowLayout will respect a component's preferredSize, and your component's preferred size is 0,0 or close to it. Change this. Best to override public Dimension getPreferredSize() and return a Dimension that matches your Rectangle's size.
Avoid using "magic" numbers, such as for your rectangle's size, and instead use constants or fields.
Call repaint() within your Timer's ActionListener so the JVM knows to paint the component.
I've got a problem here. The program below creates an animation ( a circle going from one point to another). The animation should start when you click the button.
The problem is that when I click the button, I cannot see the circle sliding. It just appears, after some time, in other place.
It's seems interesting to me(as a beginner), that if I do not use a button, and call the moveIt() method inside go(), I get a normal animation (I can see the circle sliding).
Can you give me, please, some suggestions on this problem ?
Thanks.
Here's the program:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JButton;
import java.awt.event.*;
final public class MiniMiniMusicPlayer1 implements ActionListener
{
JFrame frame;
DrawPanel drawPanel;
private int X = 7;
private int Y = 7;
public static void main(String... args)
{
new MiniMiniMusicPlayer1().go();
}
private void go()
{
frame = new JFrame("Player");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
drawPanel = new DrawPanel();
frame.getContentPane().add(BorderLayout.CENTER, drawPanel);
JButton buttonStart = new JButton("Start animation");
frame.getContentPane().add(BorderLayout.SOUTH, buttonStart);
buttonStart.addActionListener(this);
frame.setResizable(false);
frame.setSize(300, 300);
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
#Override
public void actionPerformed(ActionEvent ev){
moveIt();
}
class DrawPanel extends JPanel
{
public void paintComponent(Graphics g)
{
g.setColor(Color.BLACK);
g.fillRect(0, 0, this.getWidth(), this.getHeight());
g.setColor(Color.RED);
g.fillOval(X, Y, 50, 50);
}
}
private void moveIt()
{
for (int i = 0; i < 100; ++i)
{
X++;
Y++;
try
{
Thread.sleep(10);
}
catch (Exception e)
{
e.printStackTrace();
}
frame.repaint();
}
}
}
Swing and AWT work using an Event Dispatch thread. That thread is responsible for handling all events (running the event handlers, such as a button's actionPerformed) and repaint requests.
When you run your moveIt from go, it is ran by the main thread. It creates repaint requests, and the event dispatch thread, which runs concurrently with the main thread, dispatches them. Thus, you can see the animation.
But when you call moveIt from an event handler, it is ran by the event dispatch thread itself. Thus, the whole loop is performed, and all the repaint requests are queued, but the thread is busy and can't dispatch them until it's finished the loop.
When it is done with the loop, it dispatches the repaints (or rather, it dispatches just one, there is no need for it to repaint 100 times). The repaint is done given the final state of X and Y.
If you want to see animations, you should make sure you are not running them inside the EDT. You can use a javax.swing.Timer object for this, for example.
Remember never to run a long operation inside an event handler. It holds up the EDT and makes your GUI unresponsive. Never use Thread.sleep() in an event handler. If your long task is supposed to do something on a periodic basis, use a Timer. If it's supposed to do some big task like load from database, use SwingWorker.
I have a colored box, I want it to change color every 1/2 second, however I want my code to run as well.
I'm using Java AWT's Graphic Api to draw using g.fillRect(568, 383, 48, 48); where g is wrapped to 'Graphics.'
So you'd think that its simple right?
Color[] colors
colors = new Color[4];
colors[0] = new Color(Color.red);
colors[1] = new Color(Color.blue);
colors[2] = new Color(Color.green);
colors[3] = new Color(Color.yellow);
for(int i = 0; i < colors.length; i++){
g.setColor(colors[i]);
g.fillRect(568, 383, 48, 48);
}
This is all cool but the problem is that none of my program runs when this for loop is running...
I think I can make the game 'Multi-Threaded' which means it can do more than one thing at a time but I have no idea how to do this and it sounds hard, all help appreciated!
Most UI frameworks aren't thread safe, so you need to beware of that. For example, in Swing, you could use a Swing Timer to act as a pseudo loop. Because the Timer notifies the ActionListener from within the context of the Event Dispatching Thread, it makes it safe to update the UI or the state of the UI from within, without risking thread race conditions
Take a look at Concurrency in Swing and How to use Swing Timers for more details
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class JavaApplication430 {
public static void main(String[] args) {
new JavaApplication430();
}
public JavaApplication430() {
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 TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private Color[] colors;
private int whichColor = 0;
public TestPane() {
colors = new Color[4];
colors[0] = Color.red;
colors[1] = Color.blue;
colors[2] = Color.green;
colors[3] = Color.yellow;
Timer timer = new Timer(500, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
whichColor++;
repaint();
if (whichColor >= colors.length) {
whichColor = colors.length - 1;
((Timer)(e.getSource())).stop();
}
}
});
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();
g2d.setColor(colors[whichColor]);
g2d.fillRect(0, 0, getWidth(), getHeight());
g2d.dispose();
}
}
}
I cannot imagine how you could create an interactive game if your code is single thread. To change the box color periodically you will have to put your thread to sleep. If your game is not multi-thread, then this will freeze your application, preventing user interaction. You will find a lot of interesting materials about programming with threads in Java:
http://docs.oracle.com/javase/tutorial/essential/concurrency/
http://www.ibm.com/developerworks/library/j-thread/
http://moderntone.blogspot.com.br/2013/02/a-simple-java-multithreading-example.html
Just google it!
I have the following code:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.text.View;
public class ex10 extends JPanel {
private int x=1;
int y=1;
//Constructor
public ex10() {
while(true) {
System.out.println("x ->"+ x);
System.out.println("y ->" + y);
x = randomposition(x);
y = randomposition(y);
this.repaint();
}
}
public int randomposition(int value) {
Random random = new Random();
if (random.nextBoolean() == true) {
if (value+1 != 500) {
value++;
}
}
else {
if (value-1 != 0) {
value--;
}
}
return value;
}
#Override
public void paintComponent(Graphics g) {
//super.paintComponent(g);
g.setColor(Color.green);
g.fillRect(x, y, 20, 20);
}
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setSize(500, 500);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
frame.add(new ex10());
}
}
Unfortunately, when this.repaint() is called, the point isn't being displayed, but I still got the System.out.println. I tried setting a new thread separatedly, but to no avail.
I tried out some other solution (invokelater, and paintimmediately), also to no avail.
My goal is to set a green point which wanders on the screen.
Do you have any solution?
Your while (true) is blocking the Swing event thread putting the application to sleep.
For simple animation and game loop, use a Swing Timer. If you have long running code that needs to be in the background, then use a background thread such as a SwingWorker, but taking care to make sure that all calls that change the state of your Swing components should be done on the Swing event thread.
For example, you could change this:
while(true) {
System.out.println("x ->"+ x);
System.out.println("y ->" + y);
x = randomposition(x);
y = randomposition(y);
this.repaint();
}
to this that uses a Swing Timer (javax.swing.Timer):
int timerDelay = 20;
new Timer(timerDelay, new ActionListener(){
public void actionPerformed(ActionEvent e) {
x = randomposition(x);
y = randomposition(y);
repaint();
}
}).start();
Regarding DSquare's comments:
Indeed you are not running your GUI on the Swing event thread, something you should be doing, and yet your while true loop is still freezing your painting because your infinite loop prevents the component from completely creating itself.
As noted above, you should in fact start all Swing GUI's on the Swing event thread which you could do by placing the Swing creation code into a Runnable and queuing the Runnable on the event thread via the SwingUtilities method, invokeLater.
You need to call the super's paintComponent method in your paintComponent override so that the JPanel can do its housekeeping graphics works including clearing "dirty" pixels.
For example, change this:
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setSize(500, 500);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
frame.add(new ex10());
}
to this:
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame();
frame.setSize(500, 500);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
frame.add(new Ex10());
}
});
}
And change this:
#Override
public void paintComponent(Graphics g) {
//super.paintComponent(g);
g.setColor(Color.green);
g.fillRect(x, y, 20, 20);
}
to this:
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.green);
g.fillRect(x, y, 20, 20);
}
I've got a problem which I just can't seem to get around involving a small animation. For a project we are designing a J9 application to run on a PDA (DELL Axim X51). The problematic code (shown below) is when a mouse click is detected to run a small animation in a nested panel. If the animation is run independently of the mouse click, it works fine and repaints perfectly. When the method is called when a mouse click is recognized the repaint at every animation interval us ignored and only once the animation is completed will the panel be repainted.
I think it might have something to do with when a mouse click is recognized the application is synchronized with itself stopping any internal method calls to paint OR that the paint operation does not have high enough priority over the mouse click.
My code:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Panel;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
public class StatusLoader extends Panel implements Runnable{
int progress;
public static void main(String[] args) {
Frame f = new Frame();
StatusLoader mp = new StatusLoader();
mp.setBackground(Color.yellow);
f.add(mp);
f.setSize(300,300);
f.show();
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
mp.start();
}
public void start(){
Thread t = new Thread(this);
t.run();
}
public void run(){
for (int i = 0; i < 10; i++) {
progress+=10;
this.repaint(0);
System.out.println(i);
try{
Thread.sleep(100);
}catch(InterruptedException ex){}
}
}
public void paint(Graphics g){
System.out.println("called repaint");
g.setColor(Color.red);
g.setFont(new Font("Sansserif",2,24));
FontMetrics fm = g.getFontMetrics();
int stringWidth = fm.stringWidth("Loading");
g.drawString("Loading", getWidth()/2-stringWidth/2, getHeight()/2);
g.setFont(new Font("Sansserif",2,12));
fm = g.getFontMetrics();
stringWidth = fm.stringWidth("Map ziles for the new zoom level");
g.drawString("Map tiles for the new zoom level", getWidth()/2-stringWidth/2, getHeight()/2+30);
g.drawRect(getWidth()/2-100, getHeight()/2+60, 200, 10);
g.setColor(Color.blue);
g.fillRect(getWidth()/2-100, getHeight()/2+60, progress*2, 10);
}
}
In StatusLoader.start, you use Thread.run instead of Thread.start. That means StatusLoader.run is executed on the events thread, which is a Very Bad Thing. Change it to t.start().