basically, I have this code which was initially working with console i/o now I have to connect it to UI. It may be completely wrong, I've tried multiple things although it still ends up with freezing the GUI.
I've tried to redirect console I/O to GUI scrollpane, but the GUI freezes anyway. Probably it has to do something with threads, but I have limited knowledge on it so I need the deeper explanation how to implement it in this current situation.
This is the button on GUI class containing the method that needs to change this GUI.
public class GUI {
...
btnNext.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
controller.startTest(index, idUser);
}
});
}
This is the method startTest from another class which contains instance of Question class.
public int startTest() {
for (int i = 0; i < this.numberofQuestions; i++) {
Question qt = this.q[i];
qt.askQuestion(); <--- This needs to change Label in GUI
if(!qt.userAnswer()) <--- This needs to get string from TextField
decreaseScore(1);
}
return actScore();
}
askQuestion method:
public void askQuestion() {
System.out.println(getQuestion());
/* I've tried to change staticaly declared frame in GUI from there */
}
userAnswer method:
public boolean userAnswer() {
#SuppressWarnings("resource")
Scanner scanner = new Scanner(System.in);
if( Objects.equals(getAnswer(),userInput) ) {
System.out.println("Correct");
return true;
}
System.out.println("False");
return false;
}
Thanks for help.
You're correct in thinking that it related to threads.
When you try executing code that will take a long time to process (eg. downloading a large file) in the swing thread, the swing thread will pause to complete execution and cause the GUI to freeze. This is solved by executing the long running code in a separate thread.
As Sergiy Medvynskyy pointed out in his comment, you need to implement the long running code in the SwingWorker class.
A good way to implement it would be this:
public class TestWorker extends SwingWorker<Integer, String> {
#Override
protected Integer doInBackground() throws Exception {
//This is where you execute the long running
//code
controller.startTest(index, idUser);
publish("Finish");
}
#Override
protected void process(List<String> chunks) {
//Called when the task has finished executing.
//This is where you can update your GUI when
//the task is complete or when you want to
//notify the user of a change.
}
}
Use TestWorker.execute() to start the worker.
This website provides a good example on how to use
the SwingWorker class.
As other answers pointed out, doing heavy work on the GUI thread will freeze the GUI. You can use a SwingWorker for that, but in many cases a simple Thread does the job:
Thread t = new Thread(){
#Override
public void run(){
// do stuff
}
};
t.start();
Or if you use Java 8+:
Thread t = new Thread(() -> {
// do stuff
});
t.start();
Related
I am working with a Java Swing application, where I have a main frame and multiple panels which I commute between by setting them visible or not and in the same time, I am instantiating a class which is running a while loop in the background. Now, the problem is: the panels don't appear unless that while loop ends, but I would like to let the user click some buttons while the while loops continues in the background, without even him knowing about that. Here is a small example of my code:
startPage.setVisible(false);
lblError.setVisible(false);
new QuestionPage(Integer.parseInt(fieldUserID.getText()));
QuestionPage has a while loop going, and I would like to not freeze the whole application until that is finished, but to let that while loop run in the background. So far I tried doing 2 threads by extending the Thread class, but I am not sure if this is the right way to do it.
[EDIT]
Here is my NEXT button after using a SwingWorker in order to send in background the while loop which happens in QuestionPage class and to carry on with swing operations on the main frame
btnStart.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (validateInput(fieldUserID.getText(), fieldAge.getText(), fieldSex.getSelectedItem().toString(), fieldExperience.getText())) {
startPage.setVisible(false);
lblError.setVisible(false);
SwingWorker worker = new SwingWorker<Void, String>() {
#Override
public Void doInBackground() {
new QuestionPage(Integer.parseInt(fieldUserID.getText()));
return null;
}
};
} else {
lblError.setVisible(true);
}
};
});
[ANSWER]
The trick is to use a SwingWorker to send the long running task in the background and to also call execute() on it. Here is a minimal working example for a Start button event listener:
// Send long running task in background
SwingWorker worker = new SwingWorker<Void, String>() {
#Override
public Void doInBackground() {
new QuestionPage(Integer.parseInt(fieldUserID.getText()));
return null;
}
};
worker.execute();
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 hava a GUI that has a button that need to be pressed periodically in the sense it should pause in between. But i am not able to do that. I have tried with Thread.sleep(). Below is the code.
protected Object doInBackground() throws Exception {
while(true){
btnSend.doClick();
try {
Thread.sleep(2000);
continue;
} catch (InterruptedException e1) {
}
}
return null;
}
Can anyone tell me where i am going wrong and how to solve it?
You shouldn't use a SwingWorker for this since you should not call doClick() off the event thread. If al you want to do is make this call intermittently, then simply use a Swing Timer for calls that need to be done intermittently and on the event thread.
int timerDelay = 2000;
new Timer(timerDelay, new ActionListener() {
public void actionPerformed(ActionEvent evt) {
btnSend.doClick();
}
}).start();
Edit
You ask in comment:
But i need to update another UI on that button click event. If i dont use swingworker the other UI will freeze. I have to use swingworker. Is it possible to do so?
You've got it wrong, I fear. The button click and GUI code should all be on the EDT. Any background non-GUI code that is generated by the button click's ActionListener should be done in a SwingWorker but not the click itself.
e.g.,
elsewhere
btnSend.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
new SwingWorker<Void, Void>() {
public Void doInBackground() throws Exception {
//.....
return null;
}
}
}
});
If you need more specific advice regarding your situation, then you'll want to consider creating and posting a Minimal, Complete, and Verifiable Example Program where you condense your code into the smallest bit that still compiles and runs, has no outside dependencies (such as need to link to a database or images), has no extra code that's not relevant to your problem, but still demonstrates your problem.
hi im creating a flashing text frame by using threading handling method, here is my code:
import javax.swing.*;
public class FlashingText extends JApplet implements Runnable {
/**
*
*/
private static final long serialVersionUID = 1L;
private JLabel jlblText = new JLabel("welcome",JLabel.CENTER);
public FlashingText() {
add(jlblText);
new Thread(this).start();
}
#Override
public void run() {
try {
while(true) {
if(jlblText.getText() == null) {
jlblText.setText("Welcome");
Thread.sleep(2000);
} else
jlblText.setText(null);
}
} catch(InterruptedException ex) {
}
}
}
after i compiled and ran it, it seems the text does not flashing at all
is there anything wrong with my code?
thanks a lot!
There's a better solution, which updates the UI in Event Dispatcher Thread and does not block it.
final JLabel label = new JLabel("Some text");
final Runnable updater = new Runnable() {
#Override
public void run() {
label.setVisible(!label.isVisible());
}
};
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
executorService.scheduleAtFixedRate(new Runnable() {
#Override
public void run() {
SwingUtilities.invokeLater(updater);
}
}, 2, 2, TimeUnit.SECONDS);
From the code, it does not really seem that you are flashing anything. Some issues I see with your code:
If the label has no text, the getText() method will yield an empty string ("") and not null.
When updating visual components, you would need to go through the Event Dispatcher Thread (EDT). This is exposed to you through the SwingUtilities.invokeLater(Runnable runnable) class.
It is usually a bad idea to sleep() threads. If you make the changes through the EDT, you would be hanging the ED Thread which will cause the application UI to freeze, which is not desired.
You are swallowing exceptions. In your exception handling, you are not doing anything. It is considered bad practice to not handle exceptions (sometimes a simple log message will do).
According to me there is a problem in the following code block:
try {
while(true) {
if(jlblText.getText() == null) {
jlblText.setText("Welcome");
Thread.sleep(2000);
} else
jlblText.setText(null);
}
}
Because see at the first time the value is welcome, so it will enter the loop and go to else and set it null and then immediately it will check again, as there is no sleep in else so it will check again and enter the if block and set it to welcome, and this whole process will be done at a great speed so you would not be able to see the flashing effect. So I think that you should try putting a sleep at the end of the else block and see, according to me it should work then.
You should change:
else
jlblText.setText(null);
to
else{
jlblText.setText(null);
Thread.sleep(500);
}
or something like this
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.