Simon S.
Simon S.

Reputation: 65

JavaFX: Update ProgressBar (@FXML) from Thread

I want to update a JavaFX ProgressBar defined in an FXML file by another class, initialized in a controller thread. Currently it just does not update.

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

I already tried different versions in consideration of posts like:

I don't know if it's the right approach to make the ProgressBar static. I just did not want to pass the Object through the workflow.

Update (Xavier Lambros answer): Now i tried it with singleton but it's still not working:

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

Upvotes: 3

Views: 5372

Answers (2)

James_D
James_D

Reputation: 209398

As noted in javafx 8 compatibility issues - FXML static fields, you cannot make a @FXML-annotated field static (and it makes no sense to do so: these fields are inherently properties of the specific controller instance).

To allow the doIt() method access to the progress bar, you could just pass it directly as a parameter:

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

and then

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

In some circumstances, it might not make sense for the Main class to have a dependency on the JavaFX API. In that case you could just pass a function that updates the progress bar:

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

and

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

Note that you haven't shown what's happening in your while loop: if you are submitting too many runnables to the FX Application Thread, you might "flood" it and prevent it from updating in a reasonable time. You might consider using a Task, which has specific API for updating a progress field to which the progress bar's progress property can be bound. If it's still not working, you should edit your question to include a MCVE.

Upvotes: 4

Xavier Lambros
Xavier Lambros

Reputation: 876

I don't think you can have ProgressBar static.

My way is to have an accessor on the ProgressBar inside your controller and init the controller like this :

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

After, you can access your ProgressBar with :

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

If you need, to access it in different classes, many other possibilities, one is to make a Singleton :

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

To call it :

Singleton.getInstance().getProgressBar();

Upvotes: 0

Related Questions