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:
ProgressBar doesn't work with a fxml file and a controller
How to configure Progress Bar and Progress Indicator of javaFx?
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));
}
}
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.
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();
Related
I am working on a Java project that makes use of JavaFX's ProgressBar. From what I found on StackOverflow, usually a thread needs to be added, or the program needs to implement Runnable, in order to update the ProgressBar's value dynamically, say when program is running in for loop.
However, looking at java doc and answers in stackoverflow, I am still not sure how, or what is the best way to solve my issue.
Since I have completely no knowledge on JavaFX, can anyone please provide me some hint?
Below is the Application.java as given from the project
public class MyApplication extends Application {
private static final String UI_FILE = "/ui.fxml";
#Override
public void start(Stage stage) throws Exception {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource(UI_FILE));
VBox root = (VBox) loader.load();
Scene scene = new Scene(root);
stage.setScene(scene);
stage.setTitle("Team T-03: Course Scraper");
stage.show();
}
public static void main(String args[]) {
Application.launch(args);
}
}
The Controller.java that I should implement the related loops and methods
public class Controller {
#FXML
private ProgressBar progressbar;
#FXML
void doSomething() {
for (String object : objectList) {
List<someObject> v = someOtherFunc(object);
totalObjectCount += v.size();
progress = (double) ++objectCount / v.size();
progressbar.setProgress(progress);
}
}
}
Use a Task,
and run it in a new Thread. A Task has its own progress property, and an updateProgress method that ensures the change to the progress is executed on the FX Application Thread. You can then bind your progress bar's progress to the task's:
#FXML
void doSomething() {
Task<Void> task = new Task<>() {
#Override
public Void call() {
for (String object : objectList) {
List<someObject> v = someOtherFunc(object);
totalObjectCount += v.size();
updateProgress(++objectCount, objectList.size());
}
return null ;
}
}
progressBar.progressProperty().bind(task.progressProperty());
new Thread(task).start();
}
I am making a simple JavaFX college course project and I need a good way of dealing with threads, mainly running them while a certain flag is activated.
This is a simple sketch I came up with:
public class ListenerService extends Thread {
private static ArrayList<ListenerService> listeners = new ArrayList<>();
private ToggleButton button;
private File folder;
private SimpleBooleanProperty active = new SimpleBooleanProperty();
ListenerService(ToggleButton button, String pathname) {
this.button = button;
this.folder = new File(pathname);
button.setOnAction(event -> active.set(button.isSelected()));
active.addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue)
-> {if (newValue.booleanValue()) start();});
listeners.add(this);
}
#Override
public void run() {
while(active.get())
System.out.print(".");
}
The process is as following:
The user dynamically creates a ToggleButton on the form. A
ListenerService object is created, to which a button and a directory
are assigned.
A listener is assigned to the button - if it's clicked - activate
the flag. Otherwise, deactivate. The flag here is a
SimpleBooleanProperty instance.
If the flag is switched on, run the thread. The thread will run
while the flag is active. If the user toggles the button again and
deactivates it, the condition in the while loop would fail and
thread should stop running.
As soon as I run the program, it freezes. I tried making the flag volatile, but nothing changed. Since the flag is controlled externally (from GUI), there isn't a way to make this method synchronized.
What am I doing wrong?
You basically create a new Thread that runs as long as the button is selected and exits when the button is not selected.
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.scene.Scene;
import javafx.scene.control.ToggleButton;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class ThreadApp extends Application {
public class Worker implements Runnable{
private boolean active;
public void setActive(boolean active) {
this.active = active;
}
#Override
public void run() {
while(active)
{
System.out.println("Active! " + System.currentTimeMillis());
}
}
}
public class WorkerToggle extends ToggleButton {
Worker worker;
public WorkerToggle(String text) {
super(text);
this.worker = new Worker();
setOnAction((event) -> {
if(isSelected())
{
worker.setActive(true);
Thread thread = new Thread(worker);
thread.setDaemon(true);
thread.start();
}else
{
worker.setActive(false);
}
});
}
}
#Override
public void start(Stage primaryStage) {
BorderPane rootPane = new BorderPane();
Scene scene = new Scene(rootPane);
rootPane.setCenter(new WorkerToggle("toggle me"));
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
This should work fine, but creating Threads can be expensive, so you might want to look into ThreadPoolExecutor if you notice some performance problems there.
In JavaFX you have the ability to use a scheduled service to run things off the main FX thread. Here is simple sample that might help.
public class JavaFXApplication3 extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
PollingService service = new PollingService();
service.setPeriod(Duration.millis(1000)); // sysout every second
ToggleButton tb = new ToggleButton("Start Process");
tb.setOnAction(event -> {
System.out.println(tb.isSelected());
if(tb.isSelected()){
service.reset();
service.start();
}else {
service.cancel();
}
});
VBox vbox = new VBox(tb);
Scene scene = new Scene(vbox);
primaryStage.setScene(scene);
primaryStage.show();;
}
private class PollingService extends ScheduledService<Void> {
#Override
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
protected Void call() {
System.out.print(".#.");
return null;
}
};
}
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
I have a method that takes a while to complete, when the jar application is started. To have some feedback, i created the form frmWaiting, that displays a simple indeterminate progress bar. I also have a controller for the form,
PrincipalController.
Entry point for the application
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
Stage stagePrincipal = new Stage();
Parent parentPrincipal = FXMLLoader.load(getClass().getClassLoader().getResource("frmPrincipal.fxml"));
Scene scenePrincipal = new Scene(parentPrincipal, 300, 275);
stagePrincipal.setScene(scenePrincipal);
stagePrincipal.setHeight(400);
stagePrincipal.setWidth(500);
stagePrincipal.setResizable(false);
stagePrincipal.setTitle("Instalador");
stagePrincipal.show();
} catch (Exception e) {
e.printStackTrace();
}
}
PrincipalController - frmPrincipal.fxml:
#Override
public void initialize(URL location, ResourceBundle resources) {
try {
Stage stageWaiting = new Stage();
Parent parentWaiting;
parentWaiting = FXMLLoader.load(getClass().getClassLoader().getResource("frmWaiting.fxml"));
Scene sceneWaiting = new Scene(parentWaiting, 300, 275);
stageWaiting.setScene(sceneWaiting);
stageWaiting.setHeight(300);
stageWaiting.setWidth(400);
stageWaiting.setResizable(false);
stageWaiting.setTitle("Instalador");
stageWaiting.show();
} catch (IOException e) {
}
}
WaitingController - frmWaiting.xml:
public class WaitingController implements Initializable {
#FXML private ImageView img;
#FXML private ProgressBar progressBar;
#FXML private ProgressIndicator pgIndicator;
private Task copyTask;
#Override
public void initialize(URL location, ResourceBundle resources) {
img.setImage(new Image(getClass().getClassLoader().getResourceAsStream("image.png")));
progressBar.setProgress(ProgressBar.INDETERMINATE_PROGRESS);
ArquivoController.getInstance().copiaArquivosPadrao(); //This is the method that takes a while.
}
public ProgressBar getProgressBar() {
return progressBar;
}
I want to initialize my main form, frmPrincipal, when my method that takes a while finishes. I also want to get the progress bar working. I have tried to do it on another Thread, but i could not get the response from it when the method finishes.
All the .fxml files are correct, ommited them to make things easier if possible.
The way it is, the application waits for the method to finish, then opens the other form. But, the progressBar does not update.
Inside ArquivoController.getInstance().copiaArquivosPadrao(); you have to update the progressBar's progress.
You could do it the nice way, using a Task to run copiaArquivosPadrao(), update the tasks progress accordingly and binding to the tasks progress property.
Or you could do it the ugly way, passing progressBar to copiaArquivosPadrao(), something like this:
public void initialize(URL location, ResourceBundle resources) {
img.setImage(new Image(getClass().getClassLoader().getResourceAsStream("image.png")));
progressBar.setProgress(ProgressBar.INDETERMINATE_PROGRESS);
ArquivoController.getInstance().copiaArquivosPadrao(progressBar); //This is the method that takes a while.
}
and
public void copiaArquivosPadrao(ProgressBar progressBar) {
// call progressBar.setProgress() in here to update the progress bar
// eg.
progressBar.setProgress(0.0F);
doSomething();
progressBar.setProgress(0.20F);
doSomething();
progressBar.setProgress(0.40F);
doSomething();
progressBar.setProgress(0.60F);
doSomething();
progressBar.setProgress(0.80F);
doSomething();
progressBar.setProgress(1.00F);
}
For sure, you can do this more fine-grained in a loop or similar.
I'm trying to make a generic class that I can use for future projects. It just makes a simple javafx browser. The issue I'm having is that I want to be able to change some of the properties dynamically (on instantiation). I added some simple setters hoping it would to the job, but they do not work. Is there a way to change the variables after start() has been executed?
Class code:
package rob.rushton;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
public class RushBrowser extends Application {
public RushBrowser() {}
private String url = "www.google.com";
private final String fullUrl = "http://" + url;
String title = "Simple Browser";
private int height = 750;
private int width = 750;
public void openBrowser() {
launch();
}
public void setURL(String u) {
url = u;
}
public void setHeightWidth(int h, int w) {
height = h;
width = w;
}
public void setTitle(String t) {
title = t;
}
#Override
public void start(Stage stage) {
stage.setTitle(this.title);
BorderPane pane = new BorderPane();
Scene scene = new Scene(pane, width, height);
WebView browser = new WebView();
WebEngine engine = browser.getEngine();
engine.load(fullUrl);
pane.setCenter(browser);
stage.setScene(scene);
stage.show();
}
}
And I was trying to run it like this:
package rushtest;
import rob.rushton.RushBrowser;
public class RushTest {
public static void main(String[] args) {
RushBrowser rush = new RushBrowser();
rush.setTitle("Test Title");
rush.setURL("www.github.com");
rush.setHeightWidth(1000, 1000);
rush.openBrowser();
}
}
EDIT: (8/9/15) None of the listed suggestions below have worked :( The problem is that I do not know how to access the application thread that is started by launch()
You should either make those properties true JavaFX properties, or update their setters delegate to the actual UI objects. Some rough code - only the relevant parts shown:
Case of true JavaFX properties:
public class RushBrowser extends Application {
...
private StringProperty titleProperty = new SimpleStringProperty("Simple Browser");
...
public void setTitle(String t) {
titleProperty.set(t);
}
#Override
public void start(Stage stage) {
stage.titleProperty().bind(this.titleProperty);
...
}
}
Case of delegation - you also have to keep a reference of the Stage:
public class RushBrowser extends Application {
...
private Stage primaryStage;
// no need to keep the title member variable
...
public void setTitle(String t) {
primaryStage.setTitle(t);
}
#Override
public void start(Stage stage) {
this.primaryStage = stage;
...
}
}
EDIT
As per the comment from James_D, there is another problem: the main method has no reference to the instance created by Application.launch(). So:
If you want to customize the parameters of your application before it starts, you can override Application.init():
public class SpecialRushBrowser extends RushBrowser {
public void init() {
this.setTitle("Test Title");
...
}
}
Or from the test code:
public class RushTest {
static class TestRushBrowser extends RushBrowser {
public void init() {
super.init(); // just in case
this.setTitle("Test Title");
...
}
}
public static void main(String[] args) {
TestRushBrowser.launch();
}
}
If you do not need to modify these parameters later, you can leave your code as is (i.e. no JavaFX properties are required). Otherwise, apply the changes mentioned above.
If you want to change the parameters after the application has started, you need to provide a reference to the actual instance of RushBrowser created by Application.launch() to the code that will execute the changes. A simple but dirty way is with a global variable:
public class RushBrowser extends Application {
public static RushBrowser INSTANCE;
public void init() {
INSTANCE = this;
}
...
}
And then from any code that runs after launch():
RushBrowser.INSTANCE.setTitle(...);
...
As global state is generally dangerous, you might want to try with a dependency injection framework, if the application gets more complex. Even with DI though it can get tricky because the main class is still created from JavaFX, outside the DI framework - but that's another story.
Again you need to apply the changes above the EDIT.
The reason the code you posted doesn't work is that the (static) launch(...) method creates a new instance of the Application subclass for you, and then calls its start(...) method. So the instance for which you call all the setXXX(...) methods is not the instance whose start(...) method is invoked.
You're defining the reusable part in the wrong place: an Application subclass is inherently not reusable. You should regard the start() method in your Application subclass as the equivalent of the main() method in a regular JavaFX application.
So:
public class RushBrowser {
private final BorderPane view ;
private String url = "www.google.com";
private final String fullUrl = "http://" + url;
private String title = "Simple Browser";
private int height = 750;
private int width = 750;
private WebEngine engine ;
public RushBrowser() {
WebView browser = new WebView();
view = new BorderPane(browser);
engine = browser.getEngine();
}
public Node getView() {
return view ;
}
public void show(Stage stage) {
Scene scene = new Scene(view, width, height);
stage.setScene(scene);
stage.show();
}
public void show() {
show(new Stage());
}
public void setURL(String u) {
url = u;
}
public void setHeightWidth(int h, int w) {
height = h;
width = w;
view.setPrefSize(w, h);
}
public void setTitle(String t) {
title = t;
Scene scene = view.getScene();
if (scene != null) {
Window window = scene.getWindow();
if (window instanceof Stage) {
((Stage)window).setTitle(title);
}
}
}
}
and then you test it with an Application subclass:
public class RushBrowserTest extends Application {
#Override
public void start(Stage primaryStage) {
RushBrowser rush = new RushBrowser();
rush.setTitle("Test Title");
rush.setURL("www.github.com");
rush.setHeightWidth(1000, 1000);
rush.show(primaryStage);
}
public static void main(String[] args) { launch(args); }
}
and of course you can use it elsewhere. As an arbitrary example:
TabPane tabPane = ... ;
RushBrowser rush = new RushBrowser();
rush.setURL("www.github.com");
Tab tab = new Tab();
tab.setContent(rush.getView());
I have 2 classes .java
The main :
public class Controller extends javax.swing.JFrame
{
public static void updateProgressBar(int i) {
jProgressBar1.setValue(i);
jProgressBar1.repaint();
}
public static void main(String args[]) {
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
Controller app = new Controller();
app.setVisible(true);
app.setResizable(false);
}
});
}
private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) {
ChildModel model = new ChildModel();
Thread t1 = new Thread(model);
t1.start();
}
private javax.swing.JProgressBar jProgressBar1; //Initialized with Netbeans builder
}
My ChildModel (ChildModel.java) computes some code (that takes around 10-20 sec) and I want to show the progress on the father class (Controller.java).
Here is my ChildModel :
public class ChildModel implements Runnable
{
public ChildModel(){ /* Something */ }
public void complexMath()
{
//Lots of logic here
Controller.updateProgression(purcent);
}
#Override
public void run() {
complexMath();
}
}
The problem is obviously my static void updateProgressBar that cannot modify a non-static variable. How can I accomplish this ?
The jProgressBar1 variable is an instance variable, so you can't access it from a static method. And the method shouldn't be static: you want to update the progress in the controller, and not in all the Controller instances.
Pass a reference to the controller to the ChildModel, and use this reference from the ChildModel in order to update the progress bar. Also remember that all Swing interactions must be done in the EDT, and not in a background thread. SO the code should look like this:
public class Controller extends javax.swing.JFrame
{
public void updateProgressBar(int i) {
jProgressBar1.setValue(i);
// no need for repaint. The progress bar knows it must be repainted
// when its value changes
}
public static void main(String args[]) {
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
Controller app = new Controller();
app.setVisible(true);
app.setResizable(false);
}
});
}
private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) {
ChildModel model = new ChildModel(this);
Thread t1 = new Thread(model);
t1.start();
}
private javax.swing.JProgressBar jProgressBar1; //Initialized with Netbeans builder
}
public class ChildModel implements Runnable
{
private Controller controller;
public ChildModel(Controller controller){
this.controller = controller;
}
public void complexMath()
{
//Lots of logic here
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
controller.updateProgression(percent);
}
});
}
#Override
public void run() {
complexMath();
}
}
Swing has its own concurrency mechanisms to deal with updating components. Here you could use
a Swing Timer and update the JProgressBar. Rather than have ChildModel implement Runnable, you could use a Timer as a class member variable and pass in your instance jProgressBar1, enabling you to call setValue when required.