JFrame updates only upon resizing window - java

I'm trying to make my own version of Snake for learning purposes. Everything seems to work fine except that if I want my frame to be repainted, I have to resize my window manually. Here is my code:
package snake;
import java.awt.*;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class PlayGame extends JPanel implements Runnable{
public boolean animate = false;
public final int FRAME_DELAY = 750;
PickupBall b = new PickupBall();
Snake bob = new Snake();
public synchronized void start() {
animate = true;
}
public synchronized void stop() {
animate = false;
}
private synchronized boolean animationEnabled() {
return animate;
}
#Override
public void run(){
while(true){
if (animationEnabled()){
repaint();
}
try {
Thread.sleep(FRAME_DELAY);
}
catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
b.draw(g);
bob.draw(g);
}
public static void main(String[] args) {
JFrame jfr = new JFrame("Snake");
jfr.setSize(640,640);
jfr.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jfr.setResizable(true);
PlayGame p = new PlayGame();
jfr.setContentPane(p);
p.setBackground(Color.WHITE);
p.start();
new Thread(p).start();
jfr.setVisible(true);
}
}
Why isn't repaint() triggered without altering the frame size? I get the correlation but it makes no sense to me why it needs such a trigger when it's in a while(true) loop anyway.
What am I missing here?
Edit 1:
Removed thread object
Replaced t.start() with p.start()
Edit 2:
Added new Thread(p).start(); and now it works! Thanks.
Edit 3:
Removed revalidate();

You are executing repaint() in the worker thread and not in the event dispatch tread (EDT) which is the only one actually drawing onto the screen.
You have to en queue the call to repaint() in the EDT using SwingUtilities static methods invokeLater() or invokeAndWait().

Added new Thread(p).start();
Still have no idea how or why this is different to
Thread t = new Thread(p);
t.start();
But it worked.

Related

Java Game Loop With editable frame cap

I'm trying to create a game loop in java which has a frame cap which can be set while the game is running. The problem im having is I have a render() function and a update() function. Just setting frame cap for both render() and update() means that the speed of the game logic will change when you change the frame cap. Any idea how to have a frame cap which can be set in game while not affecting the speed of the game logic (update())?
As stated in the comments: You can create two threads one of which is responsible for updating and the other is responsible for rendering.
Try to think of an architecture that suits your needs and your game. You can try something like:
import java.awt.Dimension;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Runner extends JPanel {
private static final long serialVersionUID = -5029528072437981456L;
private JFrame window;
private Renderer renderer;
public Runner() {
setPreferredSize(new Dimension(WindowData.WIDTH, WindowData.HEIGHT));
setFocusable(true);
requestFocus();
window = new JFrame(WindowData.TITLE);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.add(this);
window.setResizable(false);
window.pack();
window.setLocationRelativeTo(null);
window.setVisible(true);
renderer = new Renderer(getGraphics());
renderer.start();
}
public static void main(String[] args) {
new Runner();
}
}
import java.awt.Graphics;
public class Renderer implements Runnable {
private Graphics context;
private Thread thread;
private boolean running;
public Renderer(Graphics context) {
this.context = context;
thread = new Thread(this, "Renderer");
running = false;
}
public void start() {
if (running)
return;
running = true;
thread.start();
}
public void render() {
context.clearRect(0, 0, WindowData.WIDTH, WindowData.HEIGHT);
context.fillRect(50, 50, 100, 100);
}
public void run() {
while (running) {
render();
// ** DO YOUR TIME CONTROL HERE ** \\
}
}
}
This code will actually lead to serious performance issues because you are not in control over the rendering ( repaint() ) time.
But this is just a demo to show you how you can use different threads. Do your own architecture.

JLabel from a different class not being drawn

I am trying to show a label from another class. However when I add it to the frame it will not show. I have tried drawing it from the counter class itself by passing in the Frame which I would assume is not good practice (ignoring the fact it didn't work). As well as what is in the code below. Can anybody help me and explain why my solution will not show the created label? As you can most likely tell i'm very new to using JPanel.
CookieChaser Class
public class CookieChaser extends JPanel {
public static void main(String[] args) throws InterruptedException {
JFrame frame = new JFrame("Cookie Chaser");
CookieChaser game = new CookieChaser();
frame.add(game);
frame.setSize(1000, 1000);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
counter Score = new counter(frame);
cookie Cookie = new cookie();
JLabel item = counter.getLabel();
frame.add(item);
frame.setVisible(true);
while (true) {
game.repaint();
Thread.sleep(10);
}
}
#Override
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
}
}
Counter Class
public class counter {
int count;
static JLabel text;
public counter(JFrame frame){
count = 0;
text = new JLabel(String.valueOf(count));
text.setLocation(0,0);
text.setSize(50,50);
}
public static JLabel getLabel(){
return text;
}
I modified your code to create the following Swing GUI.
Whenever I create a Swing game or application, I use the model / view / controller pattern. This means I create a GUI model. The GUI model contains all of the fields that my GUI needs. Next, I create a GUI view which reads the values from the GUI model. Finally, I create one or more GUI controllers, which update the GUI model and refresh / repaint the GUI view.
I made the following changes to your code:
I created a GUI model. I created the Counter class. All the Counter class does is hold a counter value.
I created a GUI view, which uses the GUI model. I created the JFrame, JPanel, and JLabel all in the view class. You may use more than one class to create the view. Since this view was simple, I created everything in one class.
All Swing applications must start with a call to the SwingUtilities invokeLater method. The invokeLater method puts the creation and updating of the Swing components on the Event Dispatch thread. Oracle and I insist that all Swing applications start this way.
I created a separate Animation runnable so that you can see the JLabel updates. I increment the counter once a second.
The repaint method in the Animation class calls the SwingUtilities invokeLater method to ensure that the JLabel update is done on the Event Dispatch thread. The animation loop runs in a separate thread to keep the GUI responsive.
Here's the revised code.
package com.ggl.testing;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class CookieChaser implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new CookieChaser());
}
private JLabel counterLabel;
#Override
public void run() {
Counter counter = new Counter();
JFrame frame = new JFrame("Cookie Chaser");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel mainPanel = new JPanel();
counterLabel = new JLabel(" ");
mainPanel.add(counterLabel);
frame.add(mainPanel);
frame.setSize(300, 200);
frame.setVisible(true);
new Thread(new Animation(this, counter)).start();
}
public void setCounterLabel(String text) {
counterLabel.setText(text);
}
public class Counter {
private int counter;
public int getCounter() {
return counter;
}
public void setCounter(int counter) {
this.counter = counter;
}
public void incrementCounter() {
this.counter++;
}
}
public class Animation implements Runnable {
private Counter counter;
private CookieChaser cookieChaser;
public Animation(CookieChaser cookieChaser, Counter counter) {
this.cookieChaser = cookieChaser;
this.counter = counter;
}
#Override
public void run() {
while (true) {
counter.incrementCounter();
repaint();
sleep(1000L);
}
}
private void repaint() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
cookieChaser.setCounterLabel(Integer.toString(counter
.getCounter()));
}
});
}
private void sleep(long duration) {
try {
Thread.sleep(duration);
} catch (InterruptedException e) {
}
}
}
}

Java game loop (painting) freezes my window

I'm changing "views" with cardLayout (this class has a JFrame variable). When a user clicks a new game button this happens:
public class Views extends JFrame implements ActionListener {
private JFrame frame;
private CardLayout cl;
private JPanel cards;
private Game game;
public void actionPerformed(ActionEvent e) {
String command = e.getActionCommand();
if (command.equals("New game")) {
cl.show(cards, "Game");
game.init();
this.revalidate();
this.repaint();
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
game.loop();
}
});
}
}
}
Game's loop method and heading of class:
public class Game extends JPanel implements KeyListener {
public void loop() {
while (player.isAlive()) {
try {
this.update();
this.repaint();
// first class JFrame variable
jframee.getFrame().repaint();
// first class JFrame variable
jframee.getFrame().revalidate();
Thread.sleep(17);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void update() {
System.out.println("updated");
}
}
I'm painting using paintComponent()
public void paintComponent(Graphics g) {
System.out.println("paint");
...
}
Actually it's not painting anything. When I do not call loop() method (so it paints it just once) all images are painted correctly. But when I call loop() method, just nothing is happening in the window. (Even close button on JFrame doesn't work.)
How to fix that? (When I was creating JFrame inside game class everything worked fine, but now I want to have more views so I need JFrame in other class.)
Thanks.
Precursor: The Event Dispatch Thread (EDT).
Swing is single-threaded. What does this mean?
All processing in a Swing program begins with an event. The EDT is a thread that processes these events in a loop along the following lines (but more complicated):
class EventDispatchThread extends Thread {
Queue<AWTEvent> queue = ...;
void postEvent(AWTEvent anEvent) {
queue.add(anEvent);
}
#Override
public void run() {
while (true) {
AWTEvent nextEvent = queue.poll();
if (nextEvent != null) {
processEvent(nextEvent);
}
}
}
void processEvent(AWTEvent theEvent) {
// calls e.g.
// ActionListener.actionPerformed,
// JComponent.paintComponent,
// Runnable.run,
// etc...
}
}
The dispatch thread is hidden from us through abstraction: we generally only write listener callbacks.
Clicking a button posts an event (in native code): when the event is processed, actionPerformed is called on the EDT.
Calling repaint posts an event: when the event is processed, paintComponent is called on the EDT.
Calling invokeLater posts an event: when the event is processed, run is called on the EDT.
Everything in Swing begins with an event.
Event tasks are processed in sequence, in the order they are posted.
The next event can only be processed when the current event task returns. This is why we cannot have an infinite loop on the EDT. actionPerformed (or run, as in your edit) never returns, so the calls to repaint post paint events but they are never processed and the program appears to freeze.
This is what it means to "block" the EDT.
There are basically two ways to do animation in a Swing program:
Use a Thread (or a SwingWorker).
The benefit to using a thread is that the processing is done off the EDT, so if there is intensive processing, the GUI can still update concurrently.
Use a javax.swing.Timer.
The benefit to using a timer is that the processing is done on the EDT, so there is no worry about synchronization, and it is safe to change the state of the GUI components.
Generally speaking, we should only use a thread in a Swing program if there's a reason to not use a timer.
To the user, there is no discernible difference between them.
Your call to revalidate indicates to me that you are modifying the state of the components in the loop (adding, removing, changing locations, etc.). This is not necessarily safe to do off the EDT. If you are modifying the state of the components, it is a compelling reason to use a timer, not a thread. Using a thread without proper synchronization can lead to subtle bugs that are difficult to diagnose. See Memory Consistency Errors.
In some cases, operations on a component are done under a tree lock (Swing makes sure they are thread-safe on their own), but in some cases they are not.
We can turn a loop of the following form:
while ( condition() ) {
body();
Thread.sleep( time );
}
in to a Timer of the following form:
new Timer(( time ), new ActionListener() {
#Override
public void actionPerformed(ActionEvent evt) {
if ( condition() ) {
body();
} else {
Timer self = (Timer) evt.getSource();
self.stop();
}
}
}).start();
Here is a simple example demonstrating animation both with a thread and a timer. The green bar moves cyclically across the black panel.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
class SwingAnimation implements Runnable{
public static void main(String[] args) {
SwingUtilities.invokeLater(new SwingAnimation());
}
JToggleButton play;
AnimationPanel animation;
#Override
public void run() {
JFrame frame = new JFrame("Simple Animation");
JPanel content = new JPanel(new BorderLayout());
play = new JToggleButton("Play");
content.add(play, BorderLayout.NORTH);
animation = new AnimationPanel(500, 50);
content.add(animation, BorderLayout.CENTER);
// 'true' to use a Thread
// 'false' to use a Timer
if (false) {
play.addActionListener(new ThreadAnimator());
} else {
play.addActionListener(new TimerAnimator());
}
frame.setContentPane(content);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
abstract class Animator implements ActionListener {
final int period = ( 1000 / 60 );
#Override
public void actionPerformed(ActionEvent ae) {
if (play.isSelected()) {
start();
} else {
stop();
}
}
abstract void start();
abstract void stop();
void animate() {
int workingPos = animation.barPosition;
++workingPos;
if (workingPos >= animation.getWidth()) {
workingPos = 0;
}
animation.barPosition = workingPos;
animation.repaint();
}
}
class ThreadAnimator extends Animator {
volatile boolean isRunning;
Runnable loop = new Runnable() {
#Override
public void run() {
try {
while (isRunning) {
animate();
Thread.sleep(period);
}
} catch (InterruptedException e) {
throw new AssertionError(e);
}
}
};
#Override
void start() {
isRunning = true;
new Thread(loop).start();
}
#Override
void stop() {
isRunning = false;
}
}
class TimerAnimator extends Animator {
Timer timer = new Timer(period, new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
animate();
}
});
#Override
void start() {
timer.start();
}
#Override
void stop() {
timer.stop();
}
}
static class AnimationPanel extends JPanel {
final int barWidth = 10;
volatile int barPosition;
AnimationPanel(int width, int height) {
setPreferredSize(new Dimension(width, height));
setBackground(Color.BLACK);
barPosition = ( width / 2 ) - ( barWidth / 2 );
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int width = getWidth();
int height = getHeight();
int currentPos = barPosition;
g.setColor(Color.GREEN);
g.fillRect(currentPos, 0, barWidth, height);
if ( (currentPos + barWidth) >= width ) {
g.fillRect(currentPos - width, 0, barWidth, height);
}
}
}
}
What does update do? You probably shouldnt call game.loop() on the EDT. You are running a loop on EDT, your repaint wont ever be invoked since repaint queues an event on EDT and it seems kind busy. Try moving game.loop() to another thread
new Thread(new Runnable() {
#override
public void run() {
game.loop();
}
}).start();
This way you wont block the EDT while the repaint still gets to be executed on the EDT.
Move game.loop() method invocation to something like:
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
game.loop()
}
});
Thanks.

Run and pause a GUI background thread by clicking a button

I need to run a background thread in my Java GUI that only runs when I click a button and pauses when I click that button again. I am not exactly sure how to set this up, but I have placed a thread in my constructor and the while loop within is set to go through when I set a specific boolean to TRUE. One button switches from setting this boolean TRUE or FALSE.
Everything else I have in this GUI works fine. When I tried debugging the thread, it actually works as I step through the thread but nothing when I try running the GUI completely. The GUI is rather large so I'm gonna put up a portion of the constructor and the action listener of the button. The rest of the code is unnecessary since it works just fine. I need to know what I am doing wrong here:
public BasketballGUI() {
// certain labels and buttons
Thread runningSim = new Thread() {
public void run() {
while(simRun) {
// do stuff here
}
}
};
runningSim.start();
}
// other GUI stuff
// actionListener that should run the thread.
class SimButtonListener implements ActionListener {
public void actionPerformed(ActionEvent arg0) {
if(!simRun) {
simRun = true;
sim.setText("Pause Simulator");
}
else if(simRun) {
simRun = false;
sim.setText("Run Simulator");
}
// other stuff in this actionListener
}
}
Establish a Swing based Timer with an ActionListener that will be called repeatedly.
In the actionPerformed(ActionEvent) method call repaint().
Start the timer (Timer.start()) when the user clicks Start
Stop the timer (Timer.stop()) when the user clicks Stop
If you cannot get it working from that description, I suggest you post an SSCCE of your best attempt.
I thought I had one 'lying around'.. Try this working SSCCE which uses images created in this SSCCE.
I could see this background thread useful for a Java GUI when handling button events to affect something like a text area or progress bar.
For the sake of argument, I will build you a tiny GUI that affects a Text Area. I hope this helps you.
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.*;
public class TestClass extends JPanel {
super("TestClass - Title");
private AtomicBoolean paused;
private JTextArea jta;
private JButton btn;
private Thread thread;
public TestClass() {
paused = new AtomicBoolean(false);
jta = new JTextArea(100, 100);
btn = new JButton();
initialize();
}
public void initialize() {
jta.setLineWrap(true);
jta.setWrapStyleWord(true);
add(new JScrollPane(jta));
btn.setPreferredSize(new Dimension(100, 100));
btn.setText("Pause");
btn.addActionListener(new ButtonListener());
add(btn);
Runnable runnable = new Runnable() {
#Override
public void run() {
while(true) {
for(int i = 0; i < Integer.MAX_VALUE; i++) {
if(paused.get()) {
synchronized(thread) {
try {
thread.wait();
} catch(InterruptedException e) {
}
}
}
}
jta.append(Integer.toString(i) + ", ");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
}
};
thread = new Thread(runnable);
thread.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(100, 30);
}
class ButtonListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent event) {
if(!paused.get()) {
btn.setText("Start");
paused.set(true);
} else {
btn.setText("Pause");
paused.set(false);
synchronized(thread) {
thread.notify();
}
}
}
}
}
Main class to call everything.
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class MainClass {
public static void main(final String[] arg) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestClass());
frame.pack();
frame.setVisible(true);
frame.setLocationRelativeTo(null);
}
});
}
}
I did not test this code to see if it works exactly, Its main goal is to break you through your coders block and use my components to fix your issue. Hope this helped. Need anything else Email me at DesignatedSoftware#gmail.com

Do Robot methods need to be run on the event queue?

Robot is part of the AWT library, but it seems quite different from most all the rest of the library. I am creating a Swing GUI that mixes Swing with Java Native Access (JNA) and Robot to allow Java to drive some MS Windows/Citrix work programs. My gut feeling is that since Robot will queue events on the "platform's native input queue" that the last thing I want to do is to run it on the EDT, but on the other hand, most of the classes in the AWT and Swing libraries should be run on the Swing event thread. So to try clarify this in my mind for me let me ask as specific a question as possible:
Should Robot methods (in particular key presses and releases, mouse moves, mouse presses and releases) be run on or off of the Swing event dispatch thread (the EDT)?
The Robot methods you mentioned should not be run on the EDT. Taking a look at the source code revealed that each one of these "event" methods has one thing in common (the afterEvent call):
public synchronized void keyPress(int keycode) {
checkKeycodeArgument(keycode);
peer.keyPress(keycode);
afterEvent();
}
public synchronized void mousePress(int buttons) {
checkButtonsArgument(buttons);
peer.mousePress(buttons);
afterEvent();
}
// etc
private void afterEvent() {
autoWaitForIdle();
autoDelay();
}
private void autoWaitForIdle() {
if (isAutoWaitForIdle) {
waitForIdle();
}
}
public synchronized void waitForIdle() {
checkNotDispatchThread();
/* snip */
}
private void checkNotDispatchThread() {
if (EventQueue.isDispatchThread()) {
throw new IllegalThreadStateException("Cannot call method from the event dispatcher thread");
}
}
If you call any of these methods on the EDT while Robot.isAutoWaitForIdle is true, an exception will be thrown. This stands to reason that even if isAutoWaitForIdle is false, these methods shouldn't be called from the EDT.
API quite exactly talks, then I'm understand that that Robot should be ignore if is invoked from EDT or not
Using the class to generate input events differs from posting events to the AWT event queue or AWT components in that the events are generated in the platform's native input queue.
I'm rellative new in Java, my first touch was Java1.6.009, then I can't compare changes for AWT and (when born) Swing in Java1.3 and rest in Java1.4
my example
import javax.imageio.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.io.*;
public class CaptureScreen implements ActionListener {
private JFrame f = new JFrame("Screen Capture");
private JPanel pane = new JPanel();
private JButton capture = new JButton("Capture");
private JDialog d = new JDialog();
private JScrollPane scrollPane = new JScrollPane();
private JLabel l = new JLabel();
private Point location;
public CaptureScreen() {
capture.setActionCommand("CaptureScreen");
capture.setFocusPainted(false);
capture.addActionListener(this);
capture.setPreferredSize(new Dimension(300, 50));
pane.add(capture);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(pane);
f.setLocation(100, 100);
f.pack();
f.setVisible(true);
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
createPicContainer();
}
});
}
private void createPicContainer() {
l.setPreferredSize(new Dimension(700, 500));
scrollPane = new JScrollPane(l,
ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
scrollPane.setBackground(Color.white);
scrollPane.getViewport().setBackground(Color.white);
d.setDefaultCloseOperation(JDialog.HIDE_ON_CLOSE);
d.add(scrollPane);
d.pack();
d.setVisible(false);
}
#Override
public void actionPerformed(ActionEvent e) {
if (e.getActionCommand().equals("CaptureScreen")) {
Dimension d1 = Toolkit.getDefaultToolkit().getScreenSize(); // gets the screen size
Robot r;
BufferedImage bI;
try {
r = new Robot(); // creates robot not sure exactly how it works
Thread.sleep(1000); // waits 1 second before capture
bI = r.createScreenCapture(new Rectangle(d1)); // tells robot to capture the screen
showPic(bI);
saveImage(bI);
} catch (AWTException e1) {
e1.printStackTrace();
} catch (InterruptedException e2) {
e2.printStackTrace();
}
}
}
private void saveImage(BufferedImage bI) {
try {
ImageIO.write(bI, "JPG", new File("screenShot.jpg"));
} catch (IOException e) {
e.printStackTrace();
}
}
private void showPic(BufferedImage bI) {
ImageIcon pic = new ImageIcon(bI);
l.setIcon(pic);
l.revalidate();
l.repaint();
d.setVisible(false);
location = f.getLocationOnScreen();
int x = location.x;
int y = location.y;
d.setLocation(x, y + f.getHeight());
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
d.setVisible(true);
}
});
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
CaptureScreen cs = new CaptureScreen();
}
});
}
}
Amplifying on #mKorbel's thoughtful answer, and confirming his empirical result, note how the various Robot methods delegate to an internal instance of the RobotPeer interface, the native implementation of which varies by platform. Moreover, the methods are synchronized. The synthetic events all arrive on the EventQueue, irrespective of the source.

Categories

Resources