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();
}
Related
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 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();
The application reacts on actions which occur on gamepad. When button is pressed something happens on UI. But I ran at the issue with app hangs up or "java.lang.IllegalStateException: Not on FX application thread" exception.
In order to fix it I tried the following approaches: Platform.runLater() and Task usage. But it didn't help.
Here is the problem code:
public class GamepadUI extends Application{
private static final int WIDTH = 300;
private static final int HEIGHT = 213;
private Pane root = new Pane();
private ImageView iv1 = new ImageView();
private boolean isXPressed = false;
#Override
public void start(Stage stage) throws Exception {
initGUI(root);
Scene scene = new Scene(root, WIDTH, HEIGHT);
stage.setScene(scene);
stage.setResizable(false);
stage.show();
}
public void pressBtn() {
if(!isXPressed) {
iv1.setVisible(true);
isXPressed = true;
}
}
public void releaseBtn() {
if(isXPressed) {
iv1.setVisible(false);
isXPressed = false;
}
}
private void initGUI(final Pane root) {
Image image = new Image(Props.BUTTON);
iv1.setImage(image);
iv1.setLayoutX(198);
iv1.setLayoutY(48);
iv1.setVisible(false);
root.getChildren().add(iv1);
runTask();
}
public void runTask() {
Task task = new Task<Void>() {
#Override
protected Void call() throws Exception {
initStubGamepad();
return null;
}
};
new Thread(task).start();
}
public static void main(String[] args) {
launch(args);
}
public void initStubGamepad() {
Random rnd = new Random();
try {
while (true) {
if (rnd.nextInt(30) == 3) {
pressBtn();
} else if (rnd.nextInt(30) == 7) {
releaseBtn();
}
}
} catch (Exception ex) {
System.out.println("Exception: " + ex);
}
}
}
initStubGamepad() emulates gamepad buttons activity polling. When user presses any button (rnd.nextInt(30) == 3) - an image appears on the UI. When user releases that button (rnd.nextInt(30) == 7) - an image disappears from the UI.
In case above java.lang.IllegalStateException: Not on FX application thread occurs. If you change runTask() to something like this:
Platform.runLater(new Runnable() {
#Override
public void run() {
initStubGamepad();
}
});
Then app will hang or even main UI won't appear at all, but gamepad activity continues.
What I want is just to show/hide different images when some activity is detected on gamepad (btw, there's no way to monitor gamepad activity except for gamepad polling in an infinite loop). What did I wrong
Explanation
In the first scenario, when you are using
Task task = new Task<Void>() {
#Override
protected Void call() throws Exception {
initStubGamepad();
return null;
}
}
Inside initStubGamepad(), which is running on a Task, you are trying to update the UI components inside pressBtn() and releaseBtn() methods, which is why you are facing a
java.lang.IllegalStateException: Not on FX application thread
because all the UI updates must occur on the JavaFX thread
In the second scenario, when you are using
Platform.runLater(new Runnable() {
#Override
public void run() {
initStubGamepad();
}
});
the UI doesnt appear, because you have an infinite loop inside the initStubGamepad(), which puts the JavaFX application thread run on an infinite loop
Solution
By the time you have reach here, you must have already found the solution. In case you haven't, try try to put the update the Javafx components on the UI thread. So, instead of calling initStubGamepad() inside Platform.runLater, try calling pressBtn() and releaseBtn() inside it.
Try using
while (true) {
if (rnd.nextInt(30) == 3) {
Platform.runLater(() -> pressBtn());
} else if (rnd.nextInt(30) == 7) {
Platform.runLater(() -> releaseBtn());
}
}
or you may also use
public void pressBtn() {
if(!isXPressed) {
Platform.runLater(() -> iv1.setVisible(true));
isXPressed = true;
}
}
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());
How to call the launch() more than once in java i am given an exception as "ERROR IN MAIN:java.lang.IllegalStateException: Application launch must not be called more than once"
I have create rest cleint in my java application when request comes it call javafx and opening webview after completing webview operarion am closing javafx windows using Platform.exit() method. when second request comes am getting this error how to reslove this error.
JavaFx Application Code:
public class AppWebview extends Application {
public static Stage stage;
#Override
public void start(Stage _stage) throws Exception {
stage = _stage;
StackPane root = new StackPane();
WebView view = new WebView();
WebEngine engine = view.getEngine();
engine.load(PaymentServerRestAPI.BROWSER_URL);
root.getChildren().add(view);
engine.setJavaScriptEnabled(true);
Scene scene = new Scene(root, 800, 600);
stage.setScene(scene);
engine.setOnResized(new EventHandler<WebEvent<Rectangle2D>>() {
public void handle(WebEvent<Rectangle2D> ev) {
Rectangle2D r = ev.getData();
stage.setWidth(r.getWidth());
stage.setHeight(r.getHeight());
}
});
JSObject window = (JSObject) engine.executeScript("window");
window.setMember("app", new BrowserApp());
stage.show();
}
public static void main(String[] args) {
launch(args);
}
RestClient Method:
Calling to JavaFX application
// method 1 to lanch javafx
javafx.application.Application.launch(AppWebview.class);
// method 2 to lanch javafx
String[] arguments = new String[] {"123"};
AppWebview .main(arguments);
You can't call launch() on a JavaFX application more than once, it's not allowed.
From the javadoc:
It must not be called more than once or an exception will be thrown.
Suggestion for showing a window periodically
Just call Application.launch() once.
Keep the JavaFX runtime running in the background using Platform.setImplicitExit(false), so that JavaFX does not shutdown automatically when you hide the last application window.
The next time you need another window, wrap the window show() call in Platform.runLater(), so that the call gets executed on the JavaFX application thread.
For a short summary implementation of this approach:
See the answer by sergioFC
If you are mixing Swing you can use a JFXPanel instead of an Application, but the usage pattern will be similar to that outlined above.
For an example of the JFXPanel apprach, see Irshad Babar
s answer.
Wumpus Sample
This example is bit more complicated than it needs to be because it also involves timer tasks. However it does provide a complete stand-alone example, which might help sometimes.
import javafx.animation.PauseTransition;
import javafx.application.*;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.*;
// hunt the Wumpus....
public class Wumpus extends Application {
private static final Insets SAFETY_ZONE = new Insets(10);
private Label cowerInFear = new Label();
private Stage mainStage;
#Override
public void start(final Stage stage) {
// wumpus rulez
mainStage = stage;
mainStage.setAlwaysOnTop(true);
// the wumpus doesn't leave when the last stage is hidden.
Platform.setImplicitExit(false);
// the savage Wumpus will attack
// in the background when we least expect
// (at regular intervals ;-).
Timer timer = new Timer();
timer.schedule(new WumpusAttack(), 0, 5_000);
// every time we cower in fear
// from the last savage attack
// the wumpus will hide two seconds later.
cowerInFear.setPadding(SAFETY_ZONE);
cowerInFear.textProperty().addListener((observable, oldValue, newValue) -> {
PauseTransition pause = new PauseTransition(
Duration.seconds(2)
);
pause.setOnFinished(event -> stage.hide());
pause.play();
});
// when we just can't take it anymore,
// a simple click will quiet the Wumpus,
// but you have to be quick...
cowerInFear.setOnMouseClicked(event -> {
timer.cancel();
Platform.exit();
});
stage.setScene(new Scene(cowerInFear));
}
// it's so scary...
public class WumpusAttack extends TimerTask {
private String[] attacks = {
"hugs you",
"reads you a bedtime story",
"sings you a lullaby",
"puts you to sleep"
};
// the restaurant at the end of the universe.
private Random random = new Random(42);
#Override
public void run() {
// use runlater when we mess with the scene graph,
// so we don't cross the streams, as that would be bad.
Platform.runLater(() -> {
cowerInFear.setText("The Wumpus " + nextAttack() + "!");
mainStage.sizeToScene();
mainStage.show();
});
}
private String nextAttack() {
return attacks[random.nextInt(attacks.length)];
}
}
public static void main(String[] args) {
launch(args);
}
}
Update, Jan 2020
Java 9 added a new feature called Platform.startup(), which you can use to trigger startup of the JavaFX runtime without defining a class derived from Application and calling launch() on it. Platform.startup() has similar restrictions to the launch() method (you cannot call Platform.startup() more than once), so the elements of how it can be applied is similar to the launch() discussion and Wumpus example in this answer.
For a demonstration on how Platform.startup() can be used, see Fabian's answer to How to achieve JavaFX and non-JavaFX interaction?
I use something like this, similar to other answers.
private static volatile boolean javaFxLaunched = false;
public static void myLaunch(Class<? extends Application> applicationClass) {
if (!javaFxLaunched) { // First time
Platform.setImplicitExit(false);
new Thread(()->Application.launch(applicationClass)).start();
javaFxLaunched = true;
} else { // Next times
Platform.runLater(()->{
try {
Application application = applicationClass.newInstance();
Stage primaryStage = new Stage();
application.start(primaryStage);
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
try this, I tried this and found successful
#Override
public void start() {
super.start();
try {
// Because we need to init the JavaFX toolkit - which usually Application.launch does
// I'm not sure if this way of launching has any effect on anything
new JFXPanel();
Platform.runLater(new Runnable() {
#Override
public void run() {
// Your class that extends Application
new ArtisanArmourerInterface().start(new Stage());
}
});
} catch (Exception e) {
e.printStackTrace();
}
}