I am writing a program that will play a song and have a JPanel displaying images during it. The song plays fine, the first image is drawn (I assume from the initial call to paintComponent) but somehow the repaint() doesn't seem to be getting called. I could really use an extra set of eyes. I have the code below for the JPanel class that will be displaying the images. Thanks so much!
class pictures extends JPanel implements Runnable {
private ImageIcon images[];
private Thread imagerunner;
private int currentImage;
pictures() {
super();
imagerunner = new Thread(this);
images = new ImageIcon[6];
imagerunner = new Thread(this);
images[0] = new ImageIcon("pic1.jpg");
images[1] = new ImageIcon("pic2.jpg");
images[2] = new ImageIcon("pic3.jpg");
images[3] = new ImageIcon("pic4.jpg");
images[4] = new ImageIcon("pic5.jpg");
images[5] = new ImageIcon("pic6.jpg");
currentImage = 0;
}
public void run() {
int i = 0;
System.out.println("starting pics");
while( i < 100 ) {
System.out.println("about to repaint()");
this.repaint();
System.out.println( "image: " + currentImage );
waiting( 2000 );
currentImage++;
}
System.out.println("done");
}
public void paintComponent( Graphics g ) {
super.paintComponent( g );
System.out.println("repainting");
images[ currentImage ].paintIcon(this,g,0,0);
}
public static void waiting (int n) {
long t0, t1;
t0 = System.currentTimeMillis();
do{
t1 = System.currentTimeMillis();
}
while (t1 - t0 < n);
}
}
You never start the thread imagerunner.
It is assigned twice (for no reason).
You can't modify GUI from another thread. Use Swing utilities for that.
The waiting() method seems to be blocking the EDT. It would be better to use a Swing Timer to schedule the updates.
You would need to do the following:
1) Actually create an instance to run.
2) You will need to invoke repaint() regularly in order to get your display to repaint.
Hope it helps. Cheers!
Related
I am building a Maze where Monster is tracking the player and moving towards it. I have a function called SwapCells where I believe the problem lays in. SwapCells is used to move both Monster and Player. I have a ScheduledExecutorService that runs Monstermover which uses SwapCells. The Monster starts moving when the game starts, now the problems is when I start moving the player, the ScheduledExecutorService stops working and I can only move the player. I believe the problem is that, unlike MoveMonster, the Keylistener is not part of ScheduledExecutorService, hence when I invoke KeyListener that in turn runs SwapCell, it makes the ScheduledExecutorService stop. Any advice? Please Help!
private final int CELLSIZE = 20;
private ArrayList<Level> levels = new ArrayList<Level>();
private Player player;
private Monster monster;
private DisplayInfo display;
private int xSize;
private int ySize;
private int currentLevel = 0;
private int direction = 0;
//private CountDownTimer timer = new CountDownTimer(15);
public Game(int xSize, int ySize) {
player = new Player(0,0);
monster = new Monster(0,1);
setXSize(xSize);
setYSize(ySize);
setLayout(new GridLayout(ySize, xSize));
addLevel();
addKeyListener(new KeyListener() {
// POSSIBLE PROBLEM IS HERE !!
#Override
public void keyPressed(KeyEvent event) {
if(!hitWall) }
swapCells(getCurrentLevel().getMap()[x+dx][y+dy], getCurrentLevel().getMap()[x][y], player);
}
}
}
});
ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);
executor.scheduleAtFixedRate(() -> moveMonster(), 0L, 1000L, TimeUnit.MILLISECONDS);
}
public void swapCells(Cell cell1, Cell cell2, Mover mover) {
Icon tempIcon = cell1.getLabel().getIcon();
String tempID = cell1.getID();
// sets mover's x and y value
mover.setX(cell1.getX());
mover.setY(cell1.getY());
// sets mover's cell to new cell
getCurrentLevel().getpMaze().setCell(cell1, mover);
getCurrentLevel().getMap()[cell1.getX()][cell1.getY()].setID(cell2.getID());
getCurrentLevel().getMap()[cell1.getX()][cell1.getY()].getLabel().setIcon(cell2.getLabel().getIcon());
getCurrentLevel().getMap()[cell2.getX()][cell2.getY()].setID(tempID);
getCurrentLevel().getMap()[cell2.getX()][cell2.getY()].getLabel().setIcon(tempIcon);
}
public void moveMonster() {
PerfectMaze currentMaze = getCurrentLevel().getpMaze();
ArrayList<Cell> trackToPlayer = currentMaze.getTrack(currentMaze.getMonsterCell(), currentMaze.getPlayerCell(), "P");
Collections.reverse(trackToPlayer);
Cell nextCell = trackToPlayer.get(1);
//swapCells(nextCell, currentMaze.getMonsterCell(), monster);
swapCells(getCurrentLevel().getMap()[nextCell.getX()][nextCell.getY()], getCurrentLevel().getMap()[monster.getX()][monster.getY()], monster);
}
You should only interact with Swing components from the Swing "Event Dispatch Thread" (EDT). But you created an ExecutorService, and its threads are manipulating Swing components directly.
Instead, perform your background work in a SwingWorker, which can pass graphical updates safely to the EDT.
I'm actually trying to fix a problem with the component of JFrame that don't want to show up when I run my game loop (see the question after the code). I have reduced the code at the minimum possible for you to get what I mean fast:
Run.class
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
Game game = new Game();
game.loop();
}
});
}
Game.class
private Frame frame;
private HashMap<String,ActionListener> actions;
private HashMap<String,Image> ressources;
public Game() {
this.setActions();
this.setRessources();
frame = new Frame(actions,ressources);
}
public void loop() {
double FPS = 60;
double UPS = 60;
long initialTime = System.nanoTime();
final double timeU = 1000000000 / UPS;
final double timeF = 1000000000 / FPS;
double deltaU = 0, deltaF = 0;
int frames = 0, ticks = 0;
long timer = System.currentTimeMillis();
boolean running = true;
boolean RENDER_TIME = false;
while (running) {
...code for update, render, with a fps control
}
}
Frame.class
public Frame(HashMap<String,ActionListener> actions, HashMap<String,Image> ressources) {
this.setTitle(Constants.GAME_NAME);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setSize(Constants.GAME_SIZE_X, Constants.GAME_SIZE_Y);
this.setLayout(new FlowLayout());
JButton myButton = new JButton("My Button");
this.add(myButton);
this.revalidate();
this.repaint();
this.setVisible(true);
}
It's not the complete code because I don't want to give a useless thing. So here, my problem is:
If I run this code, the button not going to show up in the frame. But if I comment game.loop() in the Run.class, the windows show the button. And I don't understand WHY?
I have been trying for a few days to figure it out. I need some help for this one. Alone I'm afraid I will not find out.
To avid blocking the Event Dispatching Thread by running a long process you can use swing Timer which can handle the "looping" for you :
ActionListener animate = e -> {
game.oneFrame();
panel.repaint();
};
timer = new Timer(50, animate);
timer.start();
public void oneFrame(){
//update what is needed for one "frame"
}
For more help post mcve.
Looked around and there were a few similar questions, but none seemed to show how to run more than one progress bar in a single JFrame while updating it from 3 other threads.
Preferably I plan to have a progressbar class of its own, a lot of the examples I saw people doing all the progressbar work inside a main thread which I didn't really like. If that's how you're meant to do it I'm sorry I've never used a progressbar before.
I want to monitor the progress of the robot threads. I was going to send an update directly from the Robot class to my progress bar and have a progressbar object in my main if that's possible.
my main thread is sort of like this
CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
Motor m = new Motor();
Robot xRob = new Robot(cyclicBarrier, m);
Robot yRob = new Robot(cyclicBarrier, m);
Robot zRob = new Robot(cyclicBarrier, m);
Thread xRobThread = new Thread(xRob);
Thread yRobThread = new Thread(new Robot(cyclicBarrier, m));
Thread zRobThread = new Thread(zRob);
boolean clockwise = true, counterClockwise = false;
m.setMotor(clockwise, 14400000, xRob);
m.setMotor(clockwise, 0, yRob);
m.setMotor(counterClockwise, 36000000, zRob);
xRobThread.start();
yRobThread.start();
zRobThread.start();
This is the important parts of my robot class.
public class Robot implements Runnable{
public void run(){
System.out.println("Running: ");
m.Engage(this);
try {
System.out.println("Sleeping: ");
Thread.sleep(3000);
cyclicBarrier.await();
} catch (Exception e){
e.printStackTrace();
}
System.out.println("Engaging: ");
}
public void Rotate(){
if ((opcode & clockwise) > 0){
rotation++;
if(rotation == 360){
rotation = 0;
moveCount++;
}
}
if ((opcode & counter) > 0){
rotation--;
if(rotation == -360){
rotation = 0;
moveCount --;
}
}
}
}
Here is the progress bar I have so far
public class ProgressBar {
final int MAX = 100;
final JFrame frame = new JFrame("JProgress ");
final JProgressBar pbOne = new JProgressBar();
final JProgressBar pbTwo = new JProgressBar();
final JProgressBar pbThree = new JProgressBar();
ProgressBar(){
pbOne.setMinimum(0);
pbOne.setMaximum(MAX);
pbOne.setStringPainted(true);
pbTwo.setMinimum(0);
pbTwo.setMaximum(MAX);
pbTwo.setStringPainted(true);
pbThree.setMinimum(0);
pbThree.setMaximum(MAX);
pbThree.setStringPainted(true);
frame.setLayout(new FlowLayout());
frame.getContentPane().add(pbOne);
frame.getContentPane().add(pbTwo);
frame.getContentPane().add(pbThree);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300, 200);
frame.setVisible(true);
}
public void setProgress(int progress){
try {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
pb.setValue(progress); //HERE IS where I get lost. How do I differentiate between PBs and Threads
// As in which thread the code is coming from and which progress bar it's updating.
}
});
java.lang.Thread.sleep(100);
} catch (InterruptedException e) {
JOptionPane.showMessageDialog(frame, e.getMessage());
}
}
}
I was going to update the progress everytime it went through a rotation in my robot class, but I haven't implemented that yet since I wanted to first get the design down.
I am having some difficulties using swing workers, timers, and I am actually a little confused.
As far as my understanding goes, I have to put on a timer to set-up recurring tasks that have to be called by the EDT.
I'm trying to make a program that shows graphically a sorting alghoritm (like this : https://www.youtube.com/watch?v=kPRA0W1kECg )
I just don't understand why the GUI won't refresh. I am quite sure the repaint method is being called since I put a sysout showing me the ordered values and it seems to work , but the GUI just... doesn't change.
Here's my code:
public class MainWindow {
private JFrame frame;
JPanel panel;
public final static int JFRAME_WIDTH = 800;
public final static int JFRAME_HEIGHT = 600;
public final static int NELEM = 40;
ArrayList<Double> numbers;
ArrayList<myRectangle> drawables = new ArrayList<myRectangle>();
Lock lock = new ReentrantLock();
Condition waitme = lock.newCondition();
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
MainWindow window = new MainWindow();
window.frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public MainWindow() {
initialize();
}
private void initialize() {
frame = new JFrame();
frame.setBounds(100, 100, JFRAME_WIDTH + 20, JFRAME_HEIGHT + 40);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
panel = new myPanel();
frame.getContentPane().add(panel, BorderLayout.CENTER);
Timer timer = new Timer(500, new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
lock.lock();
try{
//Updating the gui
panel.repaint();
panel.revalidate();
//Giving the OK to the sorting alghoritm to proceed.
waitme.signal();
}catch(Exception e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
});
timer.start();
SwingWorker<Integer, String> sw = new SwingWorker<Integer, String>(){
#Override
protected Integer doInBackground() throws Exception {
mapAndCreate();
bubbleSort();
return null;
}
};
sw.execute();
}
private void bubbleSort() throws InterruptedException{
for(int i=0; i < NELEM; i++){
for(int j=1; j < (NELEM-i); j++){
if(drawables.get(j-1).wid > drawables.get(j).wid){
//swap the elements!
myRectangle temp = drawables.get(j-1);
drawables.set(j-1, drawables.get(j));
drawables.set(j, temp);
lock.lock();
try{
//Wait for the GUI to update.
waitme.await();
}catch(Exception e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
}
}
}
/***
* Function that maps values from 0 to 1 into the rectangle width.
*/
private void mapAndCreate() {
double max = 0;
numbers = new ArrayList<Double>(NELEM);
//Finding maximum.
for(int i = 0; i < NELEM; i++){
Double currElem = Math.random();
if(currElem > max) max = currElem;
numbers.add(currElem);
}
//Mapping process
int offset = 0;
for(int j = 0; j < NELEM; j++){
Integer mapped = (int) (( JFRAME_WIDTH * numbers.get(j) ) / max);
myRectangle rect = new myRectangle(offset , mapped);
drawables.add(rect);
offset += JFRAME_HEIGHT / NELEM;
}
}
private class myRectangle{
int myy , wid , colorR,colorG,colorB;
public myRectangle(int y , int wid){
this.myy = y;
this.wid = wid;
Random r = new Random();
colorR = r.nextInt(255);
colorG = r.nextInt(255);
colorB = r.nextInt(255);
}
}
private class myPanel extends JPanel{
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for(myRectangle rectan : drawables){
Graphics2D graphics2D = (Graphics2D)g;
System.out.println(rectan.wid);
Rectangle2D.Double rect = new Rectangle2D.Double(0,rectan.myy,rectan.wid,JFRAME_HEIGHT / NELEM);
graphics2D.setColor(new Color(rectan.colorR,rectan.colorG,rectan.colorB));
graphics2D.fill(rect);
}
System.out.println("====================================================================================================");
}
}
}
Most OSs (or rather the UI frameworks which they use) don't support concurrent access. Simply put, you can't render two strings of text at the same time.
That's why Swing runs all rendering operations in the UI thread. Calling rendering functions (like paint()) outside of the UI thread can cause all kinds of problems. So when you do it, Swing will just remember "I should repaint" and return (instead of doing any actual work). That way, Swing protects you but most people would prefer to get an error with a useful message.
A timer always also means that there is a thread somewhere which executes when the timer runs out. This is not the UI thread of Swing. So any paing operations there must be wrapped with EventQueue.invokeLater() or similar.
Another common bug is to hog the UI thread (so no rendering happens because you do complex calculations there). That's what the SwingWorker is for. Again, in most methods of the SwingWorker, calling methods which would render something is forbidden (-> use invokeLater()).
So my guess is that the UI thread waits for the lock and the lock simply isn't unlocked early or often enough. See this demo how to do a simple animation in Swing.
public class TimerBasedAnimation extends JPanel implements ActionListener {
public void paint(Graphics g) {
// setup
// do some first-run init stuff
// calculate the next frame
// render frame
}
public void actionPerformed(ActionEvent e) {
repaint();
}
public static void main(String[] args) {
JFrame frame = new JFrame("TimerBasedAnimation");
frame.add(new TimerBasedAnimation());
...
}
}
As you can see in the code doesn't lock. Instead, you just send "render now" events from actionPerformed to Swing. Some time later, Swing will call paint(). There is no telling (and no way to make sure or force Swing) when this will happen.
So good animation code will take the current time, calculate the animation state at that time and then render it. So it doesn't blindly step through N phases in M seconds. Instead, it adjusts for every frame to create the illusion that the animation is smooth when it really isn't.
Related:
Java: Safe Animations with Swing
How to Use Swing Timers
First of all, apologies for how long winded this is.
I'm trying to make a simple roulette game that allows a user to add players, place bets for these players, and spin the roulette wheel, which is represented as a simple JLabel that updates it's text with each number it passes.
However, I've run into a bug that I'm having a lot of trouble with: the JLabel only updates the text for the last element in my loop.
Basically, my solution works like this:
When a user presses a button labelled "Spin" (given that users have been added to the game), I call a method from a class called SpinWheelService, which is an Observable singleton which in turn calls the notifyObservers() method:
public void actionPerformed(ActionEvent e) {
String cmd = e.getActionCommand();
String description = null;
if (ADD_PLAYER.equals(cmd)) {
addDialog();
} else if (PLACE_BET.equals(cmd)) {
betDialog();
} else if (SPIN.equals(cmd)) {
SpinWheelService.sws.setSpinWheelService();
} else if (DISPLAY.equals(cmd)) {
System.out.println("Display selected!");
}
}
Here is my SpinWheelService class:
package model;
import java.util.*;
public class SpinWheelService extends Observable {
public static SpinWheelService sws = new SpinWheelService();
public void setSpinWheelService() {
setChanged();
notifyObservers();
}
}
The only listener registered for SpinWheelService is this class, where GameEngine is my game engine that handles internal game logic, WheelCallbackImpl is a class that updates the View:
class SpinWheelObserver implements Observer {
GameEngine gameEngine;
ArrayList<SimplePlayer> players;
WheelCallbackImpl wheelCall;
int n;
public SpinWheelObserver(GameEngine engine, WheelCallbackImpl wheel, ArrayList<SimplePlayer> playerList) {
players = playerList;
gameEngine = engine;
wheelCall = wheel;
}
public void update(Observable sender, Object arg) {
// check if any players are present
if (players.size() == 0) {
System.out.println("Empty player array!");
return;
}
do {
gameEngine.spin(40, 1, 300, 30, wheelCall);
n = wheelCall.playback();
} while (n== 0);
}
}
The main point of note here is my gameEngine.spin() method, which is this:
public class GameEngineImpl implements GameEngine {
private List<Player> playerList = new ArrayList<Player>();
// method handles the slowing down of the roulette wheel, printing numbers at an incremental delay
public void delay(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
System.out.println("Sleep method failed.");
}
}
public void spin(int wheelSize, int initialDelay, int finalDelay,
int delayIncrement, WheelCallback callback) {
Random rand = new Random();
int curNo = rand.nextInt(wheelSize) + 1;
int finalNo = 0;
assert (curNo >= 1);
// while loop handles how long the wheel will spin for
while (initialDelay <= finalDelay) {
delay(initialDelay);
initialDelay += delayIncrement;
// handles rotating nature of the wheel, ensures that if it reaches wheel size, reverts to 1
if (curNo > wheelSize) {
curNo = 1;
callback.nextNumber(curNo, this);
curNo++;
}
assert (curNo <= wheelSize);
callback.nextNumber(curNo, this);
curNo++;
finalNo = curNo - 1;
}
calculateResult(finalNo);
callback.result(finalNo, this);
}
The method callback.nextNumber(curNo, this):
public void nextNumber(int nextNumber, GameEngine engine) {
String strNo = Integer.toString(nextNumber);
assert (nextNumber >= 1);
System.out.println(nextNumber);
wcWheel.setCounter(strNo);
}
Where in, wcWheel is my singleton instance of my View, which contains the method setCounter():
public void setCounter(String value) {
label.setText(value);
}
Sorry for how convoluted my explanation is, but basically what it boils down to is that setCounter() is definitely being called, but seems to only call the setText() method on the final number. So what I'm left with is an empty label that doesn't present the number until the entire roulette has finished spinning.
I've determined that setCounter() runs on the event dispatch thread, and I suspect this is a concurrency issue but I have no idea how to correct it.
I've tried to include all relevant code, but if I'm missing anything, please mention it and I'll post it up as well.
I'm at my wits end here, so if anyone would be kind of enough to help, that would be so great.
Thank you!
Your while loop along Thread.sleep() will block and repainting or changing of the UI until the loop is finished.
Instead you'll want to implement a javax.swing.Timer for the delay, and keep a counter for the number of ticks, to stop it. You can see more at How to Use Swing Timers
The basic construct is
Timer ( int delayInMillis, ActionListener listener )
where delayInMillis is the millisecond delay between firing of an ActionEvent. This event is listened for by the listener. So every time the event is fired, the actionPerfomed of the listener is called. So you might do something like this:
Timer timer = new Timer(delay, new ActionListener()(
#Override
public void actionPerformed(ActionEvent e) {
if (count == 0) {
((Timer)e.getSource()).stop();
} else {
//make a change to your label
count--;
}
}
));
You can call timer.start() to start the timer. Every delay milliseconds, the label will change to what you need it to, until some arbitrary count reaches 0, then timer stops. You can then set the count variable to whatever you need to, if you want to to be random, say depending on how hard the wheel is spun :D
I think you didn't post all the relevant code that is required to know exactly the problem.
But most likely the problem is due to you run your loop and JLabel.setText() in the EDT (Event Dispatching Thread).
Note that updating the UI components (e.g. the text of a JLabel) also happens in the EDT, so while your loop runs in the EDT, the text will not be updated, only after your loop ended and you return from your event listener. Then since you modified the text of the JLabel it will be refreshed / repainted and you will see the last value you set to it.
Example to demonstrate this. In the following example a loop in the event listener loops from 0 to 9 and sets the text of the label, but you will only see the final 9 be set:
JPanel p = new JPanel();
final JLabel l = new JLabel("-1");
p.add(l);
JButton b = new JButton("Loop");
p.add(b);
b.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
for ( int i = 0; i < 10; i++ ) {
l.setText( "" + i );
try { Thread.sleep( 200 ); } catch ( InterruptedException e1 ) {}
}
}
} );
A proposed solution: Use javax.swing.Timer to do the loop's work. Swing's timer calls its listeners in the EDT so it's safe to update swing components in it, and once the listener returns, a component UI update can happen immediately:
JPanel p = new JPanel();
final JLabel l = new JLabel("-1");
p.add(l);
JButton b = new JButton("Loop");
p.add(b);
b.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
new Timer(200, new ActionListener() {
int i = 0;
#Override
public void actionPerformed(ActionEvent e2) {
l.setText("" + i);
if ( ++i == 10 )
((Timer)e2.getSource()).stop();
}
}).start();
}
} );
In this solution you will see the label's text counting from 0 up to 9 nicely.
It's appears to me that your entire game must block in the action handler until the while loop has finished? So the text of the label will be getting updated but only the last update will be visible once the AWT thread is running again.