For a school project, I am programming a little Visualization for a PLC. Therefore I have a mySQL Database which has all the Information. Currently, when I click on a Button the program connects to the Database and gets it Information in an ArrayList. Then it checks the information in the ArrayList and puts Data in a ListView.
The problem is, that I want to let the program do that in a Service. As I said the GUI depends on the ArrayList. And I can't change the GUI in the service because then this Exception appears
Sep 02, 2016 9:19:02 PM javafx.concurrent.Service lambda$static$488
WARNING: Uncaught throwable in javafx concurrent thread pool
java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-4
at com.sun.javafx.tk.Toolkit.checkFxUserThread(Unknown Source)
at com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(Unknown Source)
at javafx.scene.Parent$2.onProposedChange(Unknown Source)
at com.sun.javafx.collections.VetoableListDecorator.setAll(Unknown Source)
at com.sun.javafx.collections.VetoableListDecorator.setAll(Unknown Source)
at com.sun.javafx.scene.control.skin.LabeledSkinBase.updateChildren(Unknown Source)
at com.sun.javafx.scene.control.skin.LabeledSkinBase.handleControlPropertyChanged(Unknown Source)
at com.sun.javafx.scene.control.skin.LabelSkin.handleControlPropertyChanged(Unknown Source)
at com.sun.javafx.scene.control.skin.BehaviorSkinBase.lambda$registerChangeListener$61(Unknown Source)
at com.sun.javafx.scene.control.MultiplePropertyChangeListenerHandler$1.changed(Unknown Source)
at javafx.beans.value.WeakChangeListener.changed(Unknown Source)
at com.sun.javafx.binding.ExpressionHelper$SingleChange.fireValueChangedEvent(Unknown Source)
at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(Unknown Source)
at javafx.beans.property.StringPropertyBase.fireValueChangedEvent(Unknown Source)
at javafx.beans.property.StringPropertyBase.markInvalid(Unknown Source)
at javafx.beans.property.StringPropertyBase.set(Unknown Source)
at javafx.beans.property.StringPropertyBase.set(Unknown Source)
at javafx.beans.property.StringProperty.setValue(Unknown Source)
at javafx.scene.control.Labeled.setText(Unknown Source)
at application.Controller$1$1.call(Controller.java:290)
at application.Controller$1$1.call(Controller.java:1)
at javafx.concurrent.Task$TaskCallable.call(Unknown Source)
at java.util.concurrent.FutureTask.run(Unknown Source)
at javafx.concurrent.Service.lambda$null$493(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at javafx.concurrent.Service.lambda$executeTask$494(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
My first Idea was to extract only the SQL stuff in the Service and then get the Data via the Method
service.getValue();
But the problem with this is that I don't know when the Service is finished to get the Data, because currently, the data is null.
The Service
class provides methods so you know if it has been cancelled,succeded or failed.
In the Constructor of your Class which extends Service use these methods :
// succeeded?
this.setOnSucceeded(s -> {
// ...
});
// failed
this.setOnFailed(f -> {
// ...
});
// cancelled?
this.setOnCancelled(c -> {
// ...
});
Also remember to use updateProgress(current,maximum)
; so you know what is happening during it's execution.
Here is a full example:
import javafx.concurrent.Service;
import javafx.concurrent.Task;
public class ExampleService extends Service<Boolean> {
/**
* Constructor
*/
public ExampleService() {
// succeeded?
this.setOnSucceeded(s -> {
// ...
});
// failed
this.setOnFailed(f -> {
// ...
});
// cancelled?
this.setOnCancelled(c -> {
// ...
});
}
@Override
protected Task<Boolean> createTask() {
return new Task<Boolean>(){
@Override
protected Boolean call() throws Exception {
boolean result = false;
//your code
//.......
//updateProgress(current,total)
return result;
}
};
}
}
Basically, you want to run some process and return some value,
Of course Service
nails it. This is how I go about my case and it works perfectly.
Service process = new Service() {
@Override
protected Task createTask() {
return new Task() {
@Override
protected ObjectX call() throws Exception {
updateMessage("Some message that may change with execution");
updateProgress( workDone, totalWork );
return ObjectX;
}
};
}
};
process.setOnSucceeded( e -> {
ObjectX processValue = (ObjectX)process.getValue();
// TODO, . . .
// You can modify any GUI element from here...
// ...with the values you got from the service
});
process.start();
THINGS TO NOTE
protected ObjectX call()
can return any type of object. Just make sure its an object and not a primitive type. You can go as far as populate some GUI elements inside this process and return it as an object, eg. protected VBox call()
. . . . return my_vbox;
ObjectX processValue = (ObjectX)processList.getValue();
=> You should cast the value you got from the Service
back to the Object you want to use.
If is just Object
, you may not have to. But I doubt if you will ever have to use just Object
.processList.setOnFailed()
, processList.setOnRunning()
, processList.setOnCancelled()
, processList.setOnScheduled()
,You can also bind some GUI elements to some Thread properties like this
label.textProperty.bind( process.messageProperty ); // messageProperty is a StringProperty
progressBar.progressProperty.bind( process.progressProperty )
make sure all the methods to further enhance your process have been created and initiated before calling the process.start()
; Nothing happens until you have started the process.
I hope this helps
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With