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

时间:2015-02-19 17:47:31

标签: javafx javafx-8

我的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(具有Scroll窗格),我显示最后一个边框窗格(带有进度指示器 - 不确定)。这个进度指标根本没有动画。

编辑:

正如所建议的那样,我已尝试重现此问题,

我有以下控制器文件。在这里,您可以看到两个进度指示器(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/BugReproStackOverflow

2 个答案:

答案 0 :(得分:1)

我认为你遇到了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.stackoverflow.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.stackoverflow.repro.controller;


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

import com.stackoverflow.repro.Main;
import com.stackoverflow.repro.tasks.AppIconsTask;
import com.stackoverflow.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(...)解决方法时,进度指示器将占用布局中的空间。通过在需要时添加和删除它,在不存在时不会占用布局中的空间。因此,方法的选择可能取决于您希望它在布局方面的表现。

关于您的代码的其他几条评论:

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

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

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

  1. 将字段标记为volatile
  2. getStopProf()setStopProf(...)方法标记为synchronized
  3. boolean字段替换为AtomicBoolean
  4. 第三个选项是我喜欢的选项(支持高级API而不是低级原语):

    package com.stackoverflow.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);
        }
    }
    

答案 1 :(得分:-2)

所以,事实证明这可能是我的无知或可能是一个错误。

在我的代码中而不是以下行

profilingpane.setVisible(true);

我使用以下内容,问题就消失了。这更像是解决问题的黑客。

profilingpane.setOpacity(0.0);

然后当我希望窗格显示时,

profilingpane.setOpacity(1.0);