I'm currently working on a JavaFX project. On GUI initialization I want to read some infos out of a HTML document using Selenium and FirefoxDriver. Normally I would use a crawler to get the infos but this document is full of JavaScript so I was only able to get to the infos using Selenium (I know, it's really bad).
Now I've got the problem that this process takes up to 15 seconds and I want to show the progress of Selenium on a JavaFX progress bar. So I've set up a Thread doing all the work and trying to update the GUI but the Thread freezes until Selenium is finished.
This is my attempt:
public class SeleniumThread extends Thread
{
private MainViewController main;
#Override
public void run()
{
try
{
WebDriver driver = new FirefoxDriver();
driver.get("http://---.jsp");
main.getMain().getPrimaryStage().toFront();
main.getPbStart().setProgress(0.1);
WebElement query = driver.findElement(By.id("user"));
query.sendKeys(new String[] {"Username"});
query = driver.findElement(By.id("passwd"));
query.sendKeys(new String[] {"Password"});
query.submit();
driver.get("http://---.jsp");
main.getPbStart().setProgress(0.2);
sleep(1000);
main.getPbStart().setProgress(0.25);
driver.get("http://---.jsp");
main.getPbStart().setProgress(0.4);
sleep(1000);
main.getPbStart().setProgress(0.45);
driver.get("---.jsp");
main.getPbStart().setProgress(0.6);
sleep(1000);
main.getPbStart().setProgress(0.65);
query = driver.findElement(By.cssSelector("button.xyz"));
query.click();
sleep(1000);
main.getPbStart().setProgress(0.85);
System.out.println(driver.getPageSource());
driver.quit();
}
catch(InterruptedException e)
{
// Exception ...
}
}
public MainViewController getMain()
{
return main;
}
public void setMain(MainViewController main)
{
this.main = main;
}
}
MainViewController
public void startup()
{
if(main.getCc().getMjUsername() != null &&
main.getCc().getMjPassword() != null &&
main.getCc().getMjUsername().length() != 0 &&
main.getCc().getMjPassword().length() != 0)
{
SeleniumThread st = new SeleniumThread();
st.setMain(this);
st.setDaemon(true);
st.run();
}
}
I've read that I should use a Worker like Task for it, but I have no clue how to implement it. And I need to pass a parameter to this Task, because I need to set my primaryStage to the front and update the progress bar.
I hope you can understand my problem. I'd be grateful for every help.
You are trying to update the UI from a different thread. The UI can only be updated from the UI thread. To achieve this, wrap the calls to update the progress:
Platform.runLater(() -> {main.getPbStart().setProgress(0.65);});
This will push the update of the UI into the UI thread.
You look to be trying to make JavaFX calls directly from within a background thread, and while I know little about JavaFX, I do know that this is not allowed, that JavaFX calls must be made on the JavaFX Application thread. See Concurrency in JavaFX.
You're not even creating a background thread. You call st.run(); which runs st on the calling thread -- not what you want. You should be calling st.start()!
As a side note, you seem to be extending Thread where you really want to be implementing Runnable. Thus you really should be calling new Thread(myRunnable).start();
Related
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();
I have a jframe i want to display while my main frame is running. i want to pause my main code, until the user does the necessary actions on the other frame. I've read a lot of solutions but i need to see it done for my code to understand and grasp it fully. i do not want to use jdialog like I've seen listed as an answer before. My main goal is to understand better threading so that i can use what i learn in different cases.
With the code I've created, when running the thread, only just the frame loads, none of the other features are there on the frame. (the frame is simple it has a label, a list the user selects from, and a button to basically return the chosen list value.) its like the thread is cut off from completing or something.
here is my class calling the screen:
public class myThread implements Runnable {
String result = null;
public void run() {
MessageScreen ms = new MessageScreen();
ms.setVisible(true);
}
public String getResult() {
return result;
}
public void setResult(String AS) {
result = AS;
}
}
in my main code, a method is called that is returning a String[] value, with this method at some point i have the following code calling the new thread to get the value necessary to return in the original main method:
myThread mt = new myThread();
Thread t = new Thread(mt);
t.start();
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
myreturn = new String[] {"true", mt.getResult()};
without listing the whole code for the second frame, when the user presses the button, and at the end of the listener tied to the button press the i want to close the frame and return a string that was selected from the list:
jf.dispose();
myt.setResult(AdminSelection);
in the frame class, i have the following instance variables declared:
String AdminSelection = null;
myThread myt;
i hope this is enough information for someone to help me out and understand whats gone wrong here.
The function join() waits until the end of the run() method, when you do t.join(), your thread is already or almost ended. This is because in your run() method there is nothing that blocks the thread until the user has clicked the confirm button. And is better like this!
There is no sense to create a thread here, you should use a callback, or more generally in Java, a listener. You can take a look at Creating Custom Listeners.
But, especially if you want to pause your main code, you should use a (modal) JDialog which is made for this! Don't try to block the UI by yourself, you could block the UI thread (handled by Swing/AWT) by mistake. Creating a JDialog is better because everything is already made for this usage on the UI thread.
Also, you must know that create a Thread is really long, use a Thread when you really need it.
I have the following code:
#FXML
private void test(){
textField.setText("Pending...");
boolean passed = doStuff();
if(passed){
textField.setText("OK");
} else {
textField.setText("Error");
}
}
And what I tries to achieve is that while the doStuff() do his stuff in a textField in the GUI there should be written "Pending..." and as soon as it finish it should change to "OK" / "Error".
I want that the GUI is blocked while doStuff is running so the user has to wait and can't click something else.
But what happens is that as soon as I start test it does the doStuff() but only updates the textField with "OK"/"Error" but I never see "Pending...".
I have the feeling that I have somehow update the GUI, but I'm not sure how it should be done.
Update:
What I tried is to move the doStuff in another Thread:
#FXML
private void test(){
textField.setText("Pending...");
Thread t = new Thread(){
public void run(){
boolean passed = doStuff();
if(passed){
textField.setText("OK");
} else {
textField.setText("Error");
}
}
};
t.start();
t.join();
}
It would works if i would remove the t.join(); command, but then the UI wouldn't be blocked. So I'm at a loss right now.
Thanks
You must never run long running tasks on the JavaFX Application Thread. Doing so will prevent said thread from doing any GUI related things which results in a frozen UI. This makes your user(s) sad. However, your attempt at putting the long running task on a background task is flawed. You call Thread.join which will block the calling thread until the target thread dies; this is effectively the same thing as just running the task on the calling thread.
For a quick fix to your example, you could do the following:
#FXML
private void test(){
textField.setText("Pending...");
Thread t = new Thread(){
#Override public void run(){
boolean passed = doStuff();
Platform.runLater(() -> {
if(passed){
textField.setText("OK");
} else {
textField.setText("Error");
}
});
}
};
t.start();
}
That will create a thread, start it, and let it run in the background while letting the JavaFX Application Thread continue doing what it needs to. Inside the background thread you must update the TextField inside a Platform.runLater(Runnable) call. This is needed because you must never update a live scene graph from a thread other than the JavaFX Application Thread; doing so will lead to undefined behavior. Also, you should look into “implements Runnable” vs “extends Thread” in Java. It's better, or at least more idiomatic, to do:
Thread t = new Thread(() -> { /* background code */ });
You could also use a javafx.concurrent.Task which may make it easier to communicate back to the JavaFX Application Thread. One option would be:
#FXML
private void test(){
textField.setText("Pending...");
Task<Boolean> task = new Task<>() {
#Override protected Boolean call() throws Exception {
return doStuff();
}
};
task.setOnSucceeded(event -> textField.setText(task.getValue() ? "Ok" : "Error"));
new Thread(task).start();
}
You could also bind the TextField to the message property of the Task and call updateMessage("Pending...") inside the call method. You could even provide more detailed messages if and when possible.
That said, creating and starting Threads yourself is not ideal and you should look into thread pooling (using something like an ExecutorService). You might also want to look into javafx.concurrent.Service for "reusing" Tasks.
For more information about JavaFX concurrency see Concurrency in JavaFX and read the documentation of the classes in javafx.concurrent. For the basics of multi-threading in Java see Lesson: Concurrency from The Java™ Tutorials.
I'm using SWT in a main GUI program. Within it, I create another thread to run some programs. However, if some errors are encountered in those processes, I want to report this to the user by making a message box appear. Because in SWT, only a single thread can perform GUI operations, I was having the program runner throw exceptions, so the GUI thread could deal with them. However, I am having problems because I create a new thread for the program runner (in order not to hold up the GUI thread, which will be continuously updating and refreshing some graphics), but as a result, the exceptions that take place are stuck as part of that thread, which can not create the error message box. Any suggestions on how to deal with this?
private void goButtonActionPerformed()
{
// create the program runner object
ProgramRunner PR = new ProgramRunner(); // real code passes in data to be used
try{
// check all necessary parameters are entered
boolean paramsOK = PR.checkParams();
if (paramsOK)
{
// all necessary information is available. Start Processing.
Thread t = new Thread() {
public void run()
{
try{
PR.runPrograms();
}
catch (IOException iox)
{
// This does not work to catch & display the exceptions
// which took place in PR.runPrograms(), because this
// thread is not allowed to perform GUI operations.
// However, I don't know how to pass this
// exception / error notification out of this thread.
MessageBox mb = new MessageBox(m_Shell, SWT.ICON_ERROR);
mb.setMessage(iox.getMessage());
mb.open();
}
}
};
t.start();
}
}
catch (IOException iox)
{
// this works to catch & display the exceptions which took place
// in PR.checkParams() because that is not a separate thread
MessageBox mb = new MessageBox(m_Shell, SWT.ICON_ERROR);
mb.setMessage(iox.getMessage());
mb.open();
}
Wrap catch logic inside a Display.getDefault().asyncExec to display error messages on UI thread:
Thread t = new Thread()
{
public void run()
{
try
{
PR.runProgram();
}
catch ( final IOException iox )
{
Display.getDefault().asyncExec( new Runnable()
{
public void run()
{
MessageBox mb = new MessageBox(m_Shell, SWT.ICON_ERROR);
mb.setMessage(iox.getMessage());
mb.open();
}
});
}
}
});
t.start();
the exceptions can be displayed in UI thread then.
You need to arrange that the UI code runs in the UI thread. You can do this using the asyncExec or syncExec methods of Display.
syncExec suspends the current thread until the UI code has been run. asyncExec does not suspend the thread and runs the UI code as soon as possible.
You can get the current display in any thread using Display.getDefault() so you might do something like:
Display.getDefault().asyncExec(() ->
{
if (m_Shell != null && !m_Shell.isDisposed()) {
MessageBox mb = new MessageBox(m_Shell, SWT.ICON_ERROR);
mb.setMessage(iox.getMessage());
mb.open();
}
});
I have used a Java 8 lambda expression for the Runnable here as it is shorter than the traditional method.
Since this code is being run asynchronously it is good practice to check that the shell is not null and has not been disposed.
I was building a small test tool with Java Swing using Netbeans IDE.
I am trying to update a label, which is somehow not getting 'repainted'/'refreshed'. I looked into a couple of similar questions on SO but was not able to resolve my problem.
private void excelFileChooserActionPerformed(java.awt.event.ActionEvent evt)
{
if(!JFileChooser.CANCEL_SELECTION.equals(evt.getActionCommand()))
{
String selectedFile = excelFileChooser.getSelectedFile().getAbsolutePath();
loaderLabel.setText("Please Wait..");
try {
//This is sort of a blocking call, i.e. DB calls will be made (in the same thread. It takes about 2-3 seconds)
processFile(selectedFile);
loaderLabel.setText("Done..");
missingTransactionsPanel.setVisible(true);
}
catch(Exception e) {
System.out.println(e.getMessage());
loaderLabel.setText("Failed..");
}
}
}
loaderLabel is a JLabel and the layout used is AbsoluteLayout.
So, my problem is "Please Wait..." is never shown. Although call to the method processFile takes about 2-3 seconds, "Please Wait..." is never shown. However, "Done..."/"Failed..." are shown.
If I add a popup (JOptionPane) before the call to processFile, "Please Wait.." is shown. I am not able to clearly understand why this is happening.
Is there a "good practice" that I should follow before a heavy method call? Do I need to call an explicit repaint/refresh/revalidate?
You need to call
processFile(selectedFile);
in another thread (not in the AWT thread). To do so you can do something like this :
Thread t = new Thread(){
public void run(){
processFile(selectedFile);
// now you need to refresh the UI... it must be done in the UI thread
// to do so use "SwingUtilities.invokeLater"
SwingUtilities.invokeLater(new Runnable(){
public void run(){
loaderLabel.setText("Done..");
missingTransactionsPanel.setVisible(true);
}
}
)
}
};
t.start();
Please not that I didn't work with swing for a long time, so there may be some syntax issues with this code.
Have you tried dispatching the call to the EDT with SwingUtilities.invokeLater() ?
http://www.javamex.com/tutorials/threads/invokelater.shtml