My understanding is that if I start up another thread to perform some actions, I would need to SwingUtilities.invokeAndWait or SwingUtilities.invokeLater to update the GUI while I'm in said thread. Please correct me if I'm wrong.
What I'm trying to accomplish is relatively straightforward: when the user clicks submit, I want to (before performing any actions) disable the submit button, perform the action, and at the end of the action re-enable the button. My method to perform the action updates the GUI directly (displays results) when it gets the results back.
This action basically queries a server and gets some results back.
What I have so far is:
boolean isRunning = false;
synchronized handleButtonClick() {
if ( isRunning == false ) {
button.setEnabled( false );
isRunning = true;
doAction();
}
}
doAction() {
new Thread() {
try {
performAction(); // Concern A
} catch ( ... ) {
displayStackTrace( ... ); // Concern B
} finally {
SwingUtilities.invokeLater ( /* simple Runnable to enable button */ );
isRunning = false;
}
}
}
For both of my concerns above, do I would have to use SwingUtilities.invokeAndWait since they both will update the GUI? All GUI updates revolve around updating JTextPane. Do I need to in my thread check if I'm on EDT and if so I can call my code (regardless of whether it updates the GUI or not) and NOT use SwingUtilities.invokeAndWait?
EDIT: Here is what I am doing now:
handleButtonClick() {
if ( isRunning == true )
return;
disable button;
SwingWorker task = new MyTask();
task.execute();
}
...inside MyTask
doInBackground() {
return performAction();
}
done() {
result = get();
enable button;
isRunning = false;
interpret result (do most of the GUI updates here);
}
While performAction() does some GUI updates, I have wrapped those in:
if ( SwingUtil.isEDT() )
doGUIupdate()
else
SwingUtil.invokeLater( new Runnable() {
run() {
doGUIupdate();
}
} );
Hopefully this is a step in the right direction, please comment if you believe there are better ways to handle my situation.
In my opinion you should almost never use invokeAndWait(). If something is going to take awhile that will lock your UI.
Use a SwingWorker for this kind of thing. Take a look at Improve Application Performance With SwingWorker in Java SE 6.
You should consider using SwingWorker since it will not block the UI thread, whereas both SwingUtilities methods will execute on the EDT thread, thus blocking the UI.
I keep the simple Thread inside EventQueue.invokeLater(...) and that worked smoothly...
java.awt.EventQueue.invokeLater(new Runnable() {
public void run(){
new Thread(new Runnable(){
public void run(){
try{
EdgeProgress progress = EdgeProgress.getEdgeProgress();
System.out.println("now in traceProgressMonitor...");
while(true){
// here the swing update
if(monitor.getState() == ProgressMonitor.STATE_BUSY){
System.out.println(monitor.getPercentDone()/2);
progress.setProgress(monitor.getPercentDone()/2);
}else{
break;
}
Thread.sleep(5);
}
}catch(InterruptedException ie){}
}
}).start();
}
});
Related
I am having a problem writing/updating the textarea. I am getting a value from the readtemp function, and i can see the result after calling the system out function, but nothing appears in the Textarea. What could be the problem?
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
URL temp;
try
{
temp = new URL("http://192.168.1.25/status.xml");
while (true)
{
System.out.println("Homerseklet: " + readtemp(temp));
jTextArea1.append(readtemp(temp));
}
}
catch (MalformedURLException ex)
{
Logger.getLogger(Download.class.getName()).log(Level.SEVERE, null, ex);
}
}
Correction: This won't help since the infinite loop will still block the EDT forever... Nevermind!
Your while loop is a really bad idea, but if you insist, you can at least give the EDT a chance to update the UI by dispatching your append asynchronously:
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
jTextArea1.append(readtemp(temp));
}
});
Are you doing IO and graphics in separate threads as it's supposed to be done? You should only do updating UI in the event dispatch thread, so when retrieving results from external place has completed you submit the update to event dispatch thread.
The problem exists because of the infinite loop while (true). Because the function jButton1ActionPerformed never ends, Swing has no chance to rerender the jTextArea1 component (I assume that this method is called in AWT Thread).
As mentioned in the previous answers it's a bad idea to handle long-running operations inside the swing thread. As a solution i'd replace your "textfield.append()"- Line with the following code snippet:
Java 8 way:
SwingUtilities.invokeLater(() -> jTextArea1.append(readtemp(temp)));
Pre-Java 8:
SwingUtilities.invokeLater(new Runnable() {
public void run() {
jTextArea1.append(readtemp(temp));
}
});
Source and some explanation: https://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html
I am running out of ideas how to make my progress bar responsive during performing RMI connection, so I have decided to ask You for help.
Here's the code :
Thread performLogin = new Thread(new Runnable()
{
#Override
public void run()
{
LoginResult = TryLogin();
}
});
performLogin.start();
WaiterFrame.setVisible(true);
SetProgressDialog();
try
{
performLogin.join();
}
catch(InterruptedException exc)
{
System.err.println(exc.getLocalizedMessage());
}
if (LoginResult)
{ ... }
WaiterFrame.setVisible(false);
this.dispose();
Progress bar is unresponsive - does not animate as it should while performing performLogin thread. I was trying to run progress bar frame on the other thread too, but result was the same (as well as using Eventqueue.invokelater()).
The likely cause is performLogin.join(); is blocking the Event Dispatching Thread, making the UI non-responsive.
Two things to remember with Swing (and most GUI frameworks);
It is single threaded, meaning if your block the EDT for any reason, it will no longer able to process new events or perform repaints
It's not thread safe, so you should never modify the state of the UI from outside the context of the EDT.
You could use a SwingWorker, which would allow you to run your long running process in a background thread but provides a number of mechanism through which you can send updates back to the EDT safely.
See Worker Threads and SwingWorker for more details and Issues with SwingWorker and JProgressBar for an example
If you're using Java 8 you could try something like this:
CompletableFuture<LoginResult> loginResult = CompletableFuture.supplyAsync(this::tryLogin);
WaiterFrame.setVisible(true);
setProgressDialog();
loginResult.thenAccept(lr -> {
//do your thing
WaiterFrame.setVisible(false);
})
There are other options to "thenAccept" depending on what you need to do. "thenAccept" only consumes the the content of the Future.
The same could be accomplished using Guava's ListenableFuture and Executors if Java 8 is not an option.
Thank You very much MadProgrammer! Progress bar works as intended with SwingWorker usage. I'm posting code if someone would encourage same problem in future :
PerformLogin = new SwingWorker<Boolean, Object>()
{
#Override
protected Boolean doInBackground() throws Exception
{
LoginResult = TryLogin();
if (LoginResult)
{
MainF = new MainFrame();
MainF.Connection = DataEntry.TestConnection;
MainF.prepareFormToShow();
}
return LoginResult;
}
#Override
protected void done()
{
if (LoginResult == true)
{
EventQueue.invokeLater(new Runnable()
{
#Override
public void run()
{
MainF.setVisible(true);
WaiterFrame.setVisible(false);
}
});
}
else
{
setVisible(true);
this.cancel(true);
JOptionPane.showMessageDialog(null, "Wrong adress!",
"Błąd",JOptionPane.WARNING_MESSAGE);
}
}
and
WaiterFrame.setVisible(true);
PerformLogin.execute();
in the main thread
I have a window, with a Start- and Stop-Button. The Start-Button starts the algorithm and the stop-button should stop it. I use SwingWorker do run the algorithm in the background and normally calling worker.cancel(true) should stop the algorithm running. I also have a Label, that visualize the Status, e.g. if I press "Stop", then the Labeltext changes to "stopped", so the Problem isnt on the actionLister of the Button.
My code looks like this:
public class MainWindow extends JFrame implements ActionListener, WindowListener
{
// Some code, like generating JFrame, JButtons and other stuff not affencting the task.
Worker worker = new Worker();
public void actionPerformed(ActionEvent e)
{
boolean isStarted = false;
// Start Button
if (e.getSource() == this.buttonStart)
{
if(!isStarted)
{
System.out.println("start");
labelSuccess.setText("Mapping started!");
this.setEnabled(true);
worker.execute();
isStarted = false;
}
}
// Stop Button
if (e.getSource() == this.buttonStop)
{
labelSuccess.setText("Mapping stopped!");
worker.cancel(true);
}
}
class Worker extends SwingWorker<Void, Void> {
#Override
protected Void doInBackground() throws Exception {
if(!isCancelled())
{
mapp();
Thread.sleep(60);
if (isCancelled()) {
System.out.println("SwingWorker - isCancelled");
}
}
return null;
}
}
At this Point, pressing the Stop-Button causes just a change of the Label-Text, but the algorithm in the background is still running. This now bothers me for quite a while and I just can't get it going.
Thanks a lot for any help, much appreciated.
edit1: I generate a new instance of worker now outside of actionPerformed, so now there is no new Worker generated on every mouse click.
Maybe if you use while instead of if on doInBackground() method of Worker class you will solve your problem. You must to put out of the while loop the mapp(), because you only want to invoke it one time. You should do something like this:
class Worker extends SwingWorker<Void, Void> {
#Override
protected Void doInBackground() throws Exception {
mapp();
while(!isCancelled()){
Thread.sleep(60);
}
System.out.println("SwingWorker - isCancelled");
return null;
}
This link could be useful to understanding how to use SwingWorker.
EDIT:
As you can see on another questions like this or this, using SwingWorker has some problems to manage the cancel method, because this method Attempts to cancel execution of this task. This attempt will fail if the task has already completed, has already been cancelled, or could not be cancelled for some other reason, like Oracle explains, and those "some other reasons" are discussed on the links I've posted.
You can do solve your problem using directly Threads. Your code would be something like this:
public class MainWindow extends JFrame implements ActionListener, WindowListener
{
// Some code, like generating JFrame, JButtons and other stuff not affencting the task.
final Thread th1 = new Thread(new Runnable() {
#Override
public void run() {
mapp();
}
});
public void actionPerformed(ActionEvent e)
{
boolean isStarted = false;
// Start Button
if (e.getSource() == this.buttonStart)
{
if(!isStarted)
{
System.out.println("start");
labelSuccess.setText("Mapping started!");
this.setEnabled(true);
th1.start();
isStarted = false;
}
}
// Stop Button
if (e.getSource() == this.buttonStop)
{
labelSuccess.setText("Mapping stopped!");
th1.stop();
}
}
This solutions uses the method stop(), which is deprecated, but it works. I've tried using interrupt(), but I don't know why the thread ran till finish the execution of mapp(). Obviously, using stop() is not the best method but it works stopping the mapp() execution before it finishes.
I recommend you to learn more about SwingWorker, Thread and Task to find the best solution to your problem.
Your problem is there is no loop in the worker: if you want to cancel a process using a flag, that process should check the flag from time to time, so if your method Worker.mapp() has to be stopped, check the flag there, no just before and after calling it.
I have problem while working with JFrame, which get freezes while
running the code continuously. Below is my code:
On clicking on btnRun, I called the function MainLoop():
ActionListener btnRun_Click = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
MainLoop();
}
};
Implementation of MainLoop():
void MainLoop()
{
Hopper = new CHopper(this);
System.out.println(Hopper);
btnRun.setEnabled(false);
textBox1.setText("");
Hopper.getM_cmd().ComPort = helpers.Global.ComPort;
Hopper.getM_cmd().SSPAddress = helpers.Global.SSPAddress;
Hopper.getM_cmd().Timeout = 2000;
Hopper.getM_cmd().RetryLevel = 3;
System.out.println("In MainLoop: " + Hopper);
// First connect to the validator
if (ConnectToValidator(10, 3))
{
btnHalt.setEnabled(true);
Running = true;
textBox1.append("\r\nPoll Loop\r\n"
+ "*********************************\r\n");
}
// This loop won't run until the validator is connected
while (Running)
{
// poll the validator
if (!Hopper.DoPoll(textBox1))
{
// If the poll fails, try to reconnect
textBox1.append("Attempting to reconnect...\r\n");
if (!ConnectToValidator(10, 3))
{
// If it fails after 5 attempts, exit the loop
Running = false;
}
}
// tick the timer
// timer1.start();
// update form
UpdateUI();
// setup dynamic elements of win form once
if (!bFormSetup)
{
SetupFormLayout();
bFormSetup = true;
}
}
//close com port
Hopper.getM_eSSP().CloseComPort();
btnRun.setEnabled(true);
btnHalt.setEnabled(false);
}
In the MainLoop() function, the while loop is running continuesly until the Running is true problem is that if i want to stop that while loop i have to set Running to false which is done at another button btnHalt:
ActionListener btnHalt_Click = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
textBox1.append("Poll loop stopped\r\n");
System.out.println("Hoper Stopped");
Running = false;
}
};
but btnHalt is not responding, whole frame is get freeze, also not
showing any log in the textarea.
Swing is a single thread framework. That is, there is a single thread responsible for dispatching all the events to all the components, including repaint requests.
Any action which stops/blocks this thread will cause your UI to "hang".
The first rule of Swing, NEVER run any blocking or time consuming tasks on the Event Dispatching Thread, instead, you should use a background thread.
This runs you smack into the second rule of Swing. Never create, modify or interact with any UI component outside of the EDT.
There are a number of ways you can fix this. You could use SwingUtilities.invokeLater or a SwingWorker.
SwingWorker is generally easier, as it provides a number of simple to use methods that automatically re-sync there calls to the EDT.
Take a read through Concurrency in Swing
Updated
Just so you understand ;)
Your MainLoop method should not be executed within the context of the EDT, this is very bad.
Also, you should not be interacting with any UI component from any thread other the then the EDT.
So in this section of code I have, I want to essentially tell the GUI to disable the button and bring up a pop-up window when no threads are running anymore (i.e. the method called has finished).
public void actionPerformed(ActionEvent event)
{
String command = event.getActionCommand();
//If btnConvertDocuments is clicked, the FileConverter method is called and the button is then disabled [so as to prevent duplicates].
if (command.equals("w"))
{
new Thread(new Runnable()
{
public void run()
{
FileConverter fc = new FileConverter();
}
}).start();
if (Thread.activeCount() == 0)
{
btnConvertDocuments.setEnabled(false);
//Validation message ensuring completion of the step.
JOptionPane.showMessageDialog(this, "Step 1 Complete!", "Validation", JOptionPane.INFORMATION_MESSAGE);
}
}
Why does that if (Thread.activeCount() == 0) never seem to get called? Is that not what I want to be doing in order to accomplish my objective? Thank you in advance for any input!
There are many threads that are running when you run a Java program (for example, the main thread :) and look here) if you want to check the state of a thread, use the getState() method (just remember to assign the thread to a Thread variable:
Thread t = new Thread(new Runnable()
{
public void run()
{
FileConverter fc = new FileConverter();
}
});
t.start();
if (t.getState().equals(Thread.State.TERMINATED) ) { ... }
Looking more into your question, you could call the join method as well, as it will block the current thread until t is done (or until timeout).
that's about Concurency in Swing, better would be wrap you BackGroung Task to the SwingWorker, very nice example by #Hovercraft Full Of Eels, or by implements Executor here