I created a maze generator in swing for a class, and it works great. The only thing is, I want to show the maze being created in realtime, but the way I have everything set, it only updates after all the calculations have completed. I am using paintComponent and repaint in my code. How do I have it show the JFrame and draw lines immediately, rather than doing the algorithm and showing them all at the end?
Here is the relevant code:
public void generateMaze() {
Stack<Box> stack = new Stack<>();
int totalCells = Finals.numCol * Finals.numRow, visitedCells = 1;
Box currentCell = boxes[0][0];
Box nextCell;
stack.add(currentCell);
while (visitedCells < totalCells) {
nextCell = checkNeighbors(currentCell.xCoord, currentCell.yCoord);
if (nextCell != null) {
knockWalls(currentCell, nextCell);
stack.add(currentCell);
currentCell = nextCell;
visitedCells++;
} else {
currentCell = stack.pop();
}
}
repaint();
}
Here is my paintComponent method override
public void paintComponent(Graphics g) {
for(int x = 0; x < Finals.numRow; x++) {
for(int y = 0; y < Finals.numCol; y++) {
if(boxes[y][x].top != null)
boxes[y][x].top.paint(g);
if(boxes[y][x].bottom != null)
boxes[y][x].bottom.paint(g);
if(boxes[y][x].left != null)
boxes[y][x].left.paint(g);
if(boxes[y][x].right != null)
boxes[y][x].right.paint(g);
}
}
}
The knockWalls method sets certain walls equal to null, which causes them to not be drawn in the paintComponent method. I'm still fairly new at a lot of this, so I apologize if some of the code isn't super high quality!
Thanks everyone.
As MadProgrammer already pointed out in the comments, you are almost certainly blocking the Event Dispatch Thread. This is the thread that is responsible for repainting the GUI, and for handling the interaction events (like mouse clicks and button presses).
So presumably, you start the computation via a button click, roughly like this:
// The actionPerformed method of the button that
// starts the maze solving computation
#Override
void actionPerformed(ActionEvent e)
{
generateMaze();
}
That means that the event dispatch thread will be busy with executing generateMaze(), and not be able to perform the repainting.
The simplest solution would be to change this to something like
// The actionPerformed method of the button that
// starts the maze solving computation
#Override
void actionPerformed(ActionEvent e)
{
Thread thread = new Thread(new Runnable()
{
#Override
public void run()
{
generateMaze();
}
});
thread.start();
}
However, some care has to be taken: You may not modify Swing components from this newly created thread. If you have to modify Swing components, you have to put the task that performs the actual modification of the Swing component back on the EDT, using SwingUtilities.invokeLater(task). Additionally, you have to make sure that there are no other synchronization issues. For example, the lines
if(boxes[y][x].top != null)
boxes[y][x].top.paint(g);
are still (and have to be!) executed by the event dispatch thread. In this case, you have to make sure that no other thread can set the boxes[y][x].top to null after the EDT has executed the first line and before it executes the second line. If this may be an issue in your case, you might have to provide a bit more code, e.g. the code that is showing where and how the boxes[y][x] are modified.
Related
I try to design a GUI for a neural network I made recently. I am using the MNIST-dataset and want to display the handwritten digit using JPanels with the brightness-values written inside. By pressing the "train"-button the network gets trained and every new digit is displayed. However this happens in a for loop in the actionperformed method of the button and it seems that I can´t change the background of the labels or the text(at least it doesn´t display the changes) until the last one. I don´t know whether I´m right but it seems that only the last change gets displayed. That´s why my question is whether it is possible to "refresh" the JFrame inside the actionperformed method.
I already have tried revalidate(), invalidate() & validate(), SwingUtilities.updateComponentTreeUI(frame), but none of them worked.
Here is the relevant part of my code:
train.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
for (int i = 0; i < iMax; i++) {
...
digitRefresh(reader.getInputs()[i], (int) reader.getInputs()[i][0], 0);
}
}
});
.
public void digitRefresh(double[] pixelValue, int target, int result) {
for (int i = 0; i < 784; i++) {
double value = pixelValue[i + 1];
int brightness = (int) (value * 255);
l_digit[i].setText(String.valueOf(value));
l_digit[i].setBackground(new Color(brightness, brightness, brightness));
}
l_target.setText(String.valueOf(target));
l_result.setText(String.valueOf(result));
this.revalidate();
}
thank you for every awnser and sorry for my bad english.
The simplest thing to do is start a new thread.
#Override
public void actionPerformed(ActionEvent e) {
new Thread( ()->{
for (int i = 0; i < iMax; i++) {
...
final int fi = i;
EventQueue.invokeLater( ()->{
digitRefresh(reader.getInputs()[fi], (int) reader.getInputs()[fi][0], 0);
});
}).start();
}
Now all of the work is being done on a separate thread, then as the ... work finishes, digit refresh method called from the EDT. Notice the final int fi part. There are caveats about going back and forth on threads so it is good to look into better controls than just using thread.
Swing worker for example:
How do I use SwingWorker in Java?
I have a java program which is basically a game. It has a class named 'World'. The "World" class has a method 'levelChanger()', and another method 'makeColorArray()'.
public class World {
private BufferedImage map, map1, map2, map3;
private Color[][] colorArray;
public World(int scrWd, int scrHi) {
try {
map1 = ImageIO.read(new File("map1.png"));
map2 = ImageIO.read(new File("map2.png"));
map3 = ImageIO.read(new File("map3.png"));
} catch (IOException e) {
e.printStackTrace();
}
map = map1;
makeColorArray();
}
private void makeColorArray() {
colorArray = new Color[mapHi][mapWd]; // resetting the color-array
for(int i = 0; i < mapHi; i++) {
for(int j = 0; j < mapWd; j++) {
colorArray[i][j] = new Color(map.getRGB(j, i));
}
}
}
//color-array used by paint to paint the world
public void paint(Graphics2D g2d, float camX, float camY) {
for(int i = 0; i < mapHi; i++) {
for(int j = 0; j < mapWd; j++) {
if(colorArray[i][j].getRed() == 38 && colorArray[i][j].getGreen() == 127 && colorArray[i][j].getBlue() == 0) {
//draw Image 1
}
else if(colorArray[i][j].getRed() == 255 && colorArray[i][j].getGreen() == 0 && colorArray[i][j].getBlue() == 0) {
//draw Image 2
}
}
}
}
public void levelChanger(Player player, Enemies enemies) {
if(player.getRec().intersects(checkPoint[0])) {
map = map2;
//calls the color-array maker
makeColorArray();
}
else if(player.getRec().intersects(checkPoint[1])) {
map = map3;
makeColorArray();
}
}
public void update(Player player, Enemies enemies) {
levelChanger(player, enemies);
}
}
The makeColorArray() method makes a 2d array of type 'Color'. This array stores Color-objects from a PNG image. This array is used by the paint() method of the JPanel to paint the world on the screen.
The levelChanger() method is used to change the levels (stages) of the game when certain codition is true. It is the method which calls the makeColorArray() method to re-make the color-array while changing game-level.
The problem is that I have a game-loop which runs on a Thread. Now, the painting of swing components like JPanel is done on a different background thread by java. When the game-level is being changed, the color-array object is re-made. Now, like I previously said, the color-array object is used by the paint() method to paint the world on the screen. Sometimes, the color-array object is still being used by the background Thread (not finished painting) when, according to game logic, color-array object is re-made and its members set to null. This is causing a null-pointer exception, only sometimes. Clearly a race condition.
I want to know how can i stop my game thread from resetting the color-array until the background swing thread has finished painting.
Suggestions:
Use a Model-View-Control design for your program, or use one of the many similar variants.
Use a Swing Timer to drive your GUI game loop, but use real time slices, not the timer's delay, in your model to determine length of time between loop steps.
The model should run in the GUI Swing event thread.
But its long-running tasks should be run in background threads using a SwingWorker.
This is key: Don't update the model's data, the data used by the JPanel to draw, until the background thread has completed working on it. A PropertyChangeListener and SwingPropertyChangeSupport can be useful for this.
Be sure that the JPanel draws in its paintComponent(...) method, not its paint(...) method, and that you call the super method.
Best if you make the background image a BufferedImage, and have the JPanel draw that in its paintComponent(...) method for efficiency.
Be sure that all Swing GUI code, except perhaps repaint() calls, are called on the Swing event thread.
And yes, definitely read the Concurrency in Swing tutorial.
One way with minimal changes is to synchronize access to the color array.
I personally would have the shared data abstracted out to an individual class which is completely thread-safe, that way you wouldn't have to make sure two separate parts of the code base both have to know what to synchronize on (it seems like your class here does more than just handle the map at first glance, perhaps it is such a class that I am describing though).
private void makeColorArray() {
Color[][] colorArrayTemp = new Color[mapHi][mapWd]; // resetting the color-array
for(int i = 0; i < mapHi; i++) {
for(int j = 0; j < mapWd; j++) {
colorArrayTemp [i][j] = new Color(map.getRGB(j, i));
}
}
synchronized(colorArray)
{
colorArray = colorArrayTemp;
}
}
//color-array used by paint to paint the world
public void paint(Graphics2D g2d, float camX, float camY) {
synchronized(colorArray)
{
for(int i = 0; i < mapHi; i++) {
for(int j = 0; j < mapWd; j++) {
if(colorArray[i][j].getRed() == 38 && colorArray[i][j].getGreen() == 127 && colorArray[i][j].getBlue() == 0) {
//draw Image 1
}
else if(colorArray[i][j].getRed() == 255 && colorArray[i][j].getGreen() == 0 && colorArray[i][j].getBlue() == 0) {
//draw Image 2
}
}
}
}
}
If you only want one thread modifying colorArray at once, make it synchronized.
The purpose of synchonization, is that it requires a thread to get the lock on the object. Any other thread that attempts to access that object while its locked will be blocked (it will wait).
See this post: Java: how to synchronize array accesses and what are the limitations on what goes in a synchronized condition
i need a timer for my game...
i searched a lot but without luck.
please help.
this is my mouse event:
public void mouseClicked(MouseEvent e) {
mouseX = e.getX();
mouseY = e.getY();
if(shot == false){
Ink = 0;
}
if(ready == true){
shot = true;
// I need a timer here to wait a second and then stop shooting.
}
}
Again, use a Swing Timer:
// code not compiled nor tested. It was typed free-hand.
// so it was not meant to be copy, pasted and used, but rather to show you
// the idea.
public void mouseClicked(MouseEvent e) {
mouseX = e.getX();
mouseY = e.getY();
// don't use if (shot == false). Instead do:
if (!shot) {
Ink = 0;
}
// likewise, no need to use if (ready == true). Instead do:
if (ready) {
shot = true;
// turn off your ability to shoot here by setting a boolean.
ableToShoot = false;
// start a Swing Timer that does not repeat
// in the Timer turn back on the ability to shoot by setting a boolean
Timer swingTimer = new Timer(TIMER_DELAY_TIME, new ActionListener() {
public void actionPerformed(ActionEvent evt) {
// allow shots here
ableToShoot = true;
}
});
swingTimer.setRepeats(false);
swingTimer.start();
}
}
Notes:
Don't use Thread.sleep(...) unless you want to put your entire GUI to sleep as this will sleep the Swing event thread.
Don't use a java.util.Timer. Swing's threading model dictates that almost all swing calls be made on the Swing event thread. A Swing Timer is built to do just this, to be sure that all calls in the timer are called on the EDT, the Swing event thread. A java.util.Timer does not do this, and this will lead to occasional very difficult to debug threading bugs, the worst kind of bugs.
The Swing Timer Tutorial link.
The Swing event threading model tutorial link
i have here a strange behaviour of my graphical user interface.
At first here a piece of code:
/**
*
*/
#Override
protected Void doInBackground() throws Exception {
final ModelGameState actualGameState = controller.getActualGameState();
final ModelCoinState actualCoinState = (actualGameState.getPlayersTurn() == ModelConstants.PLAYER_ONE_ID? actualGameState.getCoinsPlayerOne() : actualGameState.getCoinsPlayerTwo());
final List<ModelCoinState> temp = MoveCalculator.getMoves(actualCoinState, this.cellID);
final CountDownLatch lock = new CountDownLatch(temp.size());
int time = 500;
for(int i = 0; i < temp.size(); i++) {
final int index = i;
Timer timer = new Timer(time, new ActionListener() {
public void actionPerformed(ActionEvent e) {
if(actualGameState.getPlayersTurn() == ModelConstants.PLAYER_ONE_ID) {
actualGameState.setCoinsPlayerOne(temp.get(index));
} else {
actualGameState.setCoinsPlayerTwo(temp.get(index));
}
controller.setActualGameState(new ModelGameState(actualGameState));
lock.countDown();
}
});
timer.setRepeats(false);
timer.start();
time += 500;
}
lock.await();
return null;
}
at second here my gui:
and here my problem: everytime lock.await is called my screen
looks like that:
As you can see, behind each of my circles the top left corner
of my gui is shown everytime lock.await() is called (At least i
think it is when lock.await()is called because when i delete lock.await()
i cant see the whole animation of my gui but i also cant
see this strange behaviour and that behaviour appears always
when the program is through all code of doInBackground().
What causes this strange behaviour?
not an answer only disagree with, my comments against, no reviews, not tried your code, apologize me that there are any reason, maybe my bad
doInBackground() is bridge between AWT/Swing EDT and Workers Thread(s), by default never notified EDT
process, publish, setProgress and done() notify EDT
then Swing Timer inside doInBackground() is against all intentions, why was SwingWorker implemented in official APIs, there is place to execute long running, hard or non_safe code
again SwingWorker is designated as bridge between AWT/Swing and Workers Thread(s)
_____________________________
there are two ways
use CountDownLatch with invokeLater() or Swing Timer. don't mix that together nor from SwingWorker
use CountDownLatch, util.Timer, SheduledExecutor with notify EDT by wrap (only relevant, only output, methods will be repainted on the screen) Swing methods to the invokeLater()
use only Swing Timer (non_accurate on hour period)
I am using java.
I have a click event that adds "squares" to a container, in a loop. I want each square to show up RIGHT when it is added. I tried running the 'adding of the squares' in a separate thread, but it is not working.
Here is some code I use for 'public class GuiController implements ActionListener, MouseListener':
#Override
public void mouseClicked(MouseEvent e)
{
//createBoardPane();
new Thread
(
new Runnable()
{
public void run()
{
showAnimation();
}
}
).start();
}
public void showAnimation()
{
for(int i = 0; i < model.getAnimationList().size(); i++)
{
String coord = model.getAnimationList().get(i);
int x = Integer.parseInt(coord.substring(0, coord.indexOf(',')));
int y = Integer.parseInt(coord.substring(coord.indexOf(',') + 1, coord.length() - 2));
boolean shouldPlacePiece = (coord.charAt(coord.length() - 1) == 'p');
if(shouldPlacePiece)
{
model.getView().getBoardPane().getComponent(x + (y * model.getBoardSize())).setBackground(Color.BLACK);
}
else
{
model.getView().getBoardPane().getComponent(x + (y * model.getBoardSize())).setBackground(Color.WHITE);
}
model.getView().getBoardPane().repaint();
long time = System.currentTimeMillis();
while((System.currentTimeMillis() - time) < 250)
{
// wait loop
}
}
}
any help is appreciated!
Creating a separate Thread to run for this longish-running task was an excellent idea - unless you want to lock-up interactions with your GUI while doing your animation.
Now, Swing GUI objects are not Thread safe (with few exceptions), so you cannot work with them from a thread other than Swing's Event Dispatch Loop's thread. So take all the GUI update code in your for-loop, and wrap it with a new Runnable (yes, another one).
Then call SwingUtilities.invokeLater(Runnable doRun) with that Runnable on each iteration of the loop.
Your GUI update code will then be scheduled to run ASAP on the Event Dispatch Loop, which will occur while your worker thread goes to sleep (do you have anything against Thread.sleep?).
Alternative: Use SwingWorker instead of Thread
SwingWorker will create and manage a new the Thread for you, and publish data that it (SwingWorker) will cause to be run on the Event Dispatch Loop's thread. You'll override doInBackground with your code. Call publish with parameters to push across into the Event Dispatch Thread. Override process with code to process those parameters and update your GUI.
On gotcha with SwingWorker is that it accumulates published events over a period of about 33 milliseconds. If you're publishing more frequent than that you may get all your events bunched together every 33 milliseconds or so. In you case, 250 milliseconds between updates shouldn't be a problem.