My SwingWorker doesn't stop the background thread from executing when I call .cancel(true); I've tried loads of different things - running the background process on it's own thread, calling the method directly, surrounding the method/process in try catch to force the process to jump out of the for loop and so on....for some reason the process just continues. Basically, there is JButton that when clicked starts the SwingWorker and background process, and when I click that button again, I want the process to stop. There is a fair bit of code, The GUI (PlayAKnit), the method/process which is part of my KnitGraph class and a SoundPlayer object which loops sounds. I will share what I can here:
code in GUI class
button.addActionListener(e -> {
KnitGraph test = new KnitGraph(new KnitSpeakIn("CherryStitch").getKnitFile());
Thread graph = new Thread(test);
SwingWorker startWorker = new SwingWorker() {
#Override
protected Void doInBackground() throws Exception {
test.parseCommands(0, test.getCommands().length);
return null;
}
};
if (button.getText().equals("KNIT")) {
button.setText("UNPICK");
button.setFont(new Font("Helvetica", Font.BOLD, 48));
startWorker.execute();
}
else {
startWorker.cancel(true);
button.setText("KNIT");
button.setFont(new Font("Helvetica", Font.BOLD, 72));
}
});
method that runs in background calls methods that make sounds
void parseCommands(int start, int end) throws InterruptedException {
int asterixStartLocation = 0;
int asterixEndLocation = 0;
for(int i = start; i<end;i++) {
System.out.println(commands[i]);
if (commands[i].contains("co")) {
System.out.println("casting on");
castOn(extractInt(commands[i]));
}
if (commands[i].contains("*")) {
if (!asterixFound) {
System.out.println("I found an asterix");
asterixFound = true;
commands[i] = commands[i].replace('*', ' ');
asterixStartLocation = i;
}
else {
System.out.println("I found another asterix!");
asterixFound = false;
asterixEndLocation = i;
commands[i] = commands[i].replace('*', ' ');
parseCommands(i, i+1);
parseCommands(asterixStartLocation, asterixEndLocation);
}
}
if (commands[i].matches("\\spurl|\\sknit|\\sPurl|\\sKnit")) {
for (int j = 0; j < stitchesInRowBelow; j++) {
knitOrPurl();
}
}
if (commands[i].contains("tog") || commands[i].contains("ssk")) {
if (commands[i].contains("ssk")) {
int count = 0;
for (int k = 0; k < commands[i].length(); k++) {
if ((commands[i]).charAt(k) == 's') {
count++;
}
}
decrease(count - 1);
}
else if (commands[i].contains("psso")) {
decrease(2);
}else {
decrease(extractInt(commands[i])-1);
}
}
if (commands[i].contains("yo")) {
yarnOver();
}
if (commands[i].contains("kfb")) {
increase();
}
if (commands[i].matches("\\sk\\d|\\sp\\d|\\sk\\d\\stbl|\\sp\\d\\stbl|\\s*k\\d|\\s*p\\d")) {
int noOfStitches = extractInt(commands[i]);
for (int n=0;n<noOfStitches;n++) {
knitOrPurl();
}
}
if (commands[i].contains("cast off")) {
castOff();
}
}
}
method in SoundPlayer class just loops random .wav files
public void run() {
File sound = new File(sounds[randomInt]);
try {
String url = sound.toURI().toURL().toString();
Clip clip = AudioSystem.getClip();
clip.open(AudioSystem.getAudioInputStream(new URL(url)));
clip.loop(Clip.LOOP_CONTINUOUSLY);
} catch(Exception e) {
e.printStackTrace();
}
}
}
Related
Actually i am using threads to update progress bar inside a block of code ,The run method of the thread is not working,So,could you please help me resolve the problem.I am using SWT toolkit for the UI.
CODE:
//selection listener
tItem.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e)
{
TableItem[] tableItems =table.getItems();
int c=0;
int inty=table.getSelectionIndex();
for(int i=table.getSelectionIndex();i<tableItems.length;i++)
{
if(c==0)
{
column = new TableColumn(table, SWT.CENTER);
//reading content from the table Stringline=tableItems[i].getText(0)+","+tableItems[i].getText(1)+","+tableItems[i].getText(2)+","+tableItems[i].getText(3)+","+tableItems[i].getText(4)+","+tableItems[i].getText(5)+","+tableItems[i].getText(6);
System.out.println("\n line value is "+line);
String split= ",";
String error=",,,,,,";
if(line.equals(error))
{
break;
}
else {while ((line!= null))
String[]com = line.split(split,20);
String result =null;
//checking whether the value in it is enable or n
if(com[0].contains("ENABLE"))
{ result = "RESULTS";
continue;}
if(com[0].equals("N"))
{result = "No";}
else if(com[2].startsWith("S=") )
{
String O=tableItems[i].getText(7);
String[] part = O.split("=");
String string2 = part[1];
a=Integer.parseInt(string2);
//declaration of the thread
final Thread thread = new Thread()
{public void run()
{for(int i=0; i<=a; i++)
{
final int value = i;
try {
Display.getDefault().asyncExec(newRunnable()
{
public void run()
{
//assigning value to the progress bar
pbar.setSelection(value);
}
});
}
catch (Exception e)
{
System.out.println("Warning");
}
}
}
thread.start();
inty++;
break;
}
}
});
I'm writing a program that constantly pings a server. I wrote the code to check it once and put the ping in a JLabel and put it in a method called setPing().
Here is my code
private void formWindowOpened(java.awt.event.WindowEvent evt) {
setPing();
}
That worked but only did it once, so I did:
private void formWindowOpened(java.awt.event.WindowEvent evt) {
for(;;){
setPing();
}
}
But this doesn't even work for the first time.
I didnt put the setPing method because it was too long so here it is:
public String setPing(){
Runtime runtime = Runtime.getRuntime();
try{
Process process = runtime.exec("ping lol.garena.com");
InputStream is = process.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line;
while ((line = br.readLine()) != null) {
int i = 0;
i = line.indexOf("Average");
if(i > 0){
String finalPing = "";
line.toCharArray();
try
{
finalPing = "";
for(int x = i; x < i + 17; x++)
{
finalPing = finalPing + (line.charAt(x));
}
}catch(IndexOutOfBoundsException e)
{
try
{
finalPing = "";
for(int x = i; x < i + 16; x++)
{
finalPing = finalPing + (line.charAt(x));
}
}catch(IndexOutOfBoundsException f)
{
try
{
finalPing = "";
for(int x = i; x < i + 15; x++)
{
finalPing = finalPing + (line.charAt(x));
}
}catch(IndexOutOfBoundsException g){}
}
}
String final1Ping = finalPing.replaceAll("[^0-9]", "");
return final1Ping;
}
}
}catch(IOException e){
}
return "";
}
UPDATE
Just in case this is important, Im using netbeans. I created a form and put this code in the formWindowOpened evt instead of calling it in main:
private void formWindowOpened(java.awt.event.WindowEvent evt) {
ActionListener timerListener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
new PingWorker().execute();
}
};
Timer timer = new Timer(1000, timerListener);
timer.start();
jLabel1.setText(label.getText());
timer.stop();
// TODO add your handling code here:
}
class PingWorker extends SwingWorker {
int time;
#Override
protected Object doInBackground() throws Exception {
time = pingTime("lol.garena.com");
return new Integer(time);
}
#Override
protected void done() {
label.setText("" + time);
}
};
public JComponent getUI() {
return label;
}
public static int pingTime(String hostnameOrIP) {
Socket socket = null;
long start = System.currentTimeMillis();
try {
socket = new Socket(hostnameOrIP, 80);
} catch (IOException ex) {
ex.printStackTrace();
} finally {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
}
}
}
long end = System.currentTimeMillis();
return (int) (end - start);
}
Use a Swing Timer for repeating tasks & a SwingWorker for long running tasks. E.G. of both below - it uses a Timer to repeatedly perform a 'long running' task (a ping) in a SwingWorker.
See Concurrency in Swing for more details on the Event Dispatch Thread and doing long running or repeating tasks in a GUI.
This code combines a long running task ('pinging' a server) using SwingWorker invoked from a repeating task (updating the JLabel repeatedly with the times) using a Swing based Timer.
import java.awt.event.*;
import javax.swing.*;
import java.net.Socket;
public class LabelUpdateUsingTimer {
static String hostnameOrIP = "stackoverflow.com";
int delay = 5000;
JLabel label = new JLabel("0000");
LabelUpdateUsingTimer() {
label.setFont(label.getFont().deriveFont(120f));
ActionListener timerListener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
new PingWorker().execute();
}
};
Timer timer = new Timer(delay, timerListener);
timer.start();
JOptionPane.showMessageDialog(
null, label, hostnameOrIP, JOptionPane.INFORMATION_MESSAGE);
timer.stop();
}
class PingWorker extends SwingWorker {
int time;
#Override
protected Object doInBackground() throws Exception {
time = pingTime();
return new Integer(time);
}
#Override
protected void done() {
label.setText("" + time);
}
};
public static int pingTime() {
Socket socket = null;
long start = System.currentTimeMillis();
try {
socket = new Socket(hostnameOrIP, 80);
} catch (Exception weTried) {
} finally {
if (socket != null) {
try {
socket.close();
} catch (Exception weTried) {}
}
}
long end = System.currentTimeMillis();
return (int) (end - start);
}
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
new LabelUpdateUsingTimer();
}
};
SwingUtilities.invokeLater(r);
}
}
You could use a Thread. The problem is you are blocking the main thread, thereby blocking your program. To get around this, start a background Thread to update components repeatedly.
(Note: you need to update GUI components on the EDT, so use SwingUtilities.invokeLater)
(new Thread((new Runnable(){
#Override
public void run(){
while(true){
SwingUtilities.invokeLater(new Runnable(){
#Override
public void run(){
refToJLabel.setText(Math.random());
}
});
}
}
}))).start();
I'm writing a program that constantly pings a server. I wrote the code to check it once and put the ping in a JLabel and put it in a method called setPing().
Here is my code
private void formWindowOpened(java.awt.event.WindowEvent evt) {
setPing();
}
That worked but only did it once, so I did:
private void formWindowOpened(java.awt.event.WindowEvent evt) {
for(;;){
setPing();
}
}
But this doesn't even work for the first time.
I didnt put the setPing method because it was too long so here it is:
public String setPing(){
Runtime runtime = Runtime.getRuntime();
try{
Process process = runtime.exec("ping lol.garena.com");
InputStream is = process.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line;
while ((line = br.readLine()) != null) {
int i = 0;
i = line.indexOf("Average");
if(i > 0){
String finalPing = "";
line.toCharArray();
try
{
finalPing = "";
for(int x = i; x < i + 17; x++)
{
finalPing = finalPing + (line.charAt(x));
}
}catch(IndexOutOfBoundsException e)
{
try
{
finalPing = "";
for(int x = i; x < i + 16; x++)
{
finalPing = finalPing + (line.charAt(x));
}
}catch(IndexOutOfBoundsException f)
{
try
{
finalPing = "";
for(int x = i; x < i + 15; x++)
{
finalPing = finalPing + (line.charAt(x));
}
}catch(IndexOutOfBoundsException g){}
}
}
String final1Ping = finalPing.replaceAll("[^0-9]", "");
return final1Ping;
}
}
}catch(IOException e){
}
return "";
}
UPDATE
Just in case this is important, Im using netbeans. I created a form and put this code in the formWindowOpened evt instead of calling it in main:
private void formWindowOpened(java.awt.event.WindowEvent evt) {
ActionListener timerListener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
new PingWorker().execute();
}
};
Timer timer = new Timer(1000, timerListener);
timer.start();
jLabel1.setText(label.getText());
timer.stop();
// TODO add your handling code here:
}
class PingWorker extends SwingWorker {
int time;
#Override
protected Object doInBackground() throws Exception {
time = pingTime("lol.garena.com");
return new Integer(time);
}
#Override
protected void done() {
label.setText("" + time);
}
};
public JComponent getUI() {
return label;
}
public static int pingTime(String hostnameOrIP) {
Socket socket = null;
long start = System.currentTimeMillis();
try {
socket = new Socket(hostnameOrIP, 80);
} catch (IOException ex) {
ex.printStackTrace();
} finally {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
}
}
}
long end = System.currentTimeMillis();
return (int) (end - start);
}
Use a Swing Timer for repeating tasks & a SwingWorker for long running tasks. E.G. of both below - it uses a Timer to repeatedly perform a 'long running' task (a ping) in a SwingWorker.
See Concurrency in Swing for more details on the Event Dispatch Thread and doing long running or repeating tasks in a GUI.
This code combines a long running task ('pinging' a server) using SwingWorker invoked from a repeating task (updating the JLabel repeatedly with the times) using a Swing based Timer.
import java.awt.event.*;
import javax.swing.*;
import java.net.Socket;
public class LabelUpdateUsingTimer {
static String hostnameOrIP = "stackoverflow.com";
int delay = 5000;
JLabel label = new JLabel("0000");
LabelUpdateUsingTimer() {
label.setFont(label.getFont().deriveFont(120f));
ActionListener timerListener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
new PingWorker().execute();
}
};
Timer timer = new Timer(delay, timerListener);
timer.start();
JOptionPane.showMessageDialog(
null, label, hostnameOrIP, JOptionPane.INFORMATION_MESSAGE);
timer.stop();
}
class PingWorker extends SwingWorker {
int time;
#Override
protected Object doInBackground() throws Exception {
time = pingTime();
return new Integer(time);
}
#Override
protected void done() {
label.setText("" + time);
}
};
public static int pingTime() {
Socket socket = null;
long start = System.currentTimeMillis();
try {
socket = new Socket(hostnameOrIP, 80);
} catch (Exception weTried) {
} finally {
if (socket != null) {
try {
socket.close();
} catch (Exception weTried) {}
}
}
long end = System.currentTimeMillis();
return (int) (end - start);
}
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
new LabelUpdateUsingTimer();
}
};
SwingUtilities.invokeLater(r);
}
}
You could use a Thread. The problem is you are blocking the main thread, thereby blocking your program. To get around this, start a background Thread to update components repeatedly.
(Note: you need to update GUI components on the EDT, so use SwingUtilities.invokeLater)
(new Thread((new Runnable(){
#Override
public void run(){
while(true){
SwingUtilities.invokeLater(new Runnable(){
#Override
public void run(){
refToJLabel.setText(Math.random());
}
});
}
}
}))).start();
I have two classes (Sampling and Stacker). The Sampling class (my Main class) is extends JFrame and has a JButton with an ActionListener to open the Stacker class.
The problem is when the button is clicked, the Stacker class will open but only a frame without any components. When I switch the main method into the Stacker class, the program works fine. What is the problem?
Here is the code:
The Sampling class:
public class Sampling extends JFrame implements ActionListener
{
private JButton openStacker;
Stacker st;
public Sampling()
{
setSize(300,300);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLayout(new FlowLayout());
setLocationRelativeTo(null);
openStacker = new JButton("Start Stacker!");
add(openStacker);
openStacker.addActionListener(this);
setVisible(true);
}
public void actionPerformed(ActionEvent e)
{
dispose();
st = new Stacker();
}
public static void main (String args[])
{
new Sampling();
}
}
The Stacker game class:
public class Stacker extends JFrame implements KeyListener
{
int iteration = 1;
double time = 200;
int last = 0;
int m = 10;
int n = 20;
JButton b[][];
int length[] = {5,5};
int layer = 19;
int deltax[] = {0,0};
boolean press = false;
boolean forward = true;
boolean start = true;
public Stacker()
{
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.setSize(400,580);
this.setUndecorated(false);
this.setLocationRelativeTo(null);
b = new JButton [m][n];
setLayout(new GridLayout(n,m));
for (int y = 0;y<n;y++)
{
for (int x = 0;x<m;x++)
{
b[x][y] = new JButton(" ");
b[x][y].setBackground(Color.DARK_GRAY);
add(b[x][y]);
b[x][y].setEnabled(false);
}//end inner for
}
this.setFocusable(true);
this.pack();
this.addKeyListener(this);
this.setVisible(true);
go();
}
public void go()
{
int tmp = 0;
Component temporaryLostComponent = null;
do{
if (forward == true)
{
forward();
} else {
back();
}
if (deltax[1] == 10-length[1])
{
forward = false;
} else if (deltax[1] == 0)
{
forward = true;
}
draw();
try
{
Thread.sleep((long) time);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}while(press == false);
if (layer>12)
{
time= 150-(iteration*iteration*2-iteration);
} else
{
time = time - 2.2;
}
iteration++;
layer--;
press = false;
tmp = check();
length[0] = length[1];
length[1] = tmp;
if (layer == -1)
{
JOptionPane.showMessageDialog(temporaryLostComponent, "Congratulations! You beat the game!");
repeat();
}
if (length[1] <= 0)
{
JOptionPane.showMessageDialog(temporaryLostComponent, "Game over! You reached line "+(18-layer)+"!");
repeat();
}
last = deltax[1];
start = false;
go();
}
public int check()
{
if (start == true)
{
return length[1];
}
else if (last<deltax[1])
{
if (deltax[1]+length[1]-1 <= last+length[0]-1)
{
return length[1];
}
else
{
return length[1]-Math.abs((deltax[1]+length[1])-(last+length[0]));
}
}
else if (last>deltax[1])
{
return length[1]-Math.abs(deltax[1]-last);
}
else
{
return length[1];
}
}
public void forward()
{
deltax[0] = deltax[1];
deltax[1]++;
}
public void back()
{
deltax[0] = deltax[1];
deltax[1]--;
}
public void draw()
{
for (int x = 0;x<length[1];x++)
{
b[x+deltax[0]][layer].setBackground(Color.DARK_GRAY);
}
for (int x = 0;x<length[1];x++)
{
b[x+deltax[1]][layer].setBackground(Color.CYAN);
}
}
public void repeat()
{
if(JOptionPane.showConfirmDialog(null, "PLAY AGAIN?","WARNING",JOptionPane.YES_NO_OPTION)== JOptionPane.YES_OPTION)
{
dispose();
new Stacker();
}else{
System.exit(0);
}
}
public void keyPressed(KeyEvent e)
{
if (e.getKeyCode() == KeyEvent.VK_SPACE)
{
press = true;
}
}
public void keyReleased(KeyEvent arg0)
{
}
public void keyTyped(KeyEvent arg0)
{
}
}
Just to put all my comments into an answer, and give you somewhere to start with:
Comment 1:
Take out go(); see that happens. I tested it and it will work. If you leave it there, even the frame's close button is jammed. You're blocking the edt with the while->Thread.sleep junk. You'll want to do some refactoring. You're code it hard to follow and I have no idea what you're trying to do, so I didn't even attempt it
Comment 2:
If you're wondering why it works when you just run the main from the Stacker class, it's probably because you are running it outside the EDT,
public static void main(String[] args) { new Stacker(); }. What happens when you click the button, that action is performed within the EDT, and hence your new Stacker() will be run on the EDT. In which case the EDT gets blocked by your while loop. If you try run the program from the Stacker class, but wrap it in a SwingUtilities.invokeLater, you will also notice the program fails to work. Swing programs should be run on the EDT though.
Comment 2: Read the first few sections on Concurrency with Swing
So what you can do is use a Swing Timer (which operates on the EDT) for the game loop. What I did was refactor your code a bit. It doesn't operate the way you want it to yet, only because I didn't really understand the logic of your code. So I couldn't get it to work. What I did though, is put some of the logic into the Timer.
Timer timer = new Timer((int)time, new ActionListener(){
public void actionPerformed(ActionEvent event) {
if (forward == true) {
forward();
} else {
back();
}
if (deltax[1] == 10 - length[1]) {
forward = false;
} else if (deltax[1] == 0) {
forward = true;
}
draw();
}
});
And when the go() method is called, it just starts the timer by calling timer.start(). Basically what you need to know about the timer, is that every tick (the milliseconds you pass it), the actionPerformed will be called. So you can update the game state in that method, just like you did in the while loop each iteration.
Take some time to go over How to Use Swing Timers
To get the game working properly, you still need to make some adjustments, but this should give you a head start.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Sampling extends JFrame implements ActionListener {
private JButton openStacker;
Stacker st;
public Sampling() {
setSize(300, 300);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLayout(new FlowLayout());
setLocationRelativeTo(null);
openStacker = new JButton("Start Stacker!");
add(openStacker);
openStacker.addActionListener(this);
setVisible(true);
}
public void actionPerformed(ActionEvent e) {
dispose();
st = new Stacker();
}
public static void main(String args[]) {
SwingUtilities.invokeLater(new Runnable(){
public void run() {
new Sampling();
}
});
}
}
class Stacker extends JFrame implements KeyListener {
int iteration = 1;
double time = 200;
int last = 0;
int m = 10;
int n = 20;
JButton b[][];
int length[] = {5, 5};
int layer = 19;
int deltax[] = {0, 0};
boolean press = false;
boolean forward = true;
boolean start = true;
Timer timer = new Timer((int)time, new ActionListener(){
public void actionPerformed(ActionEvent event) {
if (forward == true) {
forward();
} else {
back();
}
if (deltax[1] == 10 - length[1]) {
forward = false;
} else if (deltax[1] == 0) {
forward = true;
}
draw();
}
});
public Stacker() {
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.setSize(400, 580);
this.setUndecorated(false);
this.setLocationRelativeTo(null);
b = new JButton[m][n];
setLayout(new GridLayout(n, m));
for (int y = 0; y < n; y++) {
for (int x = 0; x < m; x++) {
b[x][y] = new JButton(" ");
b[x][y].setBackground(Color.DARK_GRAY);
add(b[x][y]);
b[x][y].setEnabled(false);
}//end inner for
}
this.setFocusable(true);
this.pack();
JPanel panel = (JPanel)getContentPane();
panel.addKeyListener(this);
this.setVisible(true);
panel.requestFocusInWindow();
go();
}
public void go() {
int tmp = 0;
Component temporaryLostComponent = null;
timer.start();
if (layer > 12) {
time = 150 - (iteration * iteration * 2 - iteration);
} else {
time = time - 2.2;
}
iteration++;
layer--;
press = false;
tmp = check();
length[0] = length[1];
length[1] = tmp;
if (layer == -1) {
JOptionPane.showMessageDialog(temporaryLostComponent, "Congratulations! You beat the game!");
repeat();
}
if (length[1] <= 0) {
JOptionPane.showMessageDialog(temporaryLostComponent, "Game over! You reached line " + (18 - layer) + "!");
repeat();
}
last = deltax[1];
start = false;
//go();
}
public int check() {
if (start == true) {
return length[1];
} else if (last < deltax[1]) {
if (deltax[1] + length[1] - 1 <= last + length[0] - 1) {
return length[1];
} else {
return length[1] - Math.abs((deltax[1] + length[1]) - (last + length[0]));
}
} else if (last > deltax[1]) {
return length[1] - Math.abs(deltax[1] - last);
} else {
return length[1];
}
}
public void forward() {
deltax[0] = deltax[1];
deltax[1]++;
}
public void back() {
deltax[0] = deltax[1];
deltax[1]--;
}
public void draw() {
for (int x = 0; x < length[1]; x++) {
b[x + deltax[0]][layer].setBackground(Color.DARK_GRAY);
}
for (int x = 0; x < length[1]; x++) {
b[x + deltax[1]][layer].setBackground(Color.CYAN);
}
}
public void repeat() {
if (JOptionPane.showConfirmDialog(null, "PLAY AGAIN?", "WARNING", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
dispose();
new Stacker();
} else {
System.exit(0);
}
}
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_SPACE) {
System.out.println("Pressed");
press = true;
}
}
public void keyReleased(KeyEvent arg0) {
}
public void keyTyped(KeyEvent arg0) {
}
}
Notice the SwingUtilities.invokeLater in the main. That's how you can start up the program on the EDT. The link on Concurrency In Swing will give you more information.
I asked this question yesterday and have attempted to implement the top answer I received. So from the code from yesterday I have tried adding synchronized to my methods and using wait() and notifyAll(). I have been looking up a bunch of examples, and reading the documentation but I'm definitely not doing it right.
Basically, once a touchEvent happens my ButtonListener stops all of my other code from executing, and only performs the code contained within the ButtonListener. This only happens on one of my computers though, my laptop runs the code the way I expected it to, my desktop gets stuck in the ButtonListener. This was a school assignment I turned in a couple weeks ago and initially received a 70%, but I explained to my teacher that it works on some computers and he ran it on his office desktop, and viola, it worked and I got a 100%. Of course I want to figure out what the problem is though so that is why I'm still working on this.
Here are the offending code snippets:
public synchronized void playOneTurn(int player)
throws InterruptedException {
waiting = true;
while (waiting) {
try {
System.out.println("playOneTurn before wait.");
wait();
} catch (InterruptedException e) {
// e.printStackTrace();
}
}
System.out.println("playOneTurn AFTER wait.");
}
My method here, playOneTurn, is the last bit of code that runs before the first touch event. Previously, as you can tell by looking at my question linked at the top, I used a simple while loop that waited for my ButtonListener to flip a boolean, waiting. Above is my attempt to use synchronized.
class ButtonListener implements ActionListener {
#Override
public synchronized void actionPerformed(ActionEvent e) {
System.out.println("Entering Action Performed");
for (i = 0; i < size; i++) {
for (j = 0; j < size; j++) {
if (e.getSource() == cells[i][j]) {
if (model[i][j] == 'e') {
cells[i][j].setText("");
currentSpot[0] = i;
currentSpot[1] = j;
if (count % 2 == 0) {
cells[i][j].setBackground(Color.GREEN);
cells[i][j].setIcon(X_MARK);
model[i][j] = 'x';
count++;
waiting = false;
System.out.println("Boolean hit");
notifyAll();
} else {
cells[i][j].setBackground(Color.CYAN);
cells[i][j].setIcon(O_MARK);
model[i][j] = 'o';
count++;
waiting = false;
System.out.println("Boolean hit");
notifyAll();
}
} else {
System.out
.println("Hey, you can't move there silly!");
}
}
}
}
}
}
Here is my ButtonListener where my program just chills in and stops doing anything else. Please disregard my random printlns everywhere, I was just getting aggravated and trying to find out what this thing is doing.
I also had to add this try/catch block to my controller class:
while (!game.haveWinner() && !game.isFull()) {
player = (player + 1) % 2;
try {
game.playOneTurn(player);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("While loop looped");
}
I have been trying every way I can think of to correctly implement synchronized but I'm obviously doing something wrong.
Here is a Dropbox link to the entire program if it would help.
This:
while (!game.haveWinner() && !game.isFull()) {
Is going to tie up the Swing event thread and freeze your entire application .... don't do it.
Your code looks like you're trying to hack a linear console program into a Swing GUI, and that never works because their program flow and logic are completely different. The solution is to change your logic to more event-driven.
You presumably have a game loop somewhere, perhaps using a Swing Timer ... so check with each iteration of the loop for winner or for is full.
You need to get all waiting, all synchronized, all notifies out of your program. Instead make a button press change it's state.
Edit
I was playing around with your code, and came up with something like this. Note that synchronization is not my strong suite, so take this with a grain of salt, but my key goal is to make sure that Swing GUI creation code and state changing code is called only on the Swing event thread, and that any other code, especially code that requires synchronization with other threads, is not called on the Swing event thread.
import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Scanner;
import javax.swing.*;
public class TicTacToeApp {
public static void main(String[] args) {
TicTacToeGame game;
int size, need, player = 1;
String[] names = new String[2];
Scanner kbd = new Scanner(System.in);
// TODO: uncomment in running code
// System.out.print("Enter Player 1's name: ");
// names[0] = kbd.nextLine();
// System.out.print("Enter Player 2's name: ");
// names[1] = kbd.nextLine();
//
// System.out.print("Enter the TIC-TAC-TOE grid size: ");
// size = kbd.nextInt();
// System.out.print("Enter how many in a row you need to win: ");
// need = kbd.nextInt();
// System.out.println();
// TODO: For test purposes only. Delete in running code
size = 3;
need = 3;
names[0] = "Foo";
names[1] = "Bar";
game = new TicTacToeGame(size, need, names);
while (!game.haveWinner() && !game.isFull()) {
player = (player + 1) % 2;
try {
game.playOneTurn(player);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("While loop looped");
}
if (game.haveWinner())
System.out.println(names[player] + " is the winner!");
else
System.out.println("It's a TIE!");
System.out.println("\nBye!");
}
}
#SuppressWarnings("serial")
class TicTacToeGame extends JPanel {
private static final Object LOCK = new Object();
private volatile int player = 0;
private int size;
private int need;
private String[] names;
private JLabel nameLabel = new JLabel();
// private JButton testButton = new JButton();
private JButton[][] buttonGrid;
private volatile boolean waiting = false;
public TicTacToeGame(int size, int need, String[] names) {
this.size = size;
this.need = need;
this.names = names;
nameLabel.setText(names[0]);
JPanel topPanel = new JPanel();
topPanel.add(new JLabel("Player:"));
topPanel.add(nameLabel);
buttonGrid = new JButton[size][size];
ButtonListener actionListener = new ButtonListener(this);
JPanel middlePanel = new JPanel(new GridLayout(size, size));
for (int row = 0; row < size; row++) {
for (int col = 0; col < size; col++) {
JButton button = new JButton(" ");
middlePanel.add(button);
buttonGrid[row][col] = button;
button.addActionListener(actionListener);
}
}
setLayout(new BorderLayout());
add(topPanel, BorderLayout.NORTH);
add(middlePanel, BorderLayout.CENTER);
// run GUI on Swing event thread
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(TicTacToeGame.this);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public int getPlayer() {
return player;
}
public synchronized void playOneTurn(final int player)
throws InterruptedException {
this.player = player;
System.out.printf("Player %d before wait%n", player);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
nameLabel.setText(names[player]);
}
});
synchronized (LOCK) {
waiting = true;
while (waiting) {
LOCK.wait();
}
}
}
public boolean isFull() {
for (int row = 0; row < size; row++) {
for (int col = 0; col < size; col++) {
if (buttonGrid[row][col].isEnabled()) {
return false;
}
}
}
return true;
}
public boolean haveWinner() {
// TODO finish this
return false;
}
public void doNotification() {
new Thread(new Runnable() {
public void run() {
synchronized (LOCK) {
waiting = false;
LOCK.notifyAll();
}
}
}).start();
}
public void tttButtonPressed(ActionEvent e) {
AbstractButton source = (AbstractButton) e.getSource();
for (int r = 0; r < size; r++) {
for (int c = 0; c < size; c++) {
if (buttonGrid[r][c] == source) {
String text = player == 0 ? "X" : "0";
source.setText(text);
source.setEnabled(false);
}
}
}
doNotification();
}
}
class ButtonListener implements ActionListener {
private TicTacToeGame ticTacToeGame;
public ButtonListener(TicTacToeGame ticTacToeGame) {
this.ticTacToeGame = ticTacToeGame;
}
public void actionPerformed(ActionEvent e) {
ticTacToeGame.tttButtonPressed(e);
};
}
Oh OK, I found the bug.
public class TicTacToeGame extends JFrame {
public synchronized void playOneTurn(int player)
throws InterruptedException {
wait();
}
class ButtonListener implements ActionListener {
#Override
public synchronized void actionPerformed(ActionEvent e) {
notifyAll();
}
}
}
I reduced the code so it's more obvious. playOneTurn is synchronized and waits on the TicTacToeGame instance but actionPerformed is synchronized and notifies on the ButtonListener instance.
A fix would be something like this (since ButtonListener is an inner class):
#Override
public void actionPerformed(ActionEvent e) {
synchronized (TicTacToeGame.this) {
TicTacToeGame.this.notifyAll();
}
}
Or create a separate object just to be a monitor.
But as I said before (and #HovercraftFullOfEels also seems to be saying), you could do to simply remove the while loop from the program and just use the event as a single entry point.
Also: don't forget to create your GUI on the EDT with invokeLater.
When code of the ActionListner more time consuming in execution, it halts all other part of the program from running.
So, create a new thread when the control enters the ActionListner. so it leverage the main thread to run other code while the thread you created runs your ActionListner part of the code.
Example:
void actionPerformed(ActionEvent e) {
Thread t = new Thread(new runnable()) {
public void run() {
System.out.println("Entering Action Performed");
for (i = 0; i < size; i++) {
for (j = 0; j < size; j++) {
if (e.getSource() == cells[i][j]) {
if (model[i][j] == 'e') {
cells[i][j].setText("");
currentSpot[0] = i;
currentSpot[1] = j;
if (count % 2 == 0) {
cells[i][j].setBackground(Color.GREEN);
cells[i][j].setIcon(X_MARK);
model[i][j] = 'x';
count++;
waiting = false;
System.out.println("Boolean hit");
notifyAll();
} else {
cells[i][j].setBackground(Color.CYAN);
cells[i][j].setIcon(O_MARK);
model[i][j] = 'o';
count++;
waiting = false;
System.out.println("Boolean hit");
notifyAll();
}
} else {
System.out
.println("Hey, you can't move there silly!");
}
}
}
}
}
};// runnable ends here
}