Calling a method from a JButton is freezing a JFrame? - java

I'm doing a basic Pong game for a class. I have the Pong working, and I have a GUI display on startup, unfortunately I can't seem to start the game from the start JButton. I've commented where the problem is on the code, and removed irrelevant code.
frame.add(GUIPanel);
JButton startButton = new JButton("Start!");
GUIPanel.add(startButton, BorderLayout.CENTER);
startButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e)
{
frame.getContentPane().remove(GUIPanel);
frame.validate();
frame.repaint();
drawPanel = new DrawPanel();
drawPanel.requestFocus();
frame.getContentPane().add(BorderLayout.CENTER, drawPanel);
//This is the part that freezes it, everything else works fine
//except that the playGame method isn't called. If I remove the whole
//startButton and whatnot I can call playGame and it works perfectly.
playGame();
}
});
}
any ideas?

Swing is a single threaded framework.
That is, all interactions and modifications to the UI are to be made from within the context of the Event Dispatching Thread. Anything that blocks this thread will prevent it from processing, amongst other things, repaint requests and user input/interactions.
My guess is that playGame is using something like Thread.sleep or some kind of while(true) and is blocking the EDT, causing your program to appear as if it's frozen
Have a read through Concurrency in Swing for more details.
A simple solution would be to use a Swing Timer to act as you game loop. Each time it ticks, you would update the state of your game and call (something like) repaint on you game's component

It looks like you've got a parenthesis that doesn't belong next to your second to last semicolon. Try removing it.

Related

JPanel doesn't repaint if repaint() is called within JFrame code

I have a class Forest and CellularJPanel, which extends JPanel and displays Forest. I wrote a primitive code to create JFrame, Forest, CellularJPanel and add CellularJPanel to the JFrame. Next is an infinite loop, which makes Forest update and CellularJPanel repaint.
JFrame jFrame = new JFrame();
Forest forest = new Forest();
CellularJPanel forestJPanel = new CellularJPanel(forest);
jFrame.add(forestJPanel);
jFrame.pack();
//jFrame.setResizable(false);
jFrame.setLocationRelativeTo(null);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.setVisible(true);
while (true)
{
try
{
forestJPanel.repaint();
forest.update();
forest.sleep(); // calls Thread.sleep(...)
}
catch (InterruptedException e)
{
}
}
Here is a code of the CellularJPanel class:
public class CellularJPanel extends JPanel
{
private CellularAutomata cellularAutomata;
public CellularJPanel(CellularAutomata cellularAutomata)
{
super();
this.cellularAutomata = cellularAutomata;
setPreferredSize(this.cellularAutomata.getDimension());
}
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D graphics2D = (Graphics2D)g;
cellularAutomata.draw(graphics2D);
}
}
If I use above code within main() method, then everything works fine,
CellularJPanel repaints, paintComponent() is called normally.
If I paste the same code to UI JFrame button click event method, then new JFrame shows and even displays initial state of the Forest, because paintComponent is once called, when jFrame.setVisible(true) is called. Then while loop is being executed, but CellularJPanel doesn't repaint, paintComponent is not called. I have no idea why, maybe I should use SwingUtilities.invokeLater(...) or java.awt.EventQueue.invokeLater somehow, but I've tried them and it didn't work, I'm doing something wrong.
Any suggestions?
P.S.
My target is to display CellularJPanel within the same UI JFrame, from which button was clicked. But even if I add this panel to main UI JFrame, it doesn't work.
Your problem is having a while(true) on the Event Dispatch Thread which will block anything related to UI because UI events aren't getting handled anymore.
The event dispatch thread (a single thread) works down a queue of UI event messages, until it handles the one where your while(true) loop is running. It then blocks any further processing because there's an infinite loop on it. Calling SwingUtilities.invokeLater from that loop won't help because it posts an event to the event dispatch thread, which is blocked in the while(true) loop.
So remove that loop, instead use a javax.swing.Timer to time your events. In the timer event, change the state of your UI and call for a repaint. The timer event will be synchronized with the UI thread, so changing state of UI components is allowed.
There is a single UI thread that draws things - it's also the one that handles button clicks. In swing, this is called the event dispatch thread. If The UI thread is busy running a while loop, it can't paint.
You can quickly verify this by making your button click handler run only a single iteration of your loop (without the sleep): forest.update(); forestJpanel.repaint();
You can auto update from a separate thread (like Timer) that calls repaint/sleep in a loop.

Updating swing components within edt

If I understand correctly then when I create GUI swing components, for example I have this:
public class frameExample extends JFrame{
public frameExample(){
//Here adding bunch if components
setVisible(true);
}
}
So as long as I don't call the setVisible method the components are being made from the thread the instance was created. So if in a class where i have my main method write:
JFrame test=new frameExample();
and I sysout
Thread.currentThread.getName();
in the constructor in frameExample just before setVisible I should get: main.
After that the responsibility of creating and maintaining swing elements are passed to the event-dispatch-thread and because it is not thread safe every component add/remove/modify should be done within the EDT thread.
So I should place my setVisible as the last line of code in my constructor, or call it separately.
As I understand all event listening goes through the EDT. So if I create a new component within for example an actionPerformed method it should do fine.
Also if I pass a runnable instance to invokeLater or invokeAndWait then all the run() method will be done by the EDT.
So here is why I'm confused.
I made this code:
public class GUI extends JFrame {
JButton btn = new JButton("Change");
JMenuBar m = new JMenuBar();
public GUI() {
super("Test");
setSize(400, 400);
setDefaultCloseOperation(EXIT_ON_CLOSE);
m.add(new JMenu("menu"));
add(m, BorderLayout.NORTH);
add(btn, BorderLayout.SOUTH);
System.out.println("Current thread: before setVisible "+Thread.currentThread().getName());
setVisible(true);
System.out.println("Current thread: after setVisible "+Thread.currentThread().getName());
btn.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
add(new JButton("testbtn1"), BorderLayout.EAST);
add(new JButton("testbtn2"));
System.out.println("Current thread: "+Thread.currentThread().getName());
new Thread(new Runnable() {
#Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 1E8; i++) {
Math.sin(5.0);
}
System.out.println("Current thread: "+Thread.currentThread().getName());
}
}).start();
}
});
}
}
So in my anonim class where I add the two buttons im in the EDT, but the components are not added to my frame just after i resize it (which forces the edt to update its components??? what is the reason for this?).
So i dont get my new components even within edt, however when i create a random thread outside edt and make it change some gui element property (for example setText) in just works fine outside the edt.
So my first question is: why my components are not updated within edt and why they are visible after resize
second one: why can i make changes to swing components outside edt and everything works fine? Is this just random thread behvior where for example things just work fine without sync block but when you rerun you program at some point it will eventually crash because the lack of sync.
So as long as I don't call the setVisible method the components are being made from the thread the instance was created
Wrong. As long as you do not specifically create a new Thread or use a utility method (like for example the SwingUtilities#invoke... methods), each call is invoked on the current Thread, including the setVisible call.
It looks like you think that making Swing components visible somehow makes your code switch threads. No, the painting of the Swing components will happen on the EDT. And as you correctly stated, Swing is not thread safe.
That is why you should create the component on the EDT as well, and not on another thread. It might work without problems the majority of the time, but eventually you will stumble upon weird bugs.
As I understand all event listening goes through the EDT. So if I create a new component within for example an actionPerformed method it should do fine.
Also if I pass a runnable instance to invokeLater or invokeAndWait then all the run() method will be done by the EDT.
Correct.
So in my anonim class where I add the two buttons im in the EDT, but the components are not added to my frame just after i resize it
Adding components to a Container requires you to revalidate the layout (see the javadoc of the Container#add method). Just call
revalidate();
repaint();
after you add the buttons and it will work as expected. Manually resizing the frame has the same effect as you already noticed.
So i dont get my new components even within edt, however when i create a random thread outside edt and make it change some gui element property (for example setText) in just works fine outside the edt.
As stated before, it might work most of the time, but there is no guarantee it will work 100% of the time. The reason the adding of your components did not work as expected is explained above, and has nothing to do with threading issues.

Why doesn't setText work for JLabel component?

I have a JLabel which I want to change momentarily, here is the code I have written to do so:
infoLabel.setText("Added");
try {
TimeUnit.MILLISECONDS.sleep(300);
}
catch(InterruptedException ex)
{
}
infoLabel.setText("Action"); //funny part is when I comment this line it works
My default text for the label is 'Action'
Swing is a single threaded frame work, that means, if you do anything that stops this thread, then it can't respond to any new events, including paint requests.
Basically, TimeUnit.MILLISECONDS.sleep(300) is causing the Event Dispatching Thread to be put to sleep, preventing it from processing any new paint requests (amongst other things).
Instead, you should use a javax.swing.Timer
Take a look at
Concurrency in Swing
How to use Swing Timers
For more details
For example...
infoLabel.setText("Added");
Timer timer = new Timer(300, new ActionListener() {
public void actionPerformed(ActionEvent evt) {
infoLabel.setText("Action");
}
});
timer.setRepeats(false);
timer.start();
Note, 300 milliseconds is a really short time, you might like to start with a value a little larger like 2000, which is 2 seconds ;)
You're sleeping the Swing event thread putting the entire GUI to sleep. Don't do that. Use a Swing Timer instead.
Your application is run on a single thread, so when you sleep the thread, you prevent it from making any GUI updates.
Are you sure you are doing things properly? By doing everything (including sleep) in the GUI thread, it will always be busy and never get back to Java in order to let the GUI be redrawn.
Search for EDT (Event dispatch thread) for more info. Here is one question on the subject: Processing code doesn't work (Threads, draw(), noLoop(), and loop())

Flow of execution when using Swing

I'm just getting to grips with GUI programming in java. Here is a trivial program (from O'Reilly's "Head First Java") which on the face of it looks easy to understand, but there's an aspect of it which I don't follow.
import javax.swing.*;
public class Test {
public static void main(String[] args) {
JFrame frame=new JFrame();
JButton button = new JButton("click me");
frame.getContentPane().add(button);
frame.setSize(300,300);
frame.setVisible(true);
}
}
This simple program, when compiled and run, will open a window with a button on it.
What I don't understand is what is happening with the flow of execution. When I run this program, the static main method of the Test class runs, all the commands in main() are executed -- so why doesn't the process terminate after the window appears? Why am I still sitting on what looks like an infinite loop? What is looping?
If I add the line
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
then I find the result even more imcomprehensible. Now, of course, the program terminates
once I've closed the window. But again I don't see why. The frame will be on the stack but I don't see where the program flow is and just the existence of something on the stack is not enough to keep the program alive, surely? I'm missing something fundamental which as far as I can see is not covered in the book I'm reading. I am slightly surprised by this -- "Head first Java" has been very good up until now at pointing out subtleties and explaining what is really going on, but doesn't seem to address this point (at least not that I've spotted).
why doesn't the process terminate after the window appears?
Because the Java Virtual Machine exits only after all non-daemon threads have finished. While not apparent, there's in fact two threads in your program: the main thread, and the event dispatching thread, which does everything related to the Swing GUI components. The event dispatching thread keeps going as long as any GUI components are visible.
Actually the program, while it may work, is wrong, because you're creating and accessing Swing components from the main thread. You ought to be doing all GUI work in the event dispatching thread. That is, it should be something like:
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
JFrame frame=new JFrame();
JButton button = new JButton("click me");
frame.getContentPane().add(button);
frame.setSize(300,300);
frame.setVisible(true);
});
}
The Java process terminates when the last non-demon thread dies. Normally there is just one, the main thread. When you display Swing components additional non-demon threads for event dispatching and GUI shotdown are started. Those terminate when the last top-level component gets disposed. In your sample the main thread dies after leaving the main method. You can have a look into the threads with a debugger or jvisualvm from the JDK tools.
The rest of the GUI flow is event driven. When you e.g. click on the frame's close button an event is generated and sent to the appropriate listeners within the event dispatching thread.
Setting JFrame.EXIT_ON_CLOSE as default close operation is like adding a default event listener to the frame. A quite harsh one, it just shuts down the JVM without respect to the rest of the application's state.

How to keep Java Frame from waiting?

I am writing a genetic algorithm that approximates an image with a polygon. While going through the different generations, I'd like to output the progress to a JFrame. However, it seems like the JFrame waits until the GA's while loop finishes to display something. I don't believe it's a problem like repainting, since it eventually does display everything once the while loop exits. I want to GUI to update dynamically even when the while loop is running.
Here is my code:
while (some conditions) {
//do some other stuff
gui.displayPolygon(best);
gui.displayFitness(fitness);
gui.setVisible(true);
}
public void displayPolygon(Polygon poly) {
BufferedImage bpoly = ImageProcessor.createImageFromPoly(poly);
ImageProcessor.displayImage(bpoly, polyPanel);
this.setVisible(true);
}
public static void displayImage(BufferedImage bimg, JPanel panel) {
panel.removeAll();
panel.setBounds(0, 0, bimg.getWidth(), bimg.getHeight());
JImagePanel innerPanel = new JImagePanel(bimg, 25, 25);
panel.add(innerPanel);
innerPanel.setLocation(25, 25);
innerPanel.setVisible(true);
panel.setVisible(true);
}
However, it seems like the JFrame
waits until the GA's while loop
finishes to display something. I don't
believe it's a problem like repainting
Yes, if the looping code execute on the EDT then the GUI can't repaint itself until the loop finishes. The looping code should execute in its own Thread so it doesn't block the EDT.
Read the section from the Swing tutorial on Concurrency for more information.
I think your problem is that Java won't let you update the GUI from another thread than the GUI thread itself. This causes grief to everybody at some point, but fortunately a reasonably convenient workaround is provided.
The idea is to pass the code that does the updating as a Runnable to the method SwingUtilities.invokeAndWait or SwingUtilities.invokeLater. Here's an example.
To run your GA at maximum speed and exploit parallelism, I guess invokeLater would be appropriate.
EDIT: Oh wait, camickr's solution hints that you're doing something else: You're running the GA in the GUI's thread. Well, that can only do one or the other, calculate or display. So the true solution would combine both changes:
Run the GA in a separate thread (you could run it in the thread used by main() after you've instantiated the GUI); and
Use invokeLater to communicate updates to the GUI thread (which camickr calls the EDT, or Event Dispatch Thread).

Categories

Resources