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.
Related
I have a thread that I start when I press the start button. What I want to do is repaint the labels so they contain the information that my thread makes changes to. The only problem I am facing is that the jLabels repaint only after the thread is done running. Can someone give me any sugestions on how I can make it repaint while the thread is runnong? Thanks.
Here is a snippet of my code:
Store m = new Store(); //Store extends Thread
private void startActionPerformed(java.awt.event.ActionEvent evt) {
....
//I get the simulation time of the store from a textbox
//the thread runs for this number of seconds
//when it is done, the store is closed(the open variable is set to false)
....
m.start();
while (m.isOpen()) {
queue0.setText(String.valueOf(m.clientiCasai(0)));
//here I will have more queues
....
noOfClients.repaint(); //this is the label that should show the number of clients in queue 0
}
}
The problem is that the actual painting is also done during the EDT's event loop; your while() loop is basically preventing the EDT from proceeding.
One possible workaround would be to have an extra thread that would take care of updating the label.
Your startActionPerformed() method should not run on EventDispatchThread (EDT) which is the Thread that shall be used for all Swing Modification operations. If you block the EDT your UI will not repaint, it will freeze.
Your noOfClients.repaint() call should be done on EDT, but also your call setting the new value to queue0 label should be on EDT.
For simplification. If you your queue0.setText() call on EDT, the repainting will be done for you, so you can remove it.
This can be achieved by calling:
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
queue0.setText("<text>");
}
});
To solve your problem you could either pass a reference to the instance holding the method startActionPerformed() to your Store and call it from there when needed or you could start another Thread that monitors the Store progress and propagates it to the Swing 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.
In Java, I'm using an ActionListener for an array of JButtons. I would like for an earlier part of the ActionListener to set a new ImageIcon to a JButton, that change to be displayed immediately, then near the end of the ActionListener to set the JButton's ImageIcon back to null after a second long delay.
My problem is that none of the changes that happen to the JButton get displayed in the GUI window that it is set in until the ActionListener is completely finished, making the change in the JButton's ImageIcon unnoticeable. Is there any way to make an ActionListener commit a change to a JButton before it has finished executing the entire ActionListener, or should I be going about this differently?
The reason this is happening:
Swing repaints the buttons on the same thread (EDT) as the ActionListener is ran on. Hence if it is executing you ActionListener it cannot repaint since the thread is busy - as simple as that. You may have noticed that while your action listener is executing you also can't properly move your frames around etc. (GUI freezes up).
The solution:
Move heavy processing outside the EDT. It doesn't belong there anyway. As you could have guessed - use a background thread/thread pool for that. A good guide to it is Swing tutorial for concurrency
Notes:
As portrayed in the guide you do not want to modify components outside the EDT. As such the easiest strategy is to make a Runnable to execute on a background thread, start it, change the picture on the button and return without waiting for the task to finish.
public void actionPerformed(ActionEvent ae) {
Runnable task = new Runnable() {..};
executor.execute(task);
button.setIcon(newIcon);
return;
}
Note that this doesn't lock up the EDT for the task, hence allowing Swing to change the picture immediately.
This of course means that the user has no idea if the task has finished or not (And if there were any exceptions)! It is in the background after all! Hence there is an extra state of your execution: GUI is responsive and non-frozen, button is changed, but task is still running. In most applications this may be a problem (the user will spam the button or your background tasks may interleave). In that case you may want to use a SwingWorker to have a "processing" state as well.
public void actionPerformed(ActionEvent ae) {
new TaskWorker().execute();
button.setIcon(loadingIcon); //Shows loading. Maybe on button, maybe somewhere else.
return;
}
private class TaskWorker extends SwingWorker<Void, T> {
public Void doInBackground() {
//Do your task in the background here
}
protected void done() {
try {
get();
button.setIcon(doneIcon);
catch (<relevant exceptions>) {
button.setIcon(failedIcon);
}
}
}
Here done() is called on the EDT when doInBackground() is finished.
You could create a new Thread or a Thread from a Thread pool if you have one. And with that let the task work on a seperate Thread so that the ActionListener returns immediatly. And then in the other Thread you do your code and repaint the button. This is by the way a threory I'm not sure if it will work.
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.
Could someone teach me how to use a swing timer with the following purpose:
I need to have a polygon that begins being animated(simple animation such as rotating) when I click the mouse; and stops animating when I click again.
I do not have problems understanding the way the MouseListener works, but with the actual animation. I tried simulating the animation with a while block inside the paint() method where I would draw, erase and redraw the polygon(to simulate a rotation for example), but inside the while, the applet would not listen to the clicks. It would listen only after the while. I would need the swing timer to break the while when I click the mouse.
import javax.swing.Timer;
Add an attribute;
Timer timer;
boolean b; // for starting and stoping animation
Add the following code to frame's constructor.
timer = new Timer(100, new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
// change polygon data
// ...
repaint();
}
});
Override paint(Graphics g) and draw polygon from the data that was modified by actionPerformed(e).
Finally, a button that start/stop animation has the following code in its event handler.
if (b) {
timer.start();
} else {
timer.stop();
}
b = !b;
This example controls a javax.swing.Timer using a button, while this related example responds to a mouse click. The latter example reverses direction on each click, but start/stop is a straightforward alteration.
The applet won't listen to clicks because the main thread (the Event Dispatch Thread, EDT) is within the while-loop and isn't listening to your clicks.
You need another thread.
(try using SwingWorker http://download.oracle.com/javase/tutorial/uiswing/concurrency/worker.html)
So, the SwingWorker will do the while-loop in the background, publishing results to make your polygon move.
And the EDT can then focus on any events (like clicks). You can then just use the click-event to kill the SwingWorker if you want to stop it.
Good luck