JavaFX:服务和GUI

5

我正在为一个学校项目编写PLC的可视化小应用程序。因此,我有一个包含所有信息的mySQL数据库。目前,当我点击按钮时,程序会连接到数据库并在ArrayList中获取信息。然后它会检查ArrayList中的信息并将数据放入ListView中。

问题是,我想让程序在服务中完成这些操作。正如我所说,GUI取决于ArrayList。但我不能在服务中更改GUI,否则会出现异常。

  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)

我的第一个想法是从服务中仅提取SQL相关内容,然后通过该方法获取数据。

service.getValue();

但是这种方法存在一个问题,我不知道何时服务完成获取数据,因为目前数据为空。


当多个线程尝试修改同一个列表时,就会发生此异常。 - GOXR3PLUS
2个回答

6

基本上,您想要运行某些进程并返回某些值, 当然,Service可以胜任。这是我的方案并且它完美地运行。

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();

注意事项

  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.

我希望这能有所帮助。

谢谢你的帮助,它帮了很大的忙。 - Torben
有没有一种方法可以从另一个类更新进度,因为我在外部类中处理所有的SQL操作,并希望能够从那里更新。 - Torben
我认为你不应该这样做。我尝试过了,它真的很混乱。你可以考虑这样做……那个执行sql操作的外部类应该只返回一些状态(Service将其传递给其内部方法updateValue(Object)),然后你可以使用从ObjectX processValue = (ObjectX)processList.getValue();获取的状态来更新你的用户界面。 - Oniya Daniel

2

Service类提供了一些方法,让你知道它是否已经被取消、成功或失败

在扩展Service类的类的构造函数中使用下列方法:

        // succeeded?
        this.setOnSucceeded(s -> {
            // ...
        });

        // failed
        this.setOnFailed(f -> {
            // ...
        });

        // cancelled?
        this.setOnCancelled(c -> {
            // ...
        });

还要记得使用 updateProgress(current, maximum) 函数,这样你就可以在执行过程中了解发生了什么。

以下是完整示例:

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

        };
    }

}

@Torben :) 很高兴听到这个消息 - GOXR3PLUS

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接