I have a splash screen :
I need to have the animation of the progress bar (Indeterminate) but it doesn't work.
It's maybe due to because my thread is running in my initilize methode.
public class splashscreenController implements Initializable {
#Override
public void initialize(URL location, ResourceBundle resources) {
new SplashScreen().run();
}
class SplashScreen extends Task {
#Override
public Object call() {
Platform.runLater(new Runnable() {
#Override
public void run()
Parent root = null;
try {
Thread.sleep(3000);
root = FXMLLoader.load(getClass().getResource("../gui/NewUI.fxml"));
} catch (InterruptedException | IOException e) {
e.printStackTrace();
}
Stage stage = new Stage();
stage.initStyle(StageStyle.UNDECORATED);
assert root != null;
Scene scene = new Scene(root, 1280, 720);
stage.setScene(scene);
stage.show();
MainJavaFx.setPrimaryStage(stage);
((Stage) panParent.getScene().getWindow()).close();
}
});
return null;
}
}
}
There are 2 issues in your code:
new SplashScreen().run();
A Task does not provide functionality for running on a new thread. run is executed on the calling thread.
class SplashScreen extends Task {
#Override
public Object call() {
Platform.runLater(new Runnable() {
#Override
public void run() {
// placeholder for parts of your code
longRunningOperation();
guiUpdate();
}
});
return null;
}
}
Even if you execute this task on a seperate thread, the Runnable passed to Platfrom.runLater is executed on the JavaFX application thread and doing long-running operations from this runnable freezes the GUI.
Do all the long-running operations on the background thread instead and only do short updates using Platfrom.runLater.
new Thread(new SplashScreen()).start();
class SplashScreen extends Task {
#Override
public Object call() throws IOException, InterruptedException {
Thread.sleep(3000);
final Parent root = FXMLLoader.load(getClass().getResource("../gui/NewUI.fxml"));
Platform.runLater(new Runnable() {
#Override
public void run() {
Stage stage = new Stage();
stage.initStyle(StageStyle.UNDECORATED);
Scene scene = new Scene(root, 1280, 720);
stage.setScene(scene);
stage.show();
MainJavaFx.setPrimaryStage(stage);
((Stage) panParent.getScene().getWindow()).close();
}
});
return null;
}
}
Note that since you're not using the functionality provided by Task, you could simply implement Runnable with your class instead of inheriting from Task.
Related
I know there are million questions about updating UI in javafx from differents thread, but in my case I am working with one scene only and I would like interrupt a loop while and then I start another thread that will update UI.
I'm testing the following code :
Class main :
public static void main(String[] args)
{
Application.launch(args);
}
#Override
public void start(Stage stage) {
stage.setTitle("My project");
Group root = new Group();
Scene scene = new Scene(root, 800,600,Color.LIGHTBLUE);
Label lab = new Label("This is a test");
root.getChildren().add(lab);
stage.setScene(scene);
stage.show();
GuiUpdate gu=new GuiUpdate(stage,root);
int i=0;
while(i<5)
{
synchronized (this) {
gu.getThread().start();
try {
wait();
} catch (InterruptedException ie) {
ie.printStackTrace();
}
}
i++;
}
}
my class GuiUpdate, I have 2 button.
First button execute an action.
Second Button stop this Thread and set previous Scene property.
public GuiUpdate(Stage stage, Parent root){
this.root=new Group();
thread = new Thread(this);
oldRoot=root;
this.stage=stage;
}
#Override
public void run()
{
Runnable runner = new Runnable() {
#Override
public void run() {
System.out.println("position 3 : runner");
Button[] button=new Button[2];
button[0]=new Button("Action");
//Set action for button 0
button[1]=new Button("Back");
button[1].setOnAction(e -> {
notify();
stage.getScene().setRoot(oldRoot);
getThread().interrupt();
});
root.getChildren().addAll(oldRoot,button[0],button[1]);
stage.getScene().setRoot(root);
}
};
if (Platform.isFxApplicationThread()) {
System.out.println("Position 1");
runner.run();
} else {
System.out.println("Position 2");
Platform.runLater(runner);
}
}
public Thread getThread(){return thread;}
}
Problem :
When I run project, Platform.runLater(runner) seems not to be working and Application freeze because "main Thread" is waiting
I wonder from morning how to solve the problem. I have login aplication. When user waiting for login i want use processindicator. I used the second thread but it does not work
Main loader fxml
MainController
#FXML public StackPane MainStackPane;
public void initialize(URL arg0, ResourceBundle arg1) {
FXMLLoader loader = new FXMLLoader(this.getClass().getResource("/LoginForm/Login.fxml"));
Pane pane = null;
try {
pane = loader.load();
} catch (IOException e) {System.out.println(e.getMessage());}
LoginController login = loader.getController();
login.setMainController(this);
setScreen(pane, true);
}
public void setScreen(Pane pane, boolean clean){
MainStackPane.getChildren().addAll(pane);
}
LoginForm:
private MainController mainController;
private void Zaloguj() throws IOException {
String cryptUser = null, cryptPass = null;
Test test = new Test(this.mainController);
test.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
LoginSQL sql = new LoginSQL();`
Byte LoginResult = sql.SQLselect(cryptUser, cryptPass);
...}
Class Test
public class test extends Service<Void>{
private MainController mainController;
public test(MainController mainController) {
this.mainController = mainController;
}
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
protected Void call() throws Exception {
System.out.println("Service: START");
ProgressIndicator p = new ProgressIndicator();
Platform.runLater(new Runnable() {
#Override public void run() {
mainController.MainStackPane.getChildren().addAll(p);
}});
if (isCancelled()) {
mainController.MainStackPane.getChildren().remove(p);
}
return null;
};};}}
ProgressIndicator appears only in the next window after login. How to do it ?
JavaFX rendering happens in the main thread only. If you add the ProgressIndicator and then use Thread.sleep(), JavaFX won't render the indicator until Thread.sleep() is done. Also, if the login request hangs, JavaFX will also wait until the login request is complete before rendering.
What you have to do is to make sure to never interrupt/hang the main thread. Remove Thread.sleep, and also move your login request to a child thread. When the request is complete, notify the main thread so that it can remove the ProgressIndicator.
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");
}
}
We have a JDialog that contains a single stage with multiple scenes. Each scene has a next button. When the user clicks "next" we call stage.setScene. This works fine on Windows and Linux, but on Mac, setScene never returns. The application hangs, and it appears to be a thread deadlock. Here is a sample app that reproduces the problem. We have also tried various java 7 builds and java8 pre-release. We think we have found a couple workarounds, but we would like to understand why this code has problems on Mac. Sample app with bug:
public class SampleFxInsideSwing
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
new SampleFxInsideSwing().initFX();
}
});
}
private void initFX()
{
JDialog dialog = new JDialog();
dialog.setSize(new Dimension(500,500));
final JFXPanel stage = new JFXPanel();
dialog.add(stage);
Platform.runLater(new Runnable()
{
#Override
public void run()
{
Button nextButton = new Button("Next");
nextButton.setOnAction(new EventHandler<ActionEvent>()
{
//#Override
public void handle(ActionEvent event)
{
try
{
System.out.println("Clicked");
AnchorPane parent = FXMLLoader.load(SampleFxInsideSwing.class.getResource("SampleFxml.fxml"));
Scene secondScene = new Scene(parent);
stage.setScene(secondScene);
System.out.println("Displaying second scene!");
}
catch (Exception e)
{
e.printStackTrace();
}
}
});
Group group = new Group();
group.getChildren().add(nextButton);
Scene scene1 = new Scene(group);
stage.setScene(scene1);
}
});
dialog.setVisible(true);
}
}