为什么我的 JavaFX 进度指示器没有更新?

Why isnt my JavaFX Progress Indicator not updating?

我的 JavaFX 应用程序中有以下布局,

StackPane
   - BorderPane 
    - Text (on Top) 
    - Vbox (on Center)
        - ProgressIndicator
        - Label
   - BorderPane
        - Scroll Pane (with Tile Pane inside) (Center of Border Pane)
        - Button (Bottom of Border)

   - BorderPane
     - Vbox (on Center)
       - Progress Indicator
       - Label
     - Button ( Bottom)

显示第一个边框窗格时,进度指示器运行良好。我使用以下代码进行设置,

progress.progressProperty().bind(iconLoadTask.progressProperty());
progresspane.visibleProperty().bind(iconLoadTask.runningProperty());

当 iconLoadTask 完成时,进度窗格变为不可见,下一个窗格正确显示。

在下一个 BorderPane(具有滚动窗格)中,我显示了最后一个边框窗格(带有进度指示器 - 不确定)。这个进度指示器根本没有动画。

编辑:

按照建议,我已经尝试重现这个问题,

我有以下控制器文件。在这里您可以看到两个进度指示器(progress 和 stopProfInd)。进度从任务 appLoadTask 更新。这是一项有限的任务,我可以更新任务的进度。这很好用。

我有另一个指标叫做 stopProfInd。这是由 Scene Builder 不确定设置的。在这里您可以看到我使该窗格可见。我希望指示器可以动画,但它没有。

public class AppController
    implements Initializable {

@FXML
private StackPane stackpaneapps;

@FXML
private BorderPane progresspane;

@FXML
private ProgressIndicator progress;

@FXML
private ProgressIndicator stopProfInd;

@FXML
private BorderPane profilingpane;

@FXML
private Label devicename;

private ObservableValue<Number> profProperty;

// Reference to the main application.
private Main mainApp;

private AppIconsTask appLoadTask;

private ProfilingTask prTask;

private Queue<MyVbox> clickedAppQueue;

@Override // This method is called by the FXMLLoader when initialization is complete
public void initialize(URL fxmlFileLocation, ResourceBundle resources) {
    clickedAppQueue = new LinkedList<MyVbox>();
}
/**
 * Is called by the main application to give a reference back to itself.
 *
 * @param mainApp
 */
public void setMainApp(Main mainApp) {
    this.mainApp = mainApp;
}

/**
 * Is called by the main application to give a reference back to itself.
 *
 * @param task
 *
 */
public void setAppLoadTask(AppIconsTask task) {
    this.appLoadTask = task;
    progress.progressProperty().bind(appLoadTask.progressProperty());
    progresspane.visibleProperty().bind(appLoadTask.runningProperty());
    appLoadTask.setOnSucceeded(t -> drawAppIcons(appLoadTask.getApps()));
    profilingpane.setVisible(false);
}


void drawAppIcons(ObservableList<App> apps){
    progress.setVisible(false);
    profilingpane.setVisible(true);

    prTask = new ProfilingTask();
    stopProfInd.progressProperty().bind(prTask.progressProperty());
    new Thread(prTask).start();
}
}

有什么线索吗?

FXML:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.*?>
<?import javafx.scene.text.*?>
<?import javafx.scene.paint.*?>
<?import javafx.scene.effect.*?>
<?import javafx.scene.*?>
<?import javafx.scene.image.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>

<AnchorPane prefHeight="263.0" prefWidth="304.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.gamebench.ios.controller.AppController">
   <children>
      <StackPane fx:id="stackpaneapps" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="5.0" AnchorPane.rightAnchor="5.0" AnchorPane.topAnchor="10.0">
         <children>
            <BorderPane fx:id="profilingpane" prefHeight="200.0" prefWidth="200.0">
               <center>
                  <VBox alignment="CENTER" prefHeight="200.0" prefWidth="100.0" BorderPane.alignment="CENTER">
                     <children>
                        <ProgressIndicator fx:id="stopProfInd" />
                     </children>
                  </VBox>
               </center>
            </BorderPane>
            <BorderPane fx:id="progresspane" prefHeight="200.0" prefWidth="200.0">
               <center>
                  <VBox alignment="CENTER" prefHeight="200.0" prefWidth="100.0" BorderPane.alignment="CENTER">
                     <children>
                        <ProgressIndicator fx:id="progress" maxHeight="100.0" maxWidth="100.0" prefHeight="68.0" prefWidth="77.0" />
                     </children>
                  </VBox>
               </center>
            </BorderPane>
         </children>
      </StackPane>
   </children>
</AnchorPane>

按要求添加任务实施。以下是适用于 appIconsTask 的,效果很好。

    protected ObservableList<App> call() throws Exception {
    String deviceId = "blah";
    /* We will start before we get the list of installed apps */
    updateProgress(0.0f , 100);
    // Get list of installed apps
    App[] installedApps = getAppsList(deviceId , false);
    appList = FXCollections.observableList(new ArrayList<App>());
    int numApps = installedApps.length;

    double progress = 1.0f;
    for(App app: installedApps){

        byte[] pngData;
        pngData = comm.getAppIcon(deviceId, app.getBundleId());
        app.setAppIcon(pngData);
        appList.add(app);
        updateProgress((progress/numApps)*100, 100);
        progress += 1.0f;
    }
    return appList;
}

下一个进度指标的任务实现,

protected Void call() throws Exception {
    double f = 1.0;
    while(!getStopProf()){
        Thread.sleep(30);
    }
    return null;
}

这里重现问题, https://github.com/h-karthik/BugReproWhosebug

所以,原来这可能是我的无知,也可能是一个错误。

在我的代码中而不是下面一行

profilingpane.setVisible(true);

我使用以下方法,问题消失了。这更像是解决问题的技巧。

profilingpane.setOpacity(0.0);

然后当我想要显示窗格时,

profilingpane.setOpacity(1.0);

我认为您 运行 进入 this bug,在撰写本文时,它已在最新的 GA 版本 (JDK 8u20) 中修复。如果可能的话,最简单的解决方法是升级到并需要该版本。

如果您不能这样做,下一个最直观的选择是避免使用 setVisible(...) 并在需要时将进度指示器添加到场景图中。您可以使用 <fx:define> 定义场景图中未包含的 FXML 元素。像往常一样注入它,然后根据需要将元素添加到场景图中。 (如果需要,您可以用类似的方式将其删除。)

您的 FXML 文件变为:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.*?>
<?import javafx.scene.text.*?>
<?import javafx.scene.paint.*?>
<?import javafx.scene.effect.*?>
<?import javafx.scene.*?>
<?import javafx.scene.image.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>

<AnchorPane prefHeight="263.0" prefWidth="304.0"
    xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"
    fx:controller="com.Whosebug.repro.controller.AppController">
    <children>
        <StackPane fx:id="stackpaneapps" AnchorPane.bottomAnchor="0.0"
            AnchorPane.leftAnchor="5.0" AnchorPane.rightAnchor="5.0"
            AnchorPane.topAnchor="10.0">
            <children>
                <BorderPane fx:id="progresspane" prefHeight="200.0"
                    prefWidth="200.0">
                    <center>
                        <VBox alignment="CENTER" prefHeight="200.0" prefWidth="100.0"
                            BorderPane.alignment="CENTER">
                            <children>
                                <ProgressIndicator fx:id="progress" maxHeight="100.0"
                                    maxWidth="100.0" prefHeight="68.0" prefWidth="77.0" />
                            </children>
                        </VBox>
                    </center>
                </BorderPane>
            </children>

        </StackPane>
    </children>
    <fx:define>
        <BorderPane prefHeight="200.0" prefWidth="200.0" fx:id="profilingpane">
            <center>
                <VBox alignment="CENTER" prefHeight="200.0" prefWidth="100.0"
                    BorderPane.alignment="CENTER">
                    <children>
                        <ProgressIndicator fx:id="stopProfInd" />
                    </children>
                </VBox>
            </center>
        </BorderPane>
    </fx:define>
</AnchorPane>

控制器变为

package com.Whosebug.repro.controller;


import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;

import com.Whosebug.repro.Main;
import com.Whosebug.repro.tasks.AppIconsTask;
import com.Whosebug.repro.tasks.ProfilingTask;

import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.*;


public class AppController
        implements Initializable {

    @FXML
    private StackPane stackpaneapps;

    @FXML
    private BorderPane progresspane;

    @FXML
    private ProgressIndicator progress;

    @FXML
    private ProgressIndicator stopProfInd;

    @FXML
    private BorderPane profilingpane;

    // Reference to the main application.
    private Main mainApp;

    private AppIconsTask appLoadTask;

    private ProfilingTask prTask;


    @Override // This method is called by the FXMLLoader when initialization is complete
    public void initialize(URL fxmlFileLocation, ResourceBundle resources) {
    }
    /**
     * Is called by the main application to give a reference back to itself.
     *
     * @param mainApp
     */
    public void setMainApp(Main mainApp) {
        this.mainApp = mainApp;
    }

    /**
     * Is called by the main application to give a reference back to itself.
     *
     * @param task
     *
     */
    public void setAppLoadTask(AppIconsTask task) {
        this.appLoadTask = task;
        progress.progressProperty().bind(appLoadTask.progressProperty());
        progresspane.visibleProperty().bind(appLoadTask.runningProperty());
//        profilingpane.setVisible(false);
        appLoadTask.setOnSucceeded(t -> drawAppIcons());

    }


    void drawAppIcons(){
//        profilingpane.setVisible(true);
        stackpaneapps.getChildren().add(profilingpane);
        prTask = new ProfilingTask();
        new Thread(prTask).start();
    }
}

您的不透明度解决方法也有效,但这感觉更像是一种更自然的方法。功能上存在细微差别,从 StackPane 中的所有内容来看并不明显。使用 setVisible(当它正常工作时)和 setOpacity(...) 解决方法,进度指示器将在布局中占据 space。在需要时添加和删除它,当它不存在时不会在布局中占用 space。因此,方法的选择可能取决于您希望它在布局方面如何表现。

关于您的代码的一些其他评论:

如果您将线程 运行 设置为 prTask 守护线程,当您关闭最后一个 window 时,它不会阻止 JVM 退出。显然,您可能有其他机制可以在您的真实应用程序中关闭它,但它可能是一个有用的技巧(如果您从 Eclipse 中测试它,就不那么烦人了……):

    Thread prThread = new Thread(prTask);
    prThread.setDaemon(true);
    prThread.start();

此外,您在分析任务中的线程代码看起来不太正确。 stopProf 字段几乎肯定在不同的线程上更改为读取它的线程。这意味着不能保证该字段的活跃性(在一个线程中更改它和该更改在另一个线程中可见之间可能存在任意延迟,可能是不确定的)。您应该执行以下操作之一:

  1. 将字段标记为volatile,或
  2. getStopProf()setStopProf(...)方法标记为synchronized,或
  3. boolean 字段替换为 AtomicBoolean

第三个选项是我更喜欢的选项(优先使用高级 API 而不是低级原语):

package com.Whosebug.repro.tasks;

import java.util.concurrent.atomic.AtomicBoolean;

import javafx.concurrent.Task;

/**
 * Created by karthik on 17/02/15.
 */
public class ProfilingTask extends Task<Void> {

    AtomicBoolean stopProf;
    @Override
    protected Void call() throws Exception {
        double f = 1.0;
        while(!getStopProf()){
            Thread.sleep(30);

        }
        return null;
    }
    public boolean getStopProf(){
        return stopProf.get();
    }

    public void setStopProf(boolean stop){
        stopProf.set(stop);
    }
}