I have a "little" issue with Service usage.
The code below doesn't work: text value isn't updated in HMI but its value is correct !!?
public class FilterController
{
#FXML
private TextField totalItemCount;
private final Service service = new Service() {
#Override
protected Task createTask()
{
return new Task<Void>() {
#Override protected Void call() throws Exception {
int x = (int) (Math.random() * 10000);
System.out.println("x = " + x);
try {
totalItemCount.setText(Integer.toString(x));
System.out.println("totalItemCount = " + totalItemCount.getText());
}
catch (Throwable ex)
{
System.err.println("Fail");
ex.printStackTrace();
}
return null;
}
};
}
#Override
protected void failed()
{
super.failed();
System.err.println("FAILED");
}
};
#FXML
public void handleFindProblemsEvent()
{
System.out.println("Handle Find Problems");
service.restart();
}
}
I don't have any error. Fail message isn't displayed, so I can think that job has been done but it's not the case.
Is it a bug or a bad usage ?
Thanks for your help.
Note: I use jre1.8.0_25
JavaFX is a single thread GUI toolkit, so every update of a GUI component has to be done on the main application (JavaFX) thread.
What you are doing there, is trying to update a TextField from a background thread and an IllegalStateException will get thrown.
The Task and Service classes are meant to compute something in the background and do a GUI update afterwards.
Like explained over here and over here, you should create a Task<Integer> and return the computed value. If this succeeds, you can retrieve the value in the succeeded() method with getValue() and set the value to the TextField.
The succeeded() method is getting called from the GUI Thread, so its safe to update the TextField here.
You have not called the failed() method anywhere.
I assume you Task is executed in its own thread so you need to sync calls to fx APIs with Platform.runLater
Related
I have a SwingWorker class whose doInBackground executes queries on a remote database. I invoke publish(true) which sets setVisible to true for the JDialog holding a loader animation.
Everything is working fine as expected:
Background method starts.
JDialog modal is shown.
Background method completes.
JDialog is hidden/disposed in done() method.
UI is updated with database values.
However, when I point my application to the database running on my local machine the JDialog is shown but never closed/disposed even though the done() was called. This halts execution of UI code in the done method as well not until I manually close the loader dialog.
This odd behaviour is as follows:
Background method starts.
JDialog modal is shown.
Background method completes.
JDialog is NOT hidden/disposed in done() method.
UI is NOT updated
I must mention that execution over the remote database takes 10 seconds or more but a split second on my local database. How is the faster speed causing this odd behaviour?
Below is my code snippet:
new SwingWorker<Void, Boolean>() {
JDialog loader = new MyDialogLoader();
#Override
protected Void doInBackground() {
publish(true);
//DATABASE EXECUTION CODE IS HERE
publish(false);
return null;
}
#Override
protected void process(List<Boolean> chunks) {
for (Boolean val : chunks) {
loader.setVisible(val);
}
}
#Override
protected void done() {
loader.dispose();
//UI UPDATE WITH DATABASE VALUES CODE HERE;
}
}.execute();
Probably there is an Exception being thrown in doBackground(), so publish(false) is not being executed. Maybe an error accessing the database...
The Exceptions thrown by doBackground are silently caught and saved by the SwingWorker. The way to check if such an Exception was thrown, is to call the get() method in the done() method. It will throw an ExecutionException having the original exception as cause:
#Override
protected void done() {
loader.dispose();
try {
get();
} catch (ExecutionException ex) {
ex.printStackTrace();
// some message or what ever using ex.getCause()
} catch (InterruptedException ex) {
ex.printStackTrace();
// TODO
}
//UI UPDATE WITH DATABASE VALUES CODE HERE;
}
I am sharing this as the answer because it has solved the issue.
I did away with publish and process methods; the loader JDialog is now made visible from doInBackground. To ensure this UI interaction is performed on the Event Dispatch Thread, the statement is placed inside SwingUtilities.invokeLater.
new SwingWorker<Void, Void>() {
JDialog loader = new MyDialogLoader();
#Override
protected Void doInBackground() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
try {
loader.setVisible(true);
} catch (Exception e) {
e.printStackTrace()
}
}
});
//DATABASE EXECUTION CODE IS HERE
return null;
}
#Override
protected void done() {
loader.dispose();
//UI UPDATE WITH DATABASE VALUES CODE HERE;
}
}.execute();
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();
Attached is the code snippet below. I am new to multi-threading. Attempted to do multi threading which sort of works. However, after I click the button the first time, the second time onwards would not "create the thread" and run my method anymore.
I have also experimented with implementing the Runnable interface, but it could not get my Anchorpane reference to load the snackbar and hence I used the task method instead. Appreciate your help!
#FXML
private AnchorPane anchorPane;
Thread thread;
#FXML
void onClickLoginButton(ActionEvent event) throws Exception {
thread = new Thread(task);
thread.start();
}
Task<Void> task = new Task<Void>() {
#Override
public Void call(){
//System.out.println("Thread running"+thread.getId());
try {
credential = login.login();
} catch (UnknownHostException u) {
Platform.runLater(() -> {
System.out.println("No wifi");
JFXSnackbar snackbar = new JFXSnackbar(anchorPane);
snackbar.show("Please check your internet connection", 3000);
//u.printStackTrace();
});
} catch (Exception e) {
//e.printStackTrace();
}
//System.out.println("Thread running"+thread.getId());
return null;
}
};
The reason why this runs only once has to do with how Task works in general
Per Task's documentation here: https://docs.oracle.com/javase/8/javafx/api/javafx/concurrent/Task.html
As with FutureTask, a Task is a one-shot class and cannot be reused.
See Service for a reusable Worker.
Thus, if you want to repeat the said process multiple times, using a Task is not a good choice. Check the recommended alternatives for workers and services in you want to achieve something like that.
I have external Processor.process(...) heavy method that can be running for a long time (tens of minutes) in normal case.
I want to show some loading screen with 'Cancel' button and run that Processor in background. I want to stop executing background task when 'Cancel' button clicked.
The problem is that I have a single call to Processor.process(...).
If I had some task in a loop, I would do checks like if( isCancelled() ){ break; } or similar on each iteration.
Is there any way to terminate|kill that heavy background task? Let's assume that Processor.process(...) is very long 'atomic' operation (I just don't want to change all the logic deep behind it to accomplish this single task)
Here is how I start loading screen and background task:
private void handleProcess(ActionEvent event) {
SomeService service = new SomeService();
// ... configuring service
Stage loadingStage = new Stage();
// ... configuring 'loading' stage
service.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent t) {
#SuppressWarnings("unchecked")
Entity entity = (Entity) t.getSource().getValue();
// ... do some stuff
loadingStage.close();
}
});
service.start(); // launch background process
loadingStage.show(); // show 'loading' screen
}
UPDATED Here is Service for background job:
private static class SomeService extends Service<Entity> {
private Thread threadToStopOnCancel; // added
// fields, getters, setters omitted
protected Task<Entity> createTask() {
// _params initialization omitted
return new Task<Entity>() {
protected Entity call() throws IOException {
threadToStopOnCancel = Thread.currentThread(); // added
return Processor.process(_params);
}
};
}
#SuppressWarnings("deprecation")
#Override
protected void cancelled() {
if (threadToStopOnCancel != null) { threadToStopOnCancel.stop(); } // added
super.cancelled();
}
For now, I call service.cancel(); from loading screen. But it just sets the service's state to 'CANCELED', and the task is still working in background.
In your service definition you can override the cancelled() method or pass an EventHandler to the setOnCancelled() method.
See here.
I am creating a Java application with a downloader. My problem is, the progress bar is not working. I want my progress bar to show the download progress but failed. Here is some part of my code. The progressbar just stuck at 0%...
Download.class
public void startDownload()
{
ExecutorService executor = Executors.newSingleThreadExecutor();
FutureTask<Void> verDownloader = new FutureTask<Void>(vd);
FutureTask<Void> launcher = new FutureTask<Void>(dd);
executor.execute(verDownloader);
executor.execute(launcher);
executor.shutdown();
}
VersionDownloader.class
public class VersionDownloader implements Callable<Void>, PropertyChangeListener
{
#Override
public Void call() throws Exception
{
done = false;
final SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>()
{
#Override
protected Void doInBackground() throws Exception
{
try
{
URL fileURL = new URL(url);
org.apache.commons.io.FileUtils.copyURLToFile(fileURL, path);
}
catch(Exception e)
{
}
return null;
}
#Override
public void done()
{
done = true;
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
};
worker.execute();
worker.addPropertyChangeListener(this);
worker.get();
}
}
}
catch(Exception e)
{
}
return null;
}
#Override
public void propertyChange(PropertyChangeEvent evt)
{
if(!done)
{
int progress_a = progress;
//launcher.frame.progress is a JProgressBar
launcher.frame.progress.setValue(progress_a);
}
}
}
Is that any code wrong?
This sort of thing is less trivial than it sounds. copyURLToFile() is going to block until the copy is completed, so that will only give you two events - 0% and 100%.
If you want to show progress while you do the download, there is one prerequisite: You must know the file length before the download starts (so you can compute percentages). You could get that - maybe - by issuing an HTTP HEAD request before starting the copy - but whether the Content-Length field is there in the response depends on the server - for chunked encoding, you don't get this information (though you might be able to force some servers to tell you by issuing an HTTP 1.0 request). Failing that, you could cheat and pick an arbitrary number of bytes.
Once you have the length or an approximation, you can either
Run a timer and periodically check the number of bytes downloaded so far, and compare that with the size, OR
Open the URL as an InputStream and do your own loop to copy the bytes, and on each loop iteration, update the progress bar
Either way, make sure you use EventQueue.invokeLater() to update the progress bar's value property or you may have deadlock issues - it is not safe to modify Swing components from any thread but the event thread.
Read the section from the Swing tutorial on How to Use Progress Bars for a working example that uses a SwingWorker.
Start with something that works and make changes for your particular requirement. If you still have problems then post a SSCCE that demonstrates the problem.