I have a JavaFX with TabPane which holds Java Objects with data into different tabs. I found that when the content of the tab takes time to load because there are SQL queries for execution the application just hangs. Is there any way to display some "Loading" message during the content utilization? for example:
Tab.setContent(<some_heavy_Java_Object>);
Is there any workaround to solve this in JavaFX or Java?
P.S I tested this code sample but I get error when I try to run the code:
TabContentInfrastructure content;
class GetDailySalesService extends Service<ObservableList<Object>>
{
#Override
protected Task createTask()
{
return new GetDailySalesTask();
}
}
class GetDailySalesTask extends Task<ObservableList<Object>>
{
#Override
protected ObservableList<Object> call() throws Exception
{
content = new TabContentInfrastructure();
return (ObservableList<Object>) content.initTestTabContentData();
}
}
..........
VBox vbox = new VBox();
content = new TabContentInfrastructure();
vbox.getChildren().add(content.initTestTabContentData());
GetDailySalesService service = new GetDailySalesService();
Region veil = new Region();
veil.setStyle("-fx-background-color: rgba(0, 0, 0, 0.4)");
veil.setPrefSize(240, 160);
ProgressIndicator p = new ProgressIndicator();
p.setMaxSize(140, 140);
p.progressProperty().bind(service.progressProperty());
veil.visibleProperty().bind(service.runningProperty());
p.visibleProperty().bind(service.runningProperty());
//tableView.itemsProperty().bind(service.valueProperty());
StackPane stack = new StackPane();
stack.getChildren().addAll(vbox, veil, p);
service.start();
tabdata.setContent(stack);
Can you help me to solve this issue.
Another attempt to solve the issue:
Task<VBox> task = new Task<VBox>()
{
#Override
protected VBox call() throws Exception
{
TabContentInfrastructure content = new TabContentInfrastructure();
return content.initTestTabContentData();
}
};
Thread th = new Thread(task);
th.setDaemon(true);
th.start();
Region veil = new Region();
veil.setStyle("-fx-background-color: rgba(0, 0, 0, 0.4)");
veil.setPrefSize(240, 160);
ProgressIndicator p = new ProgressIndicator();
p.setMaxSize(140, 140);
//p.progressProperty().bind(service.progressProperty());
veil.visibleProperty().bind(task.runningProperty());
p.visibleProperty().bind(task.runningProperty());
//vb.visibleProperty().bind(service.runningProperty().not());
//tableView.itemsProperty().bind(service.valueProperty());
StackPane stack = new StackPane();
task.setOnSucceeded(new EventHandler<WorkerStateEvent>()
{
#Override
public void handle(WorkerStateEvent t){
System.out.print("Entered setOnSucceeded**********" + t.getSource().getValue());
stack.getChildren().clear();
stack.getChildren().addAll(task.getValue());
}
});
stack.getChildren().addAll(veil, p);
tabdata.setContent(stack);
This time the result is null.
And another unsuccessful attempt.
StackPane stack = new StackPane();
Region veil = new Region();
ProgressIndicator p = new ProgressIndicator();
Task<VBox> task = new Task<VBox>()
{ // create new task
#Override
public VBox call() throws InterruptedException
{
Platform.runLater(new Runnable()
{ // USE THIS INSTEAD
#Override
public void run()
{
try
{
// ui updates here(inside application thread)
// this is needed if you want to update your ui
// you cannot update any ui from outside the application thread
TabContentInfrastructure content = new TabContentInfrastructure();
//stack.getChildren().clear();
stack.getChildren().addAll(content.initTestTabContentData());
}
catch (InterruptedException ex)
{
//Logger.getLogger(InfrastructureDataTabs.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
return null;
}
};
new Thread(task).start();
veil.setStyle("-fx-background-color: rgba(0, 0, 0, 0.4)");
veil.setPrefSize(240, 160);
p.setMaxSize(140, 140);
p.progressProperty().bind(task.progressProperty());
veil.visibleProperty().bind(task.runningProperty());
p.visibleProperty().bind(task.runningProperty());
//vb.visibleProperty().bind(service.runningProperty().not());
//tableView.itemsProperty().bind(service.valueProperty());
stack.getChildren().addAll(veil, p);
tabdata.setContent(stack);
you must load the data in a different Task Thread, I see that you are trying to do the same. The problem with your code is that you are not updating your progress bar. You must use updateProgress as shown here
http://docs.oracle.com/javafx/2/threads/jfxpub-threads.htm#BABGJIDB
Here is a very nice example from Jewelsea where he has very nicely displayed the use of Task and how to use it to update the progress on the UI
Update progress bar and multiple labels from thread
Here you can find out how to use the Task as well as update the UI from the task
Some more Nice examples are
https://community.oracle.com/message/9927179#9927179
https://community.oracle.com/message/10631701#10631701
You should just execute the expensive computations in another thread and then update e.g. a progresss bar in the javafx application thread.
Also your application wont hang during the process anymore.
Like this:
Task task = new Task<Void>() { // create new task
#Override
public Void call() {
// do expensive computations here
Platform.runLater(new Runnable() { // return to application thread
#Override
public void run() {
// ui updates here(inside application thread)
}
});
return null;
}
};
new Thread(task).start(); // execute task in new thread
Hope it helps, Laurenz.
EDIT -------------
Task<Void> task = new Task<Void>() { // create new task
#Override
public Void call() {
try {
Thread.sleep(50); // this simulates expensive computations(in your case loading) - your app would hang for this duration
} catch (InterruptedException e) {
e.printStackTrace();
}
// REMOVE THE SLEEP AND PUT YOUR TASK HERE
// Main.this.root.setPrefHeight(50); // would NOT work(because outside application thread)
Platform.runLater(new Runnable() { // USE THIS INSTEAD
#Override
public void run() {
// ui updates here(inside application thread)
// this is needed if you want to update your ui
// you cannot update any ui from outside the application thread
}
});
return null;
}
};
new Thread(task).start(); // execute task in new thread
Related
This question already has an answer here:
Update JavaFX Live nodes outside Application Thread
(1 answer)
Closed 2 years ago.
I'm in the process of teaching myself JavaFX and I recently stumbled upon "Platform.runLater", which should help you update the UI from inside a time consuming thread.
If I try to set any node inside the task thread, it won't let me because it's not running on the Application thread. HOWEVER, setting the progress of a progressbar works fine. Why is this? I've compared examples of use of the "runLater", and it does what it says until it comes to a "ProgressBar" (i.e, can't set labels or other nodes).
This works, as expected:
#Override
public void start(Stage primaryStage) throws Exception {
ProgressBar progressBar = new ProgressBar(0);
HBox hBox = new HBox(progressBar);
Scene scene = new Scene(hBox);
primaryStage.setScene(scene);
primaryStage.show();
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
Platform.runLater(()->{
progressBar.setProgress(progressBar.getProgress()+0.1);
});
}
}
});
thread.setDaemon(true);
thread.start();
}
public static void main(String[] args) {
launch(args);
}
And this too, for unknown reason:
#Override
public void start(Stage primaryStage) throws Exception {
ProgressBar progressBar = new ProgressBar(0);
HBox hBox = new HBox(progressBar);
Scene scene = new Scene(hBox);
primaryStage.setScene(scene);
primaryStage.show();
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
progressBar.setProgress(progressBar.getProgress()+0.1);
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
thread.setDaemon(true);
thread.start();
}
public static void main(String[] args) {
launch(args);
}
Why is this? I've compared both documentation and numerous examples...
In javafx can only manipulate UI elements such as the progressBar from within the dedicated platform thread. In your first example you do exactly this:
Platform.runLater(() -> {
progressBar.setProgress(progressBar.getProgress()+0.1);
});
which will execute `progressBar.setProgress(progressBar.getProgress()+0.1);´ within the dedicated UI thread as soon it has free capacity (previous commands have been exectued on it).
In your second example you try manipulate it from your newly spawned thread which will lead to the Not on FX application thread ... exception.
Whenever you want to make changes on the UI elements within some new thread make sure you perform those within
Platform.runLater(() -> {
...
});
I try to implement a GUI button so that when it is pressed, it performs two actions - the execution of the main code (2-3 seconds) and the display of the gif-preloader.
I used Task for this purpose, initializing and running it in the setOnAction method. Task itself, in turn, uses the showGif () method to launch the image.
Separately, they work correctly - showGif () opens GIF, Task displays a counter working in parallel with the main code in the console.
But when I put showGif () in the Task, the method does not work. It reaches the line "pane.setCenter (hb);" and stops. I thought that he didn’t have enough time to launch GIF and added a 5-second delay to the main code - that didn’t help either.
What I do wrong?
Besides Task, I also tried Platform.runLater(new Runnable) - the result is the same.
The button action:
btn_find.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
isStarted = true;
task = new Task<Object>() {
#Override protected Object call() throws Exception {
showGif();
return null;
}
};
new Thread(task).start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}...
The Gif method:
protected static void showGif() {
System.out.println("opening GIF...");
File file = new File("/Users/user/Desktop/cat-preloader.gif");
String localUrl = null;
try {
localUrl = file.toURI().toURL().toString();
} catch (Exception e) {
e.printStackTrace();
}
Image image = new Image(localUrl, 200,200, false, true);
ImageView imageView = new ImageView(image);
hb = new HBox();
hb.setStyle("-fx-background-color: lightgrey");
hb.setOpacity(0.7);
hb.getChildren().add(imageView);
HBox.setMargin(imageView, new Insets(300, 100, 60, 200));
BorderPane.setMargin(hb, new Insets(0, 0,600, 0));
System.out.println("setting the pane");
// here thread execution stops
pane.setCenter(hb);
System.out.println("GIF started");
}
I'm currently trying to create a Splash Screen for my program since it takes some time to start up.
The problem is that it takes a while to create the GUI (creating dialogues, updating tables etc.). And I can't move the GUI creation to a background thread (like the "Task" class), since I'll get an "Not on FXApplication Thread" exception.
I tried using:
Platform.runLater(new Runnable() {
public void run() {
//create GUI
}
}
And the "call" method of a Task:
public class InitWorker extends Task<Void> {
private Model model;
private ViewJFX view;
public InitWorker(Model model) {
this.model = model;
}
#Override
protected Void call() throws Exception {
View view = new View();
Collection collection = new Collection();
//do stuff
}
}
When I wrote the program in Swing I could just display and update the Splash Screen on the EventDispatchThread, without any real concurreny. The code looked like this:
public void build() {
MainOld.updateProgressBar(MainOld.PROGRESSBAR_VALUE++, "Creating Menus");
menuCreator = new MenuCreatorOld (model, this);
menuCreator.createMenu();
MainOld.updateProgressBar(MainOld.PROGRESSBAR_VALUE, "Creating Toolbar");
toolBar = menuCreator.createToolBar();
createWesternPanelToolBar();
shoppingPanel = new ShoppingListOld(model, this, collectionController, shoppingController, controller);
centerTabbedPane = new JTabbedPane();
MainOld.updateProgressBar(MainOld.PROGRESSBAR_VALUE++, "Creating Collection");
collectionPanel = new CollectionOld(model, collectionController, this, controller);
MainOld.updateProgressBar(MainOld.PROGRESSBAR_VALUE++, "Creating Wish List");
wishPanel = new WishListOld(model, this, collectionController, wishController, controller);
MainOld.updateProgressBar(MainOld.PROGRESSBAR_VALUE++, "Creating Folders Table");
//and so on
}
public static void updateProgressBar(int progressValue, String text) {
System.out.println("Loading Bar Value:"+progressValue);
progressBar.setValue(progressValue);
loadingLabel.setText(text);
progressBar.setString(text);
}
Is there any way to create the GUI in the background while displaying a Splash Screen with a loading bar?
Edit:
I had a look at my code and was able to decrease the startup time by 5 seconds. Most of the dialogs pull data from the database when they are created. So I moved the creation of the dialogs into their getter methods. That resulted in an improvement of 3 seconds. But I would still like to know if there is in a way to create the GUI on a background thread.
Edit:
As suggested, I also tried using "RunLater" in a "Task".
This way I can create the GUI and display the SplashScreen, but I can't update the progress bar and progress label, since the GUI creation blocks the JavaFX application thread. The progress bar and label are only updated, after the GUI has been fully created.
Here's an example you guys can run (I removed the splash screen and only kept the progress bar and progress label):
public class InitWorker extends Task<Void> {
private static ProgressBar progressBar;
private static Label progressLabel;
private static double PROGRESS_MAX = 5;
private double loadingValue;
public InitWorker() {
loadingValue = 0;
}
#Override
protected void succeeded() {
System.out.println("Succeeded");
}
#Override
protected void failed() {
System.out.println("Failed");
}
#Override
protected Void call() throws Exception {
System.out.println("RUNNING");
Platform.runLater(new Runnable() {
public void run() {
displaySplashScreen();
for(int i=0; i<10; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
updateProgressBar(loadingValue++, "Label "+i);
Stage stage = new Stage();
Label label = new Label("Label " + i);
VBox panel = new VBox();
panel.getChildren().add(label);
Scene scene = new Scene(panel);
stage.setScene(scene);
stage.centerOnScreen();
stage.show();
}
// updateProgressBar(1, "Initializing...");
}});
return null;
}
public void updateProgressBar(double loadingValue, String text) {
progressBar.setProgress(loadingValue / PROGRESS_MAX);
progressLabel.setText(text);
}
public static void displaySplashScreen() {
Stage progressBarStage = new Stage();
progressBar = new ProgressBar();
Scene progressBarScene = new Scene(progressBar);
progressBarStage.setScene(progressBarScene);
Stage progressLabelStage = new Stage();
progressLabel = new Label("Loading...");
progressLabel.setPadding(new Insets(5));
progressLabel.setStyle("-fx-background-color: red");
Scene progressLabelScene = new Scene(progressLabel);
progressLabelStage.setScene(progressLabelScene);
double progressBarWidth = 500;
double progressBarHeight = 75;
//muss angezeigt werden, um sie abhängig von Größe zu positionieren
progressBarStage.show();
progressLabelStage.show();
//
progressBarStage.setWidth(progressBarWidth);
progressBarStage.setHeight(progressBarHeight);
progressBarStage.centerOnScreen();
progressBarStage.centerOnScreen();
progressLabelStage.setY(progressLabelStage.getY() + 25);
}
}
See Task documentation titled "A Task Which Modifies The Scene Graph", which provides an example:
final Group group = new Group();
Task<Void> task = new Task<Void>() {
#Override protected Void call() throws Exception {
for (int i=0; i<100; i++) {
if (isCancelled()) break;
final Rectangle r = new Rectangle(10, 10);
r.setX(10 * i);
Platform.runLater(new Runnable() {
#Override public void run() {
group.getChildren().add(r);
}
});
}
return null;
}
};
The above example add the rectangles to the scene graph via a 100 runLater calls. A more efficient way to do this would be to add the rectangles to a group not attached to the active scene graph, then only add the group to the active scene graph in the runLater call. For example:
final Group groupInSceneGraph = new Group();
Task<Void> task = new Task<Void>() {
#Override protected Void call() throws Exception {
final Group localGroup = new Group();
for (int i=0; i<100; i++) {
if (isCancelled()) break;
final Rectangle r = new Rectangle(10, 10);
r.setX(10 * i);
localGroup.getChildren().add(r);
}
Platform.runLater(new Runnable() {
#Override public void run() {
groupInSceneGraph.add(localGroup);
}
});
return null;
}
};
You can create and modify most scene graph objects off of the JavaFX application thread (including loading FXML), as long as the objects aren't attached to the active scene graph. By active scene graph I mean a scene graph which is currently attached as a scene to a displayed stage. (A complicated control such as a WebView may be an exception to this rule and may require creation on the JavaFX application thread).
You must only attach the scene graph objects created off of the JavaFX application thread to the active scene graph on the JavaFX application thread (for example using Platform.runLater()). And, you must work with them on the JavaFX application thread as long they continue to be attached to the active scene graph.
I'm trying to create a Javafx application that takes screenshots from a URL. Where I'm running into issues is with threading. In my main method, I'm trying to run the two screenshots but it's getting stuck after loading the first page. I've tried to wrap the monitorPageStatus() (since it calls the actual saveToPng() function) method in a task that is submitted to the executor (code below). How can I properly submit the task to the executor so both screenshots are taken?
public class InsightScreenshot {
{
// Clever way to init JavaFX once
JFXPanel fxPanel = new JFXPanel();
}
private Browser browser;
public Stage stage;
private Timer timer = new java.util.Timer();
private ExecutorService exec = Executors.newSingleThreadExecutor(r -> {
Thread t = new Thread(r);
t.setDaemon(true); // allows app to exit if tasks are running
return t ;
});
#SuppressWarnings("restriction")
/**
*
* #param url
* #param imageName add png extension
*/
public void showWindow(String url, String imagePath) {
// JavaFX stuff needs to be done on JavaFX thread
Platform.runLater(new Runnable() {
private Stage window;
#SuppressWarnings("restriction")
#Override
public void run() {
Stage window = new Stage();
window.setTitle(url);
browser = new Browser(url);
monitorPageStatus(imagePath, window);
VBox layout = new VBox();
layout.getChildren().addAll(browser);
Scene scene = new Scene(layout);
window.setScene(scene);
//window.setOnCloseRequest(we -> System.exit(0));
window.show();
}
});
}
private void monitorPageStatus(String imageName, Stage window) {
timer.schedule(new TimerTask() {
#SuppressWarnings("restriction")
public void run() {
Platform.runLater(() -> {
if (browser.isPageLoaded()) {
System.out.println("Page now loaded, taking screenshot...");
saveAsPng(imageName);
window.close();
cancel();
} else
System.out.println("Loading page...");
});
}
}, 1000, 1000);
}
private void saveAsPng(String imageName) {
WritableImage image = browser.snapshot(new SnapshotParameters(), null);
//TODO change file path?
File file = new File(imageName);
try {
ImageIO.write(SwingFXUtils.fromFXImage(image, null), "png", file);
System.out.println("Screenshot saved as " + imageName);
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
InsightScreenshot pic = new InsightScreenshot();
pic.showWindow("ttps://www.google.com", "/images/google.png");
InsightScreenshot pic2 = new InsightScreenshot();
pic2.showWindow("https://www.facebook.com", "/images/fb.png");
}
}
When I try to load some files from JSON I want to create a progress bar that veils the screen for some seconds. The loading from JSON works, the progress bar works the only problem I have is with the veil.
So, I have my application that is running and when I try to load the JSON file I try to set the scene with the progress bar for the stage. All the things are going fine until now (even the new scene is showing the progress bar). The problem comes when I the progress bar finishes the progress (100%) it shows me blank ...and doesn't show me the old application scene. How can I resolve this ?
This is my code in the progress loader:
public Scene createContent() {
final StackPane g = new StackPane();
Region veil = new Region();
veil.setStyle("-fx-background-color: rgba(0, 0, 0, 0.4)");
veil.setOpacity(0.8);
final ProgressIndicator p1 = new ProgressIndicator();
p1.setPrefSize(100, 100);
p1.setMaxSize(150, 150);
p1.progressProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue ov, Number oldVal, Number newVal) {
if (p1.getProgress() < 0.25) {
p1.setStyle("-fx-progress-color: red;");
} else if (p1.getProgress() < 0.5) {
p1.setStyle("-fx-progress-color: orange;");
} else {
p1.setStyle("-fx-progress-color: green;");
}
}
});
// animate the styled ProgressIndicator
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.setAutoReverse(true);
final KeyValue kv = new KeyValue(p1.progressProperty(), 1);
final KeyFrame kf1 = new KeyFrame(Duration.millis(3000), kv);
timeline.getKeyFrames().add(kf1);
g.getChildren().addAll(veil,p1);
g.setAlignment(Pos.CENTER);
Task task = new Task() {
#Override
protected Object call() throws Exception {
for (int i = 0; i < 500; i++) {
updateProgress(i, 500);
Thread.sleep(5);
}
stage.hide();
return null;
}
};
p1.progressProperty().bind(task.progressProperty());
veil.visibleProperty().bind(task.runningProperty());
p1.visibleProperty().bind(task.runningProperty());
new Thread(task).start();
Scene scene = new Scene(g, 200, 200);
return scene;
}
public void play() {
timeline.play();
}
public void stop() {
timeline.stop();
}
public void start(Stage stage) {
this.stage=stage;
this.stage.setScene(createContent());
this.stage.show();
}
And this is in the JSON loader class:
ProgressLoader pl=new ProgressLoader();
pl.start(VisualAppFactory.getStage());
I do not know what you are trying to achieve excactly and what you mean by "veil", but your problem most certainly comes from calling stage.hide() while not being on the FX-Thread. Check out the documentation of the method or surround the call with a try block
try {
stage.hide();
} catch (Exception e) {
e.printStackTrace();
}
to see the effect.
Use Platform.runLater to execute the call on the FX-Thread:
Platform.runLater(()-> stage.hide());
With task.setOnSucceeded(...) you get notified when the task finished so you can set your old view into the stage or something.