Julik
Julik

Reputation: 71

JavaFX Preloader Thread

I need someone explain me how Preloader works in JavaFX application. I have already found out the lifecycle and I know that first is called start(..) of preloader and then start(..) of Application but is this two methods are called from the same Thread? I have tried to make a simple app in start method of application a put Thread.sleep(8000) and in the start of Preloader I created a simple stage and show it. But when I run the application it just freeze with black stage and only after 8 seconds shows preloader stage correctly, but why?

Update my post with source code:

public class PreloaderApp extends Preloader {

public void init(){
    System.out.println("Thread Preloader ID:"+ Thread.currentThread().getId());
    System.out.println("  ------  ");

    Pane effectRegion = LoaderFrame.getInstance().getEffectPane();
    JFXFillTransition fill = new JFXFillTransition();
    fill.setDuration(Duration.millis(400));
    fill.setRegion(effectRegion);
    fill.setAutoReverse(true);
    fill.setCycleCount(Animation.INDEFINITE);
    fill.setFromValue(Color.WHITE);
    fill.setToValue(Color.rgb(0,77,147));

    fill.play();

}

@Override
public void start(Stage primaryStage){
    System.out.println("Thread Preloader(start) ID:"+ Thread.currentThread().getId());
    System.out.println("  ------  ");
    LoaderFrame.getInstance().show();

}

}

public class LoaderFrame extends Stage {

private static final LoaderFrame instance = new LoaderFrame();

public static LoaderFrame getInstance(){
    return instance;
}


private Scene scene;
private AnchorPane root;
private BorderPane wraper;
private StackPane effectPane;

private JFXButton loaderPathButton;


public LoaderFrame(){
    initScene();
    this.initStyle(StageStyle.TRANSPARENT);
    this.getIcons().add(new Image("file:imgs/favico/icon48.png"));
}

public void initScene(){
    FXMLLoader loader = new FXMLLoader(Main.class.getResource("xml/loader_frame.fxml"));
    root = null;

    try {
        root = loader.load();
        wraper = (BorderPane) root.lookup("#rootPane");
        loaderPathButton = (JFXButton) root.lookup("#loaderPathButton");
        effectPane = (StackPane) root.lookup("#effectPane");
        scene = new Scene(root);
        scene.setFill(Color.TRANSPARENT);
        scene.getStylesheets().add("file:css/loader.css");
        this.setScene(scene);
    } catch (IOException e) {
        e.printStackTrace();
    }

}


public Pane getEffectPane(){
    return effectPane;
}

}

Upvotes: 1

Views: 1447

Answers (1)

jns
jns

Reputation: 6952

To investigate the Preloader lifecycle you could add the following line to your Preloader class:

    @Override
    public void handleStateChangeNotification(StateChangeNotification info) {
        System.out.println("state: " + info.getType());
    }

In your Application class you can add a System.out.prinln message in the init / start method. This will show the Preloader running through its different states: BEFORE_LOAD, BEFORE_INIT, BEFORE_START.

As the Preloaders start method is called on the JavaFXThread you have to wrap your call to Thread.sleep in Platform.runlater.

Note that preloaders are subject to the same rules as other JavaFX applications including FX threading rules. In particular, the class constructor and init() method will be called on a non-FX thread and start() will be executed on the FX application thread. This also means that the application constructor/init() will run concurrently with preloader start().

If you need to do some time consuming initialization, you should do it in your Apps init method. When the Preloader state changes to BEFORE_START you can hide your Preloader stage

public class App extends Application {

    private static final int PRELOADER_SHOWTIME_MILLIS = 2000;

    @Override
    public void init() throws Exception {
        long start = System.currentTimeMillis();

        // time consuming initializations

        long duration = System.currentTimeMillis() - start;
        long remainingShowTime = PRELOADER_SHOWTIME_MILLIS - duration;

        if(remaingShowTime > 0 ){
            Thread.sleep(remainingShowTime);
        }
    }

    @Override
    public void start(Stage primaryStage) {
        StackPane root = new StackPane(new Label("application"));
        Scene scene = new Scene(root, 400, 400);

        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

public class TestPreloader extends Preloader {

    private Stage stage;  

    @Override
    public void start(Stage primaryStage) throws Exception {
        stage = primaryStage;

        Rectangle rect = new Rectangle(200, 200, Color.RED);
        FadeTransition transition = new FadeTransition(Duration.seconds(2), rect);
        transition.setFromValue(0);
        transition.setToValue(1);

        Label lbl = new Label("preloader");
        StackPane root = new StackPane(rect, lbl);

        primaryStage.setScene(new Scene(root));
        primaryStage.show();

        transition.play();
    }

    @Override
    public void handleStateChangeNotification(StateChangeNotification info) {
        if (info.getType() == Type.BEFORE_START) {
           stage.hide();
        }
     }

}

public class Main {

    public static void main(String[] args) {
        LauncherImpl.launchApplication(App.class, TestPreloader.class, args);
    }

}

Upvotes: 1

Related Questions