user8053218
user8053218

Reputation:

JavaFX Preloader never called from main

My IDE is IntelliJ. I tried this document to learn preloader but for some reason preloader never gets called from my main class, or even its methods get called.

So here is my main class:

public class LongInitApp extends Application {
Stage stage;
BooleanProperty ready = new SimpleBooleanProperty(false);
private void longStart() {
    //simulate long init in background
    Task task = new Task<Void>() {
        @Override
        protected Void call() throws Exception {
            int max = 10;
            for (int i = 1; i <= max; i++) {
                Thread.sleep(200);
                notifyPreloader(new ProgressNotification(((double) i)/max));
            }
            ready.setValue(Boolean.TRUE);

            notifyPreloader(new StateChangeNotification(
                StateChangeNotification.Type.BEFORE_START));
           return null;
        }};
    new Thread(task).start();
}

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

@Override
public void start(final Stage stage) throws Exception {
    longStart();
    stage.setScene(new Scene(new Label("Application started"),400, 400));
    ready.addListener(new ChangeListener<Boolean>(){
        public void changed(
            ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) {
                if (Boolean.TRUE.equals(t1)) {
                    Platform.runLater(new Runnable() {
                        public void run() {
                            stage.show();
                        }});}}});;}}

And my preloader class:

public class LongAppInitPreloader extends Preloader {
ProgressBar bar;
Stage stage;
boolean noLoadingProgress = true;

private Scene createPreloaderScene() {
    bar = new ProgressBar(0);
    BorderPane p = new BorderPane();
    p.setCenter(bar);
    return new Scene(p, 300, 150);
}

public void start(Stage stage) throws Exception {
    this.stage = stage;
    stage.setScene(createPreloaderScene());
    stage.show();
}

@Override
public void handleProgressNotification(ProgressNotification pn) {
    if (pn.getProgress() != 1.0 || !noLoadingProgress) {
      bar.setProgress(pn.getProgress()/2);
      if (pn.getProgress() > 0) {
          noLoadingProgress = false;
      }
    }
}

@Override
public void handleStateChangeNotification(StateChangeNotification evt) {
    //ignore, hide after application signals it is ready
}

@Override
public void handleApplicationNotification(PreloaderNotification pn) {
    if (pn instanceof ProgressNotification) {
       double v = ((ProgressNotification) pn).getProgress();
       if (!noLoadingProgress) {           
           v = 0.5 + v/2;
       }
       bar.setProgress(v);            
    } else if (pn instanceof StateChangeNotification) {
        stage.hide();
    }
}  
 }

You can check this http://docs.oracle.com/javafx/2/deployment/preloaders.htm document too. This codes belong to example 9-11 and 9-12.

Upvotes: 0

Views: 1453

Answers (2)

James_D
James_D

Reputation: 209408

You need to specify the preloader class to the application when it launches. A "quick and dirty" way to do this is to use the non-public API class com.sun.javafx.application.LauncherImpl. Note that this class is not guaranteed to be available in future releases of JavaFX, and so you should use this for quick testing only (if at all). Use the following main method in your LongInitApp class:

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

The "official" way to include a preloader is by specifying it as part of the JavaFX deployment process. Full documentation of the deployment process is at http://docs.oracle.com/javase/8/docs/technotes/guides/deploy/, but a minimal approach is as follows.

  1. Compile the application (your IDE will typically do this when you save anyway)
  2. From the command line, run the javapackager tool with the createjar command:

    javapackager -createjar -outfile myapp.jar -appclass my.package.LongInitApp \
      -preloader my.package.LongAppInitPreloader -srcdir dir
    

    where my.package is the package containing your application and preloader classes (these can be in different packages) and dir is the root of the directory structure containing all your classes (e.g. if my.package really were your package name, then dir would have a sub-directory my, which would have a subdirectory package, which would contain the .class files).

    This will generate a myapp.jar file which is executable and aware of your preloader, so you can then execute it with java -jar myapp.jar. If you're interested, you can extract the generated manifest from the jar file and see what's in it with jar xf myapp.jar META-INF/MANIFEST.MF (and then view the file META-INF/MANIFEST.MF). (In brief, what this does is declare the main class to be an internal class designed to launch a JavaFX application. The manifest file contains properties specifying the JavaFX application class (LongInitApp in your case) and the preloader class, if one exists. The internal class that launches the application retrieves these properties and its main method basically launches the application you have defined, using the preloader if it's there.)

    Note your application class LongInitApp does not need (and probably should not have) a main method if you use this approach.

Most IDEs have some form of support for this. E.g. if you use Eclipse with the E(fx)clipse plugin, and create a JavaFX project, it will generate a build.fxbuild file for you. Double-clicking that file will open a visual editor where you can set the properties defined in the javapackager command above. Clicking on "Generate ant build.xml and run" will then create the jar file.

Unless you need the specific functionality that a preloader provides, above and beyond that which you can easily program yourself, it may not be worth the effort of going through this. Specifically, a preloader is particularly useful if you are deploying your application via Java Web Start, and the download of the main jar file will take a long time. The preloader can be shown while that is occurring. If you are using a standalone application (or self-contained application package), then you can pretty easily create the "preloader" functionality yourself.

For example, unless I were using Java Web Start and the main jar file were large, I would probably refactor your example code by making the "preloader" just a regular old class:

import javafx.scene.Scene;
import javafx.beans.property.DoubleProperty ;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class LongAppInitPreloader {
    private ProgressBar bar;

    private Stage stage;

    public LongAppInitPreloader() {
        this.stage = new Stage();
        stage.setScene(createPreloaderScene());
    }

    private Scene createPreloaderScene() {
        bar = new ProgressBar(0);
        BorderPane p = new BorderPane();
        p.setCenter(bar);
        return new Scene(p, 300, 150);
    }

    public void show() {
        stage.show();
    }

    public void hide() {
        stage.hide();
    }

    public DoubleProperty progressProperty() {
        return bar.progressProperty();
    }
}

and then your application class can just use a task and update its progress:

import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.stage.Stage;

public class LongInitApp extends Application {

    @Override
    public void start(final Stage stage) throws Exception {
        Task<Void> task = createLongStartTask();

        stage.setScene(new Scene(new Label("Application started"), 400, 400));

        LongAppInitPreloader preloader = new LongAppInitPreloader();
        preloader.progressProperty().bind(task.progressProperty());

        task.setOnSucceeded(e -> {
            stage.show();
            preloader.hide();
        });
        preloader.show();

        new Thread(task).start();
    }

    private Task<Void> createLongStartTask() {
        // simulate long init in background
        Task<Void> task = new Task<Void>() {
            @Override
            protected Void call() throws Exception {
                int max = 10;
                for (int i = 1; i <= max; i++) {
                    Thread.sleep(200);
                    updateProgress(i, max);
                }

                return null;
            }
        };
        return task ;
    }

    // just here so I can run directly from Eclipse:
    public static void main(String args[]) {
        launch(args);
    }
}

Upvotes: 2

Keyur Bhanderi
Keyur Bhanderi

Reputation: 1544

You are missing Main method in LongInitApp class

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

Upvotes: 0

Related Questions