Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaFX: Service and GUI

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.

like image 572
Torben Avatar asked Sep 02 '16 19:09

Torben


2 Answers

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;
            }

        };
    }

}
like image 44
GOXR3PLUS Avatar answered Oct 07 '22 08:10

GOXR3PLUS


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

  1. inner method 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;
  2. 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.
  3. also see processList.setOnFailed(), processList.setOnRunning(), processList.setOnCancelled(), processList.setOnScheduled(),
  4. 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 )
    
  5. 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

like image 95
Oniya Daniel Avatar answered Oct 07 '22 07:10

Oniya Daniel