How do I run a terminate command on JFrame exit? - java

I know the basics behind trying to write code that terminates a thread, but I've run into a bit of a problem.
I've got a JButton in a JFrame GUI that launches the simulation I'm trying to animate. It is called in the ActionPerformed code of the JButton by
new AnswerWorker().execute();
The AnswerWorker class, in turn, extends SwingWorker so that the animation frame can be drawn while the GUI is still active.
public class AnswerWorker extends SwingWorker<String, Integer> {
protected String doInBackground() throws Exception
{
Threading threading = new Threading();
return null;
}
protected void done()
{
try {
JOptionPane.showMessageDialog(InputGUI.this, AMEC.unsuccesfulpercentage + "% of iterations had trucks that had to sleep over");
AMEC.unsuccesfulpercentage = 0;
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
To create a way of stopping the simulation thread, I created the class Threading, that calls the function that runs the simulation.
public class Threading extends Thread {
#Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
AMEC.runsimulation();
} catch (IOException ex) {
Logger.getLogger(InputGUI.class.getName()).log(Level.SEVERE, null, ex);
} catch (InterruptedException ex) {
Logger.getLogger(InputGUI.class.getName()).log(Level.SEVERE, null, ex);
}
}
return;
}
}
Now, in the runsimulation() function, I initialize a JFrame, and I want to terminate the thread running the simulation if the JFrame is closed by clicking on its close button. How do I do this?
EDIT: all of the above code is called in a file InputGUI.java that contains all my GUI elements. The runsimulation function is in my main project file, AMEC.java

You could override the dispose() method on the JFrame to include a call to stop the thread
#Override
dispose(){
stopThread();
super.dispose();
}

How do I run a terminate command on JFrame exit?
add WindowListener to JFrame, from windowClosing event you have to call JFrame.setVisible(false) and then to call SwingWorker
after SwingWorker ended to terminate current JVM, to show a JOptionPane in the case that any exception raised, or to show JFrame again back to screen, required to change DefaultCloseOperation, to HIDE_ON_CLOSE

Related

Changing owner for ReentrantLock

So I wrote a little program which will move circles around and when they collide they will move opposite direction, however when I'm trying to delay the execution so they won't move around stupidly fast I get java.lang.IllegalMonitorStateException
Lock in canvasRender.java, creating an instance:
ReentrantLock renderLock = new ReentrantLock();
Method which will pause execution for a moment, so circles won't move around super fast.
publlic void delay(){
renderLock.unlock();
try { Thread.sleep(10); } catch (Exception e) {} ;
renderLock.lock();
}
then from another class where I create a window and add actionListener
public static void main(String[] args){
//Buttons and other elements
// ...
JButton start = new JButton("Start!");
createAndShowGUI();
}
In createAndShowGUI():
static void createAndShowGUI(){
//adding elements to panels
start.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
start(); //this will set gameIsRunning variable to true and create models
while (gameIsRunning) {
//update(); //which has delay(); at the end of frame drawing
//but even if just put delay()
delay(); //still says exception
start.setEnabled(false); //while game is running button is unavailable
}
start.setEnabled(true);
}
});
}
In this case my lock is owned by the Thread main, but at the time when I click button 'Start!' current is Thread AWT-EventQueue-0, and so the program crashes. How to fix this issue? (or where am I silly?)
The problem is that you're calling renderLock.unlock() from AWT-EventQueue-0 after renderLock.lock() was called by main. The thread AWT-EventQueue-0 isn't allowed to call unlock() it, since it's not the thread that called lock() it in the first place.
You could probably simplify things by dropping ReentrantLock and just using synchronized.
I don't know the design of the rest of your program, but it seems to me that the contents of the while loop belong in a separate thread. You generally don't want to loop in a UI listener method (such as actionPerformed() in ActionListener) because it will freeze up the GUI.
One thing you could do is add an Object to synchronize on:
private static final Object LOCK = new Object()
Then move the game-updating logic to its own thread — something like this:
private static class GameThread extends Thread {
public GameThread() {
super("GameThread");
}
public void run() {
synchronized (LOCK) {
start();
while (gameIsRunning) {
update();
try {
// Try to sleep for 10 millis:
LOCK.wait(10);
} catch (InterruptedException ignored) { }
}
}
// Re-enable the button:
javax.swing.SwingUtilities.invokeLater(() -> start.setEnabled(true));
}
}
And you can change your ActionListener to simply disable the button and start a GameThread:
start.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
start.setEnabled(false);
synchronized(LOCK) {
if(!gameIsRunning) {
new GameThread().start();
}
}
}
});
Any other code that checks or modifies the state of the game should also be enclosed within a synchronized (LOCK) block. And if update() modifies the GUI as well as the game state, then it probably needs to do so with SwingUtilities.invokeLater().
It might also make things more clear to rename start() to setupGame() and JButton start to JButton startButton.

Get GUI of a different class to update continuously using java swing worker

I am creating a new window of the MainWindow class from within a different class.
The JFrame of the MainWindow class opens, and I can click my button, but the GUI does not update as it should after the button is clicked. I use SwingWorkers in this class, and if I run it on its own the GUI updates continuously.
However, creating it from the other class, it is not updating properly, even after I called the creating using a SwingWorker.
private void StartButtonActionPerformed(java.awt.event.ActionEvent evt) {
// TODO add your handling code here:
WindowEvent winClose = new WindowEvent(this,WindowEvent.WINDOW_CLOSING);
Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(winClose);
StartGame doGame = new StartGame();
doGame.execute();
}
class StartGame extends SwingWorker<Void, Void>
{
#Override
public Void doInBackground()
{
try {
MainWindow game = new MainWindow(num_respawns, hits, regen_secs, time, map);
game.setVisible(true);
} catch (IOException ex) {
Logger.getLogger(GameSetup.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
#Override
public void done()
{
System.out.println("opening done");
}
}

Updating the JPanel in a JFrame

I have a JFrame with a CardLayout component. I am using the CardLayout to switch between different JPanel's at different moments of the application execution. At some point I am using a SwingWorker Object to generate some XML files. In this time I want to display another JPanel in my window to tell the user to wait. On this JPanel I want to switch between 3 labels.
JLabel 1 would be : "Please wait."
JLabel 2 would be : "Please wait.."
JLabel 3 would be : "Please wait..."
Right now the code looks like this:
ConvertersWorker.execute();
CLayout.show(Cards, WAIT_PANEL);
Timer t =new Timer(500, new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e) {
WaitPanel.SwitchLabels();
}
});
t.start();
while (!this.finishedConverting)
{
}
//After this im am executing other methods based on the generated XML files
The SwingWorker code:
SwingWorker<Boolean, Void> ConvertersWorker = new SwingWorker<Boolean, Void>() {
public Boolean doInBackground() {
Boolean result = RunConverters();
return result;
}
public void done() {
try {
finishedConverting = get();
} catch (InterruptedException ex) {
ex.printStackTrace();
} catch (ExecutionException ex) {
ex.printStackTrace();
}
}
} ;
The second JPanel is not even displayed because the JFrame blocks. It blocks because of the while loop but I don't know how to implement it differently. And also the method done() from the SwingWorker is never executed. If it were executed then the finishedConverting variable would have been set to true and the while loop would have stopped. Can anyone help me to find a better solution?
I know you solved this but that's happening because you are using just one thread, and it blocked because of the While, so you need to create a new thread to handle this
new Thread(){
public void run() {
//code here
}
}.start();
and to refresh the content of a JPanel you can use
myJpanel.doLayout();
or
myJpanel.repaint();
I removed the while loop and moved the code which was after the loop in another method which is executed in the done() method of the SwingWorker so now it works.

NullPointerException: Timertask Cancel and Object Disposing

This is my first time here. Please, excuse my bad English.
I am writing here to ask a question about TimerTask and collateral effects of object disposing.
Here is a simple example. This example shows a JFrame with a big button which starts a TimerTask. This task only writes a message, sleeps 6 seconds, and writes another message.
Every 10 secs (it's not important this fact) the task is executed.
If I click the button (when I know that the task is stopped and in the sleep method), the object writer (that let's us write a message) is set to null and the timer is cancelled.
Then, if I cancel the task, and an execution of this task is running, it can be thrown NullPointerException (remember that writer was set to null).
My question is: how can I avoid NullPointerException in this case? Catching it in the
run method of Timertask? or directly interrupting the task with interrupt method?
Thank you for your understanding.
public class CancelTimer extends JFrame{
public static void main(String[] args) {
new CancelTimer().setVisible(true);
}
Timer timer;
Writer writer;
public CancelTimer() {
super();
writer=new Writer();
timer= new Timer("timer");
timer.schedule(new TimerTask() {
#Override
public void run() {
try{
writer.print("Start and wait");
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
writer.print("Bye, bye!");
} catch (NullPointerException e){
System.out.println("NullPointerException due external disposing task!");
}
}
}, 3000, 10000);
JPanel panel = new JPanel(new BorderLayout());
JButton button= new JButton();
button.setAction(new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
writer=null;
timer.cancel();
}
});
panel.add(button,BorderLayout.CENTER);
this.setContentPane(panel);
this.pack();
this.setSize(new Dimension(400,400));
}
private class Writer {
public void print(String text){
System.out.println("PRINT SOME TEXT: "+text);
}
}
}
If you don't set your writer to null, but instead use a boolean value to track whether or not your task is active, you can save the trouble of having to catch the NullPointerException.
Basically, track the state of whether or not you should be writing something some other way. Setting your writer to null is just silly, because, as you've noticed, it then requires you to catch and handle an exception. This is not what exceptions are for.

Why does my GUI still hang even after using SwingUtilities.invokeLater?

I have this ActionListener that gets called in the EDT. My plot() function is computationally heavy, it can easily take five seconds. It made the GUI hang as expected. I added the SwingUtilities.invokeLater code and it still hangs. Shouldn't the GUI be responsive now that I am spawning a separate thread for my heave computation?
final ActionListener applyListener = new ActionListener()
{
#CommitingFunction
public void actionPerformed(ActionEvent arg0)
{
/*Don't do plotting in the EDT :)*/
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
plot();
}
});
}
};
Not at all. InvokeLater is not producing a new thread. invokeLater exists to tell Swing explicitly "use the Event Dispatching Thread for this, but not right now". invoke and invokeLater exist to let you do operations that are only safe for the Event Dispatching Thread from other threads- not by doing them on those threads, but by telling the EDT to do them.
Your ActionListener will run very quickly, throwing the Runnable on Swing's event dispatching queue. Then when it gets that far, it will take five seconds to run the plot().
The only workaround is to refactor plot(). Use a SwingWorker (or similar multithreading strategy, but SwingWorker is probably the best for this) to actually move the logic of plot() onto a different thread. That thread cannot safely draw anything because it is not the Swing Event Dispatching Thread, so all of its draw operations need to be performed via invokeLater(). For efficiency reasons, you should try to do all of the drawing operations at once, on one invokeLater(), using results stored from your calculation.
You're doing the opposite of what you think you are. Instead of running your computation thread outside of the EDT, you're explicitly calling it within it!
SwingUtilities.invokeLater() queues up the runnable for invocation at a later time, in the EDT! You want to use SwingWorker instead.
invokeLater add a task to the GUIs work queue. It will be invoked after all other tasks have been performed, however it still uses the GUI thread.
I suggest you look at using an ExecutorService.
As #Adam suggests, any actual drawing it does needs to be done via invokeLater.
You don't show what is inside the plot() function, but you should not put any painting in there. Compute whatever you want in the new thread, and do the painting in the EDT. To do this, it is better to use SwingWorker
Here is what I did for my company's app, this is some pseudo code because of legal reasons, but the jist of it is that if the screen is unresponsive, it will reboot the GUI. Whenever you use SwingUtilities to kick off the EDT, in that same init block, create two watcher threads. One thread will simply perform an action on the EDT thread using Swing utilities. Another thread will monitor the first thread to see if feels the first thread is responsive. The first thread will only acknowledge responsiveness if it can perform a very simple command.
set isEDTCheck to true when running in normal fashion, false in debug mode (otherwise you'll constantly get rebooted.
if (isEDTCheck) {
new Thread("EDTHeartbeat") {
#Override
public void run() {
Runnable thisThingYouDo = new Runnable() {
public void run() {
int x = 0;
}
};
while (true) {
// first thread says we are waiting, aka bad state
edtwait=true;
try {
javax.swing.SwingUtilities.invokeAndWait(thisThingYouDo);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// first thread says we are not waiting, good state
edtwait=false;
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
new Thread("EDTValidator") {
#Override
public void run() {
while (true) {
// is first thread in bad state?
if (edtwait) {
try {
Thread.sleep(3000);
// after 3 seconds are we still in bad state? if so, get rid of initial frame, pop up a dialog box in AWT that does no commands
if (edtwait) {
mainFrame.setVisible(false);
new Dialog();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
}
public class Dialog extends Frame {
private static final int WIDTH = 400;
private static final int HEIGHT = 300;
Frame f = null;
public Dialog() {
f = this;
hasSomethingBeenEntered=false;
this.setTitle("APP PROBLEM DETECTED");
this.setSize(WIDTH, HEIGHT);
this.setLocation((int)Toolkit.getDefaultToolkit().getScreenSize().getWidth() - myapp.width, 0);
Panel p1 = new Panel() {
#Override
public void paint(final Graphics g) {
int left = Dialog.WIDTH/2 - 45; // don't use WIDTH shadowed by Panel class
int top = Dialog.HEIGHT/2 - 20; // same as above
g.drawString("APP HAS DETECTED A PROBLEM", left, top);
}
};
this.add("Center", p1);
this.setAlwaysOnTop(true);
TextArea tb = new TextArea("APP HAS DETECTED A MAJOR PROBLEM\nIT WILL NOW RESTART IN 5 SECONDS");
this.add(tb);
this.setVisible(true);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
restartApp();
}
private void restartApp() {
Runtime.getRuntime().exec("cmd /c start cmd.exe /K \"cd C:\\Progra~1\\Common~1 && C:\\Progra~1\\Common~1\\MyAppDir\\myjavaapp.jar\"");
System.exit(0);
}

Categories