JavaFX: 如何从线程中更新(@FXML)进度条?

3

我希望能够通过另一个类对在控制器线程中初始化的FXML文件中定义的JavaFX ProgressBar进行更新。目前,它没有被更新。

test.fxml

<ProgressBar fx:id="progressBar" prefWidth="5000.0" progress="0.0">
    <VBox.margin>
        <Insets top="3.0" />
    </VBox.margin>
</ProgressBar>

Controller.java

@FXML
public static ProgressBar progressBar = new ProgressBar(0);

MyMain main;

@FXML
private void handleStartWork() throws Exception {
    new Thread() {
        @Override
        public void run() {
            try {
                main = new MyMain();
                main.doIt();
            } catch (final Exception v) {
                // ...
            }
        }
    }.start();
}

MyMain.java

public void doIt(){
    while(...){
        Platform.runLater(() -> PoCOverviewController.progressBar.setProgress((count / sum) * 100));
    }
}

我已经尝试过不同的版本,考虑到类似以下帖子的内容:

我不知道将ProgressBar设为静态变量是否是正确的方法。我只是不想通过工作流传递对象。

更新(Xavier Lambros回答):现在我尝试使用单例模式,但仍然无法正常工作:

Controller.java

@FXML
public ProgressBar progressBar = new ProgressBar(0);    

private static Controller INSTANCE = new Controller();

public static Controller getInstance() {
    return INSTANCE;
}

public ProgressBar getProgressBar() {
    return progressBar;
}

MyMain.java

public void doIt(){
    while(...){
        Platform.runLater(() -> Controller.getInstance().getProgressBar()
                .setProgress((count / sum) * 100));
    }
}

可能是javafx 8兼容性问题 - FXML静态字段的重复。 - fabian
@fabian 已将 ProgressBar 的 static 修饰符移除,但仍无法正常工作。 - Simon S.
countsumint 还是 long 类型?接下来的问题可能会有所帮助:https://dev59.com/5m445IYBdhLWcg3w0dVD - fabian
@fabian 实际上它们是不同的数据类型,但我已经将它们转换了。((double) count / (double) sum) * 100) 我想让这个例子尽可能地简短。 - Simon S.
2个回答

4
正如javafx 8兼容性问题 - FXML静态字段中所述,您不能使@FXML注释的字段static(而且这样做没有意义:这些字段本质上是特定控制器实例的属性)。
为了让doIt()方法可以访问进度条,您可以直接将其作为参数传递:
@FXML
public ProgressBar progressBar ;

MyMain main;

@FXML
private void handleStartWork() throws Exception {
    new Thread() {
        @Override
        public void run() {
            try {
                main = new MyMain();
                main.doIt(progressBar);
            } catch (final Exception v) {
                // ...
            }
        }
    }.start();
}

然后

public void doIt(ProgressBar progressBar){
    while(...){
        Platform.runLater(() -> progressBar.setProgress((count / sum) * 100));
    }
}

在某些情况下,Main类依赖于JavaFX API可能没有意义。在这种情况下,您可以只传递一个更新进度条的函数:
@FXML
public ProgressBar progressBar ;

MyMain main;

@FXML
private void handleStartWork() throws Exception {
    new Thread() {
        @Override
        public void run() {
            try {
                main = new MyMain();
                main.doIt(progressBar::setProgress);
            } catch (final Exception v) {
                // ...
            }
        }
    }.start();
}

并且

public void doIt(DoubleConsumer progressUpdate){
    while(...){
        Platform.runLater(() -> progressUpdate.accept((count / sum) * 100));
    }
}

请注意,您没有展示您的while循环中正在发生的情况:如果您向FX应用程序线程提交了过多的可运行任务,可能会“淹没”它并阻止它在合理的时间内进行更新。您可以考虑使用Task,它具有特定的API用于更新进度字段,并将进度条的progress属性绑定到该字段。如果仍然无法工作,您应编辑您的问题以包含MCVE

0

我认为你不能将ProgressBar设置为静态的。

我的方法是在控制器内部拥有一个ProgressBar访问器,并像这样初始化控制器:

FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/YourController.fxml");
loader.load();

之后,你可以通过以下方式访问你的 ProgressBar:

loader.<YourController>getController().getProgressBar();

如果你需要在不同的类中访问它,有很多其他的可能性,其中之一是创建一个单例:
public class Singleton
{   
    private ProgressBar progressBar; 

    private Singleton()
    {}

    private static Singleton INSTANCE = new Singleton();

    public static Singleton getInstance()
    {   
        return INSTANCE;
    }

    public ProgressBar getProgressBar() {
        return progressBar;
    }

    public ProgressBar setProgressBar() {
        return progressBar;
    }
}

调用它:

Singleton.getInstance().getProgressBar();

那么,在我的MyMain.java类中是否有一种方法可以调用“loader.<YourController>getController().getProgressBar();”,因为没有FXMLLoader对象? - Simon S.
把你的MyMain.java放在这里。 - Xavier Lambros
作为 StackOverflow 上的新手,我无法直接评论您的问题。但是我认为您不能直接将控制器设置为单例。单例应该是另一个类。FXML 控制器是特定的,必须遵守一些规则。否则,我不明白为什么您的 MyMain 中没有 FXMLLoader,因为在 JavaFX 项目中,MyMain 应该包含场景初始化...无论如何,单例的想法是将您在控制器初始化中设置的 progressBar 存储起来,并通过设计模式的 Singleton 让其在项目中任何地方都可以访问。 - Xavier Lambros
我不想说废话,但你为什么不直接将进度条传递给 doIt() 方法:main.doIt(progressBar)public void doIt(ProgressBar progressBar) { ... () -> progressBar.setProgress(...) } - James_D

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