I have implemented Conway's Game of Life problem in Java swing. Everything is working fine. As you can see in the screenshot below, whenever the "Tick" button is clicked, the game progresses to the next life form. Now, I am planning to include an "Autoplay" button alongside "Tick" button. The purpose of this autoplay is simple. When I hit it, an automated operation should carry on as if I am pressing tick button at an interval of 1 second.
I tried this. But this seems to block all the other operations. How to do this action in a separate thread? A small code snippet would get me going.
class AutoPlayListener implements ActionListener{
public void actionPerformed(ActionEvent e) {
if(e.getSource() == btnAutoPlay){
while(true){
Thread.sleep(1000); //InterruptedException try catch hidden
btnTick.doClick();
}
}
}
}
Use a javax.swing.Timer. It will be able to work with the existing ActionListener if the while(true) and Thread.sleep() calls are removed.
As #Ranman said you're blocking main UI thread. I believe SwingUtilities.invokeLater is usually used for things like this.
There are two options:
Start a new thread. The thread will contain the while loop, and execute a method that processes the array. In each iteration, call repaint() or invalidate() on your window to tell it that it needs redrawing.
Use a Timer. The GUI thread will call your routine at regular intervals.
Threads:
In actionPerformed method, create a new Thread. and call its start method.
The Runnable of the thread should run a while loop (as you have already done), and then simply exit.
Timer:
Create an object in your class of type Timer. Use the one in java.swing.Timer if you are using swing (there is also java.util.Timer which isn't good for GUI ops). The timer should have an ActionListener that calls your method once, but the Timer has a repeat rate of 1000ms.
Tips
to invoke the action, you should put it in a separate method, rather than directly under the button handler. That way, you aren't calling GUI stuff from outside the GUI thread.
e.g.
tickButton.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
doTick();
}
});
The mechanism to stop the thread is equally important! In general, don't use a while(true) in a thread as it will get lost; invent a semaphore to terminate it.
use a JToggleButton rather than Button?
Synchronization:
If you use threads, you will need something like this, to prevent new threads being created each time the button is pressed:
Code
Thread autoplayThread = null;
Object lock;
boolean autoplaying = false;
public void actionPerformed(ActionEvent e){
synchronized(lock){ // prevent any race condition here
if(!autoplaying && autoplayThread==null ){
autoplaying = true;
autoplayThread = new Thread(new Runnable(){
public void run(){
try{
while(autoplaying){ .... }
}finally{
synchronized(lock) {
autoplaying=false;
autoplayThread=null;
}
}
}
});
autoplayThread.start();
}else{ // stop the thread!
autoplaying=false;
}
}
}
Related
startbtn.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
isRun = true;
while(isRun)
runProgram();
}
});
stopbtn.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
isRun = false;
}
});
When I click the startbtn Button, the whole program just stopped and I can't handle anything.
What's wrong?
The problem is that when you set the variable isRun to true the program enters in the while loop without exiting. This happens in the same thread the GUI is running, so it becomes irresponsive and it cannot handle any other event (as the click to Stop the running action by setting isRun to false).
So, the solution is to run the while loop task in another thread, leaving the thread where the GUI is running free to handle more events.
To run the task in another thread I recommend you to use any of the classes in the concurrent framework such as ExecutorService
Going even further, if your purpose is to submit a task that may be canceled, you could even use Future.
Also, bear in mind to use some synchronization mechanism to guarantee both threads see the latest value assigned to isRun.
I have a little application counting time by pressing a button,
I just use thread.sleep() to count.
When the button is pressed, it triggers the ActionListener which is a class that perform a thread.run(). The thread.sleep() is then started from inside the run() function.
//The Thread
class twentyMins implements Runnable
#Override
public void run() {
try {
Thread.sleep(1000*60*20);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
}
//The ActionListener
class Reset implements ActionListener {
public static twentyMins count = new twentyMins();
#Override
public void actionPerformed(ActionEvent event) {
count.run();
}
}
The issue is, the button will not bounce up and be able to be pressed again.
And the application can't even be stopped by pressing the EXIT button on the JFrame.
Straightforwardly, I think my application is frozen while the thread.sleep() is running.
Is there something better then Thread.sleep()?
You didn't actually start a background thread here. Any object can implement Runnable (and the run method) but that doesn't make it a thread. Hence when your Reset button is clicked, it locks up the single thread responsible for the UI.
You need to pass your Runnable object to the constructor of the Thread class (java.lang.Thread), as described in the official docs.
What did you expect? You are calling
count.run();
Which will run in same main thread thereby blocking it for 20 mins. Consider creating a thread and calling start() on it.
To perform sleep() on main thread will block the UI.
You could create another thread or just use java.util.Timer class to finish this task.
I have a background thread which will not start processing until it's start variable becomes true:
class MyBackgroundThread implements Runnable {
// ...
public void run() {
while(true) {
if(!start) continue;
doSomethingWith(myValue);
}
}
}
The start variable is set to true from clicking a button on a JFrame which is of course running on the Event Dispatch Thread. There's also a myValue field in the background thread class, which is set from clicking the button:
startBtn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
backgroundThreadInstance.setMyValue(100);
backgroundThreadInstance.setStart(true);
// ...
}
});
As you can see, it assigns something to myValue before setting start to true. Does this mean that setting myValue and start as volatile is not required? Since myValue is written to first, it will be leaked to the background thread before start is, thus the background thread will never get the chance to process an uninitialised myValue?
Short answer is yes. Though, in practice, eventually the change to true would likely be seen by your thread, in theory it might never happen.
However, agree with #NamshubWriter that there are better ways to do this than an busy/idle loop. I like his proposal to set the integer and then submit it to an ExecutorService. e.g.
public void actionPerformed(ActionEvent e) {
BackgroundRunnableInstance runnable = new BackgroundRunnableInstance();
runnable.setMyValue(100); // could be in the constructor instead
someExecutorService.submit(runnable);
}
One difference is that if they hit the button multiple times you would have several runnables started. Which may or may not be what you want.
I'm making a chess program for a project. I'm trying to add a move history box to the side of the board. The move history works fine, and the data is properly sent to the text area, but the text inside the JTextArea disappears while the AI is thinking about his move.
public void aiMove(){
if (!playing){ return; }
paintImmediately(0,0,totalX,totalY);
ai = eve.getMove(chess,wtm,aiOut); //text disappears here
chess.makeMove(ai);
wtm = !wtm;
humanMove = true;
writeMove(ai); //updates move history, text reappears here
playing = stillPlaying();
repaint();
}
private void writeMove(Move move){
char c = "abcdefgh".charAt(7-move.fromY);
char h ="abcdefgh".charAt(7-move.toY);
String s = Character.toString(c)+(move.fromX+1)+" - "+Character.toString(h)+(move.toX+1)+" ";
if (!wtm){
String q = chess.getFullMove()+". "+s+" ";
moves.setText(moves.getText()+q);
}
else {
moves.setText(moves.getText()+s+"\n");
}
}
Here's a print screen of what's happening.
http://s13.postimage.org/mh7hltfk7/JText_Area_disappear.png
SOLVED
Thanks to all replies. I changed aiMove() so it creates a thread. Here is what I did.
Attempt #3... swing is still so foreign to me. I didn't want to change writeMove to getMove or I would have to rewrite the human's turn slightly. Since the project is essentially done, I am trying to avoid as much work as possible :)
The GUI is entirely optional anyways, I was just doing it for fun, and to try and learn a bit of swing.
public void aiMove(){
if (!playing){ return; }
if (!aiThread.isAlive()){
aiThread = new Thread(){
public void run(){
ai = eve.getMove(chess,wtm,aiOut);
chess.makeMove(ai);
wtm = !wtm;
humanMove = true;
SwingUtilities.invokeLater(new Runnable(){
public void run(){
writeMove(ai);
}
});
repaint();
playing = stillPlaying();
}
};
aiThread.start();
}
}
It also fixed a problem I had before, in that if I were to hold down the 'a' key (force ai move), it would queue up many forced ai moves. Now that doesn't happen.
The problem is your AI thinking is CPU intensive/time consuming, thus it is considered a long running task. You should not do long running tasks on GUI Event Dispatch Thread as this will cause the UI to seem frozen and thus only show updates after the task has finished.
Fortunately there are 2 different approaches you could use:
Use a Swing Worker which as the tutorial states:
The SwingWorker subclass can define a method, done, which is
automatically invoked on the event dispatch thread when the background
task is finished.
SwingWorker implements java.util.concurrent.Future.
This interface allows the background task to provide a return value to
the other thread. Other methods in this interface allow cancellation
of the background task and discovering whether the background task has
finished or been cancelled.
The background task can provide
intermediate results by invoking SwingWorker.publish, causing
SwingWorker.process to be invoked from the event dispatch thread.
The background task can define bound properties. Changes to these
properties trigger events, causing event-handling methods to be
invoked on the event dispatch thread.
Alternatively create separate Thread for AI thinking and wrap setText call in SwingUtilities.invokeLater(...);
Thread t=new Thread(new Runnable() {
#Override
public void run() {
}
});
t.start();
UPDATE
After reading MadProgrammers comment (+1 to it) please remember to create/manipulate your GUI/Swing components on EDT via the SwingUtilities.invokeLater(..) block. You can read more on it here.
UPDATE 2:
That edit is defeating the point, the only call on EDT in SwingUtilitites block should be the setText or atleast only code that manipulates a Swing component i.e
public void aiMove(){
if (!playing){ return; }
if (!aiThread.isAlive()){ //originally initialized by constructor
aiThread = new Thread(){
public void run(){
ai = eve.getMove(chess,wtm,aiOut);
chess.makeMove(ai);
wtm = !wtm;
humanMove = true;
SwingUtilities.invokeLater(new Runnable(){
public void run(){
writeMove(ai);
}
});
repaint();
playing = stillPlaying();
}
};
aiThread.start();
}
}
I have some code which takes a few minutes to process, it has to connect to the web for each string in a long array, each string is a url. I want to make it so that everytime it connects, it should refresh the jtextarea so that the user is not staring into a blank page that looks frozen for 20 min. or however long it takes. here is an example of something i tried and didnt work:
try {
ArrayList<String> myLinks = LinkParser.getmyLinksArray(jTextArea1.getText());
for (String s : myLinks) {
jTextArea2.append(LinkChecker.checkFileStatus(s) + "\n");
}
} catch (IOException ex) {
JOptionPane.showMessageDialog(jTextArea1, "Parsing Error", "Parsing Error", JOptionPane.ERROR_MESSAGE);
Logger.getLogger(MYView.class.getName()).log(Level.SEVERE, null, ex);
}
The problem is that you need to perform the computation asynchronously. You should create a background thread that performs the computation, and then use SwingUtilities.invokeLater to update the JTextArea.
final ArrayList<String> myLinks = //...
(new Thread()
{
public void run(){
for (String s : myLinks) {
try{
final String result = LinkChecker.checkFileStatus(s) + "\n";
SwingUtilities.invokeLater(new Runnable(){
public void run(){
jtextArea2.append(result);
}
});
}catch(IOException error){
// handle error
}
}
}
}).start();
Edit
It has been pointed out that JTextArea's append function actually is thread safe (unlike most Swing functions). Therefore, for this particular, case it is not necessary to update it via invokeLater. However, you should still do you processing in a background thread so as to allow the GUI to update, so the code is:
final ArrayList<String> myLinks = //...
(new Thread()
{
public void run(){
for (String s : myLinks) {
try{
jtextArea2.append(LinkChecker.checkFileStatus(s) + "\n");
}catch(IOException error){
// handle error
}
}
}
}).start();
However, for pretty much any other operation that modifies a Swing object, you will need to use invokeLater (to ensure the modification occurs in the GUI thread), since almost all the Swing functions aren't thread safe.
You need to investigate threading and its relationship to GUI updates in Swing. Anything that affects or makes use of GUI components in Swing must done on a special thread called the Event Dispatch Thread (EDT).
If your code snippet, if it's freezing the GUI, I imagine that it is being run in the EDT. Performing a long-running action on the EDT will make the GUI unresponsive, because no further updates can be done while your long-running process is using the thread.
There is a helper class called SwingWorker that allows you to offload long-running computations to a background thread, and then make updates to the GUI thread when it is complete. The SwingWorker looks after the context switches between the GUI thread and the background thread. You can also display progress bars to let the user know the state of the long-running process, so they know your application hasn't hung.
swing/awt is a single threaded library, so once a component is shown, just changing it's appearance won't work correctly. You need to change the component on the GUI Thread, not from your thread. To do this wrap any code that updates a component with SwingUtilities.invokeLater... as in
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
jTextArea2.append(LinkChecker.checkFileStatus(s) + "\n");
}
});
also you want to limit what you do on the gui thread to avoid the gui from becoming sluggish, so if checkFileStatus is time consuming, execute it outside the run method and store the result in a final local variable, and just access the variable in the run() code.