i know multithreading a bit but not in vast and i think the problem is of multithreading. I am calling a method to set label's text by invoking a new thread and leaving it blank after a specified time. I am getting the desired output every time but not only the place which i am going to show you by my piece of code. I am expecting that message should be set and disappeared after the specified time and the window should be minimized after that time. But what actually happening is when it is going to the other thread main thread execution starts and goes for sleep for 5 sec and the message is not appearing and after 5 sec window is getting minimized without showing the message which i am setting on the label.
(Main thread)
Validation.setMessageOnLabel("Username and password has been copied", jLabel15,1.5F);
try {
Thread.sleep(5000);
} catch (InterruptedException ex) {
Logger.getLogger(PasswordManager.class.getName()).log(Level.SEVERE, null, ex);
}
setState(ICONIFIED);
validation.java (setMessageOnLabel())
static public void setMessageOnLabel(final String msg, final JLabel label, final float time)
{
new Thread(new Runnable() {
#Override
public void run() {
label.setText(msg);
try {
Thread.sleep((long) (time*1000));
} catch (InterruptedException ex) {
Logger.getLogger(PasswordManager.class.getName()).log(Level.SEVERE, null, ex);
}
label.setText("");
}
}).start();
}
Since you're calling setState() directly, I assume the first code snippet is part of a JFrame. In that case you're most probably sending the event dispatch thread to sleep for 5 seconds and thus prevent screen updates during that time.
Put the sleep into another thread or use a swing worker instead and call setState() on the EDT in the worker's callback method, since setState() is not labelled as thread-safe and calling it on a thread other than the EDT might result in unexpected behavior.
From the linked tutorial:
Some Swing component methods are labelled "thread safe" in the API specification; these can be safely invoked from any thread. All other Swing component methods must be invoked from the event dispatch thread. Programs that ignore this rule may function correctly most of the time, but are subject to unpredictable errors that are difficult to reproduce.
Don't use Thread.sleep(5000);, that block EDT.
For that purposes you can use swing Timer, examine next example:
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.Timer;
public class TestFrame extends JFrame {
private JLabel lbl;
public TestFrame() {
init();
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack();
setLocationRelativeTo(null);
setVisible(true);
}
private void init() {
lbl = new JLabel(" ");
JButton setText = new JButton("setText");
setText.addActionListener(getActionListener());
add(lbl);
add(setText,BorderLayout.SOUTH);
}
private ActionListener getActionListener() {
return new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
lbl.setText("wait...");
Timer t = new Timer(5000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
lbl.setText("");
setState(JFrame.ICONIFIED);
}
});
t.setRepeats(false);
t.start();
}
};
}
public static void main(String args[]) {
new TestFrame();
}
}
When dealing with Swing components you shuld not use threads like that. Launch your own SwingWorker instead.
public class MySwingWorker extends SwingWorker<Object, Object> {
#Override
public Object doInBackground() {
//your code here
//dont forget to repaint changed component or validate parent of it,
//if your text dont shows up.
return null;
}
}
you can also execute your own runnable via SwingUtilites
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
//again your code here...
}
});
Related
Hello stack exchangers,
I have a problem with progress bars in java Swing. I think my confusions arise from my poor understanding of threads and the Swing event queue (I don't know much about java Threads, and exactly what is happening on the AWTEventQueue, although I generally understand what multithreading is about).
The context is that a JButton is pressed to start a long calculation. Before the calculation starts, I make a progress bar in a JFrame, which I thought would be painted, but it isn't. The frame appears, but it is just grey. The button, in this example has "clickMe" written on it.
In the "clickMe" action listener, I first make and display a JFrame in a subtask which is "run" (I'm not clear on when this is scheduled TBH). Then, I call doTask() which is running in the same thread as the action listener (which I think is the AWTEventThread??). The doTask() runs, printing out numbers to the Console. Intermixed with the doTask() output are iteration counts of the progressbar (from when the action listener started makeProgressBar()).
So, from the output, it looks like both the progress bar is running and the AWTEventThread, but the value set in the JProgressBar GUI is never updated.
How can I change my code so that the GUI gets updated? I've tried understanding the JProgressBar tutorial and hunted around the web, but I think my problem is more a conceptual understanding of Java Tasks.
This is my code:
package problemclass;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;
public class ProblemClass
{
void progressBarButtonClick()
{
JFrame buttonInAFrame = new JFrame();
JPanel buttonInAFramePanel = new JPanel();
JButton clickMe = new JButton("Click me!");
buttonInAFramePanel.add(clickMe);
clickMe.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e)
{
JFrame progBarFrame = makeProgressBar();
doTask();
progBarFrame.dispose();
}
});
buttonInAFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
buttonInAFrame.add(buttonInAFramePanel);
buttonInAFrame.pack();
buttonInAFrame.setVisible(true);
}
private void doTask()
{
for(int i = 0; i < 20000; i++)
{
if (i % 100 == 0)
{
System.out.println("TASK iteration " + i);
try
{
Thread.sleep(1000);
}
catch (InterruptedException e) {}
}
}
}
private JFrame makeProgressBar()
{
JFrame progBarFrame = new JFrame();
JPanel progBarPanel = new JPanel();
JProgressBar progressBar = new JProgressBar();
progBarPanel.add(progressBar);
progressBar.setValue(0);
progressBar.setStringPainted(true);
progressBar.setIndeterminate(true);
new Thread(new Runnable()
{
public void run()
{
for (int i = 0; i <= 100; i++)
{
final int j = i;
System.out.println("Progress Iteration " + j);
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
progressBar.setValue(j);
}
});
try
{
java.lang.Thread.sleep(100);
}
catch(Exception e) { }
}
}
}).start();
progBarFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
progBarFrame.add(progBarPanel);
progBarFrame.pack();
progBarFrame.setVisible(true);
return progBarFrame;
}
public static void main(String[] args)
{
EventQueue.invokeLater(() ->
{
new ProblemClass().progressBarButtonClick();
});
}
}
JFrame progBarFrame = makeProgressBar();
doTask();
Not sure exactly what you are trying to do.
The above code has two loops:
In the makePrgressBar() method you start a Thread and invoke SwingUtilities.invokeLater(…), to update the progress bar, which is correct.
but then in doTack() you start another loop. This time you don't start a Thread so the code is invoked on the EDT and since you use Thread.sleep, the EDT will sleep and the GUI will not repaint itself until the entire loop is finished.
I would suggest you get rid of the doTask() method since I don't know why you need two blocks of code that loop. Or if you really need it, then you also need to use a Thread and invokeLater(…).
Just like you, I recently did some work on progress bars and threading and went nuts until I realized that it is just so simple.In a nutshell this is the code I have when my button is clicked:
// Create 2 threads. One handles your GUI. Other does the task
Thread t1 = new Thread(new Runnable() {
#Override
public void run() {
// code goes here.
//In here I choose to hide the button, display the progress bar
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
#Override
public void run() {
// code goes here.
//In here I get the task done, then hide the progress bar
}
});
t2.start();
Works like a charm every time. Hope it helps!
Bellow is the code for the simplest GUI countdown. Can the same be done in a shorter and more elegant way with the usage of the Swing timer?
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
public class CountdownNew {
static JLabel label;
// Method which defines the appearance of the window.
public static void showGUI() {
JFrame frame = new JFrame("Simple Countdown");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
label = new JLabel("Some Text");
frame.add(label);
frame.pack();
frame.setVisible(true);
}
// Define a new thread in which the countdown is counting down.
public static Thread counter = new Thread() {
public void run() {
for (int i=10; i>0; i=i-1) {
updateGUI(i,label);
try {Thread.sleep(1000);} catch(InterruptedException e) {};
}
}
};
// A method which updates GUI (sets a new value of JLabel).
private static void updateGUI(final int i, final JLabel label) {
SwingUtilities.invokeLater(
new Runnable() {
public void run() {
label.setText("You have " + i + " seconds.");
}
}
);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
showGUI();
counter.start();
}
});
}
}
Yes you SHOULD use a Swing Timer. You SHOULD NOT, use a util Timer and TimerTask.
When a Swing Timer fires the code is executed on the EDT which means you just need to invoke the label.setText() method.
When using the uitl Timer and TimerTask, the code DOES NOT execute on the EDT, which means you need to wrap your code in a SwingUtilities.invokeLater to make sure the code executes on the EDT.
And that is way using a Swing Timer is shorter and more elegant than your current approach, it simplifies the coding because to code is executed on the EDT.
You could make it a little more elegant by using Timer with an appropriate TimerTask.
Yes, use a timer. updateGUI would be the code for the timer task, but it will need some changes as you won't be able to pass in i for each call since you just get a run() method.
I am making a program to check the stock market for a symbol and I got that far, and added a basic gui to it. I am stumped on how to make it check every hour and create a green up arrow if it increased and red down arrow if it decreased.
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;
public class QuoteTracker {
JFrame frame;
JPanel mainPanel;
JLabel enterLabel;
JLabel resultLabel;
JTextField text;
JTextField result;
JButton query;
JButton redArrow;
JButton greenArrow;
String url;
public static void main(String[] args) {
new QuoteTracker().buildGui();
}
public class checkingQuote implements Runnable {
#Override
public void run() {
while (true) {
try {
checkQuote(url);
//if increase in value green button
System.out.println("Sleeping");
Thread.sleep(1000 * 60 * 60);
System.out.println("Waking");
} catch (InterruptedException ie) {
ie.printStackTrace();
break;
}
}
}
}
public void checkQuote(String symbol) {
try {
String url = "http://finance.yahoo.com/q?s=" + symbol + "&ql=0";
this.url = url;
Document doc = Jsoup.connect(url).get();
Elements css = doc.select("p > span:first-child > span");
result.setText(css.text());
} catch (IOException e) {
}
}
public void buildGui() {
frame = new JFrame("QuoteTracker");
mainPanel = new JPanel();
enterLabel = new JLabel("enter symbol ");
resultLabel = new JLabel("result ");
text = new JTextField(4);
result = new JTextField(8);
query = new JButton("query");
query.addActionListener(new queryListener());
mainPanel.add(enterLabel);
mainPanel.add(text);
mainPanel.add(query);
mainPanel.add(resultLabel);
mainPanel.add(result);
frame.getContentPane().add(mainPanel);
frame.setSize(300, 400);
frame.setVisible(true);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
class queryListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent ev) {
checkQuote(text.getText());
}
}
}
Do I even need a thread? I've never made one before and tried to add things that made sense. I am thinking I either need a thread or to use java's Timer?
Use SwingWorker to execute long running task in the background while updating the UI based on some results from that long running task. That means, it is actually about two different threads communicating to each other - Worker Threads and Event Dispatch Thread (EDT)
But before that, I want to point some few notes about your code.
Invoke the initialization of your UI in the EDT. That is, instead of just straightly calling new QuoteTracker().buildGui(), call it inside the run method of a Runnable passed to SwingUtilities.invokeLater (like this)
Classes should start in capital letter as per the Java standard.
To apply SwingWorker in you existing code, you can do the following :
First, you must place your checkQuote method in some other class (ideally a service class) then modify your checkQuote method to return the String that is set to the textfield result. Something like this
public Class QuoteService{
public String checkQuote(String symbol) {
try {
String url = "http://finance.yahoo.com/q?s=" + symbol + "&ql=0";
this.url = url;
Document doc = Jsoup.connect(url).get();
Elements css = doc.select("p > span:first-child > span");
return css.text();
} catch (IOException e) {
e.printStackTrace();
}
return "";
}
}
You can then make your QuoteTracker class to focus mainly in the UI part of your application. Just create the service object as instance level field so that you can freely call checkQuote method within your the class.
Invoke SwingWorker when the button is clicked.
public void actionPerformed(ActionEvent ev) {
new SwingWorker<Void, String>() {
#Override // this method is done in the Worker Thread
protected Void doInBackground() throws Exception {
while(true){
String res = checkQuote(text.getText());
publish(res); //will call the process method
Thread.sleep(1000 * 60 * 60); //1 hour
}
}
#Override // this method is done in the EDT
protected void process(List<String> resultList){
String res = resultList.get(0);
if(!"".equals(res)){
result.setText(res);
}
}
#Override // this method is done in the EDT. Executed after executing the doInBackground() method
protected void done() {
//... clean up
}
}.execute();
}
Note that done() will be executed after the execution of doInBackground() is finished, which means, in the code I posted, it will never be executed since the while loop used to periodically call checkQuote is infinite. Just modify it so that you can interrupt that loop according to your need
Further Read : Concurrency in Swing
You can use thread and normal while loop in main thread as well, but at the very first , you need to start you thread and that thread must refer your object.
Add following line in public void buildGui() {
Thread t1 = new Thread(new checkingQuote());
t1.start();
This will start you thread, for testing purpose i have modified checkingQuote class
public class checkingQuote implements Runnable {
int count = 0;
#Override
public void run() {
System.out.println("Inside Runner");
while (true) {
try {
count++;
checkQuote(url);
//if increase in value green button
result.setText(""+count);
System.out.println("Sleeping");
Thread.sleep(1000);
System.out.println("Waking");
} catch (InterruptedException ie) {
ie.printStackTrace();
break;
}
}
}
}
I am seeing number change in the text box.... same way you can alter the logic to get and show the quotes.. but you must keep the value for previous quote to compare with the latest code to show green and red notification...
In gui application it is better to use Timer, also you may use ScheduledThreadPoolExecutor. But in the second case notice, that your scheduled tasks may run in non-GUI thread. As you can't invoke ATW/Swing directly from another thread, you should wrap any call to Swing into SwingUtilities.invokeLater() method.
Also notice, that when you do something durable inside GUI thread, the whole GUI becomes unrepsonsive. So, to achieve a better responsiveness, you would query in a separate thread, and expose results to Swing through invokeLater after quotes have checked. So your checkQuote method may be rewritten this way:
public void checkQuote(String symbol) {
try {
final String url = "http://finance.yahoo.com/q?s=" + symbol + "&ql=0";
Document doc = Jsoup.connect(url).get();
Elements css = doc.select("p > span:first-child > span");
final String text = css.text();
SwingUtilities.invokeLater(new Runnable() {
#Override public void run() {
this.url = url;
result.setText(text);
}
}
} catch (IOException e) {
// Don't swallow exceptions
logger.error("Something gone wrong", e);
}
}
public void checkQuote() {
final String symbol = text.getText();
new Thread(new Runnable() {
#Override public void run() {
checkQuote(symbol);
}
}).start();
}
and call it from Timer and from button click listener.
I need to stop user making multiple clicks on a JButton while the first click still execute.
I was able to came with a solution for this issue but I do not completelly understand why it's working.
Bellow I posted the code (trimmed to a minimum) that works and the one that does not work.
In first example (good) if you run it and click the button multiple times only one action is considered as for the second example (bad) if you click the mouse multiple times you get action executed at least twice.
The second (bad) example simply does not use invokeLater() method.
Where the difference in behaviour cames from?
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;
public class TestButtonTask {
public static void main(String[] args) {
final JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
final JButton task = new JButton("Test");
task.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
long t = System.currentTimeMillis();
System.out.println("Action received");
task.setText("Working...");
task.setEnabled(false);
SwingUtilities.invokeLater(new Thread() {
#Override
public void run() {
try {
sleep(2 * 1000);
} catch (InterruptedException ex) {
Logger.getLogger(TestButtonTask.class.getName()).log(Level.SEVERE, null, ex);
}
SwingUtilities.invokeLater(new Runnable() {
public void run() {
task.setEnabled(true);
task.setText("Test");
}
});
}
});
}
});
frame.add(task);
frame.pack();
frame.setVisible(true);
} //end main
} //end class
And now the "wrong" code
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;
public class TestButtonTask {
public static void main(String[] args) {
final JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
final JButton task = new JButton("Test");
task.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
long t = System.currentTimeMillis();
System.out.println("Action received");
task.setText("Working...");
task.setEnabled(false);
SwingUtilities.invokeLater(new Thread() {
#Override
public void run() {
try {
sleep(2 * 1000);
} catch (InterruptedException ex) {
Logger.getLogger(TestButtonTask.class.getName()).log(Level.SEVERE, null, ex);
}
//SwingUtilities.invokeLater(new Runnable() {
//public void run() {
task.setEnabled(true);
task.setText("Test");
//}
//});
}
});
}
});
frame.add(task);
frame.pack();
frame.setVisible(true);
} //end main
} //end class
After info provided by #kleopatra and #Boris Pavlović here is the code I created that seems to work pretty decent.
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;
public class TestButtonTask {
public static void main(String[] args) {
final JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
final JButton task = new JButton("Test");
task.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
task.setText("Working...");
task.setEnabled(false);
SwingWorker worker = new SwingWorker<Void, Void>() {
#Override
protected Void doInBackground() throws Exception {
try {
Thread.sleep(3 * 1000);
} catch (InterruptedException ex) {
Logger.getLogger(TestButtonTask.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
};
worker.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
System.out.println("Event " + evt + " name" + evt.getPropertyName() + " value " + evt.getNewValue());
if ("DONE".equals(evt.getNewValue().toString())) {
task.setEnabled(true);
task.setText("Test");
}
}
});
worker.execute();
}
});
frame.add(task);
frame.pack();
frame.setVisible(true);
} //end main
} //end class
you have two choises
1) JButton#setMultiClickThreshhold
2) you have to split this idea to the two separated actions inside actionListener or Action
1st. step, JButton#setEnabeld(false);
2nd. step, then call rest of code wrapped to the javax.swing.Action (from and dealyed by javax.swing.Timer), SwingWorker or Runnable#Thread
Okay, here's a code snippet using an Action
it disable's itself on performed
it spawns a task, at the end of which is enables itself again. Note: for simplicity here the task is simulated by a Timer, real-world would spawn a SwingWorker to do the background work, listening to its property changes and enable itself on receiving a done
set as the button's action
The code:
Action taskAction = new AbstractAction("Test") {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("Action received ");
setEnabled(false);
putValue(NAME, "Working...");
startTask();
}
// simulate starting a task - here we simply use a Timer
// real-world code would spawn a SwingWorker
private void startTask() {
ActionListener l = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
putValue(NAME, "Test");
setEnabled(true);
}
};
Timer timer = new Timer(2000, l);
timer.setRepeats(false);
timer.start();
}};
JButton task = new JButton(taskAction);
There are two more ways.
You can define a flag. Set it when action start and reset back after the end. Check the flags in the actionPerformed. If inProgress==true just do nothing.
Another way is to remove the listener and assign it back after the action ends.
The right way is using a SwingWorker. When user click the button before submmiting a job to the SwingWorker the state of the button should be changed to disabled JButton#setEnabled(false). After the SwingWorker finished the job state of the button should be reset to enabled. Here's Oracle's tutorial on SwingWorker
After years of dealing with the frustration of this problem, I've implemented a solution that I think is the best.
First, why nothing else works:
JButton::setMutliclickThreshold() is not really an optimal solution, because (as you said) there is no way to know how long to set the threshold. This is only good to guard against double-click happy end-users because you have to set an arbitrary threshold.
JButton::setEnabled() is an obviously fragile solution that will only make life much more difficult.
So, I've created the SingletonSwingWorker. Now, Singletons are called anti-patterns, but if implemented properly, they can be a very powerful. Here is the code:
public abstract class SingletonSwingWorker extends SwingWorker {
abstract void initAndGo();
private static HashMap<Class, SingletonSwingWorker> workers;
public static void runWorker(SingletonSwingWorker newInstance) {
if(workers == null) {
workers = new HashMap<>();
}
if(!workers.containsKey(newInstance.getClass()) || workers.get(newInstance.getClass()).isDone()) {
workers.put(newInstance.getClass(), newInstance);
newInstance.initAndGo();
}
}
}
This will enable you to create classes which extend SingletonSwingWorker and guarantee only one instance of that class will be executable at one time. Here is an example implementation:
public static void main(String[] args) {
final JFrame frame = new JFrame();
JButton button = new JButton("Click");
button.setMultiClickThreshhold(5);
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
DisplayText_Task.runWorker(new DisplayText_Task(frame));
}
});
JPanel panel = new JPanel();
panel.add(button);
frame.add(panel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
static class DisplayText_Task extends SingletonSwingWorker {
JFrame dialogOwner;
public DisplayText_Task(JFrame dialogOwner) {
this.dialogOwner = dialogOwner;
}
JDialog loadingDialog;
#Override
void initAndGo() {
loadingDialog = new JDialog(dialogOwner);
JProgressBar jpb = new JProgressBar();
jpb.setIndeterminate(true);
loadingDialog.add(jpb);
loadingDialog.pack();
loadingDialog.setVisible(true);
execute(); // This must be put in the initAndGo() method or no-workie
}
#Override
protected Object doInBackground() throws Exception {
for(int i = 0; i < 100; i++) {
System.out.println(i);
Thread.sleep(200);
}
return null;
}
#Override
protected void done() {
if(!isCancelled()) {
try {
get();
} catch (ExecutionException | InterruptedException e) {
loadingDialog.dispose();
e.printStackTrace();
return;
}
loadingDialog.dispose();
} else
loadingDialog.dispose();
}
}
In my SwingWorker implementations, I like to load a JProgressBar, so I always do that before running doInBackground(). With this implementation, I load the JProgressBar inside the initAndGo() method and I also call execute(), which must be placed in the initAndGo() method or the class will not work.
Anyways, I think this is a good solution and it shouldn't be that hard to refactor code to refit your applications with it.
Very interested in feedback on this solution.
Note that when you are modifying anything in GUI your code must run on Event Dispatch thread using invokeLater or invokeAndWait if you are in another thread. So second example is incorrect as you are trying to modify enabled state from another thread and it can cause unpredictable bugs.
When the program starts, a new JFrame is created. Once the user clicks the start button a thread is created and started. Part of this threads execution is to validate the data on the form and then execute with that data. Once the data has been validated the thread calls dispose() on the original frame and then creates a new JFrame that acts as a control panel.
There is also an automatic mode of the program that doesn't display any GUI at all, this mode reads data from a configuration file and then starts the execution thread and runs everything but without the control panel.
I want the program to end once the thread completes, but in GUI mode, only if the user has closed the control panel as well.
Is it possible to make the thread wait for the frame to close. I assuming that the frame is run from it's own Thread? or is that not the case.
Thanks.
The answer you chose is a little awkward. Using Thread.sleep(1000) will check for window state every second. It is not a performance issue, but just bad coding style. And you may have a one second response time.
This code is a little bit better.
private static Object lock = new Object();
private static JFrame frame = new JFrame();
/**
* #param args
*/
public static void main(String[] args) {
frame.setSize(300, 300);
frame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
frame.setVisible(true);
Thread t = new Thread() {
public void run() {
synchronized(lock) {
while (frame.isVisible())
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Working now");
}
}
};
t.start();
frame.addWindowListener(new WindowAdapter() {
#Override
public void windowClosing(WindowEvent arg0) {
synchronized (lock) {
frame.setVisible(false);
lock.notify();
}
}
});
t.join();
}
You can make reference from your thread to the JFrame. Then set the default close operation of JFrame to HIDE_ON_CLOSE. If the JFrame is closed, you can stop the thread.
Example code:
import java.awt.Dimension;
import javax.swing.JFrame;
public class FrameExample extends JFrame {
public FrameExample() {
setSize(new Dimension(100, 100));
setDefaultCloseOperation(HIDE_ON_CLOSE);
setVisible(true);
}
private static class T implements Runnable {
private FrameExample e;
public T(FrameExample e) {
this.e = e;
}
#Override
public void run() {
while (true) {
if (e.isVisible()) {
// do the validation
System.out.println("validation");
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
break;
}
}
}
}
}
public static void main(String[] args) {
FrameExample frameExample = new FrameExample();
new Thread(new T(frameExample)).start();
}
}
All Swing components, including JFrame, are managed by a single thread, called the Event Dispatch Thread, or EDT. (It's possible to call methods on Swing objects from other threads, but this is usually unsafe, except in a few cases not relevant here.)
You'll probably accomplish what you want here by putting the data validation and execution code in its own object which is otherwise completely unaware of the outside world. Then, call it from one of two other objects: one that manages a GUI, and another that runs in "automatic mode".