3

I'm developing a JavaFX application that requires resources to be loaded in from a file before the main Application stage can be launched. My solution to accomplish this task is to use a PreLoader so that the user can't interact with the application until the resources have been loaded (pretty standard stuff).

I have a class extending the PreLoader class that creates an object that loads a .fxml file which is what the PreLoader scene will display. It works all nice and dandy until I try to insert code to load the files.

I want to do this concurrently so that during the loading of the files the status gets updated to the screen. I've looked into using tasks and the forums on here but none help as I don't know where to put code where.

Class extending PreLoader:

public class SplashPreLoader extends Preloader {
    private Stage stage;
    private SplashScene loadScreen;

    public void start(Stage stage) throws Exception {
        SplashScene intro = new SplashScene();
        this.loadScreen = intro;
        this.stage = stage;
        stage.setScene(new Scene(intro, 475, 425));
        stage.initStyle(StageStyle.UNDECORATED);
        stage.show();
    }

    @Override
    public void handleProgressNotification(ProgressNotification pn) {
        //bar.setProgress(pn.getProgress());
    }

    @Override
    public void handleStateChangeNotification(StateChangeNotification evt) {
        if (evt.getType() == StateChangeNotification.Type.BEFORE_LOAD) {
            //loadScreen.setStatusMessage("Hello Mister");
        }
        if (evt.getType() == StateChangeNotification.Type.BEFORE_START) {
      /*      Task<Void> task = new Task<Void>() {
                @Override
                public Void call() {
                    updateMessage("this mesashlkjfhkjlsd");
                    return null ;
                }
            };
            statusMessage.textProperty().bind(task.messageProperty());
            task.messageProperty().addListener((obs, oldMessage, newMessage) -> loadScreen.setStatusMessage(newMessage));
            Thread th = new Thread(task);
            th.setDaemon(true);
            th.start();*/

            //loadScreen.loadWebDriver();

            stage.hide();
        }
}

Scene component:

public class SplashScene extends StackPane {

    @FXML public Label statusMessage;

    public SplashScene() {
        FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("splash_scene.fxml"));
        fxmlLoader.setRoot(this);
        fxmlLoader.setController(this);

        //statusMessage.setTextFill(Color.WHITE);

        try {
            fxmlLoader.load();
        } catch (IOException exception) {
            throw new RuntimeException(exception);
        }

    }

    public String getStatusMessage() { return statusMessageProperty().getValue(); }

    public void setStatusMessage(String value) {
        statusMessageProperty().set(value);
        System.out.println("at hert");
    }

    public StringProperty statusMessageProperty() { return statusMessage.textProperty(); }

    public void loadWebDriver() { 
        //This is the function that I want to call to load all the files. 
    }

statusMessage is the Label that I want to modify as the files are being loaded. I've tried putting the task (thread) in the loadWebDriver() function, at the end of the start() and in the preceeding if clause but it is not producing the results I want.

I find it strange too because when I tried this without the task, I had the label change code before the file loading code but they were always executed in the opposite order.

I feel like this could help from the documentation but it doesn't mean anything to me.. Anyone know what this means?

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().

Do I need to utilize the init() method?

Nick97832954
  • 113
  • 6
  • https://github.com/sedj601/SplashScreenTest. Using [MVC](https://stackoverflow.com/questions/32342864/applying-mvc-with-javafx), you can load your model in ` task.setOnSucceeded`. – SedJ601 Jan 27 '20 at 20:57

1 Answers1

4

Here is an example that will hopefully be helpful. One thing you can do is use a Task to load all the data. When the Task is finished, use ideas from Applying MVC with JavaFX to pass the Model to any Controller that needs it.

Main

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

import java.io.IOException;
import javafx.concurrent.Task;

/**
 * JavaFX App
 */
public class App extends Application {



    @Override
    public void start(Stage primaryStage) throws IOException {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("primary.fxml"));
        Parent root = loader.load();
        PrimaryController primaryController = loader.getController();

        primaryStage.setTitle("Hello World");
        primaryStage.setScene(new Scene(root, 300, 275));
        primaryStage.show();

        final Task<DataModel> task = new Task<DataModel>(){
            @Override
            protected DataModel call() throws Exception
            {
                updateProgress(0, 3);
                DataModel dataModel = new DataModel();
                dataModel.loadListViewData();
                Thread.sleep(2000);
                updateProgress(1, 3);
                dataModel.loadComoBoxData();
                Thread.sleep(2000);
                updateProgress(2, 3);
                dataModel.loadTextAreaData();
                Thread.sleep(2000);
                updateProgress(3, 3);
                Thread.sleep(1000);

                return dataModel;
            }
        };

        task.setOnSucceeded((event) -> {             
            try
            {   
                FXMLLoader secondaryLoader = new FXMLLoader(getClass().getResource("secondary.fxml"));
                Stage secondaryStage = new Stage();
                Parent secondaryRoot = secondaryLoader.load();
                SecondaryController secondaryController = secondaryLoader.getController();
                secondaryController.initModel(task.getValue());
                secondaryStage.setTitle("Scene One");
                secondaryStage.setScene(new Scene(secondaryRoot, 500, 500));
                secondaryStage.show();
                primaryStage.close();
            } catch (IOException e)
            {
                e.printStackTrace();
            }

        });

        primaryController.getPBSplashValue().progressProperty().bind(task.progressProperty());
        primaryController.getPISplash().progressProperty().bind(task.progressProperty());

        new Thread(task).start();
    }

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

}

PrimaryController/SplashScreen

import javafx.fxml.FXML;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.ProgressIndicator;

public class PrimaryController
{
    @FXML
    ProgressBar pbSplash;
    @FXML
    ProgressIndicator piSplash;


    public ProgressBar getPBSplashValue()
    {
        return pbSplash;
    }

    public ProgressIndicator getPISplash()
    {
        return piSplash;
    }

}

PrimaryFXML

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.ProgressBar?>
<?import javafx.scene.control.ProgressIndicator?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>

<StackPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sed.test.maventtestproject.PrimaryController">
   <children>
      <VBox maxHeight="-Infinity" maxWidth="-Infinity">
         <children>
            <ProgressBar fx:id="pbSplash" prefWidth="200.0" progress="0.0" />
            <ProgressIndicator fx:id="piSplash" progress="0.0" />
         </children>
      </VBox>
   </children>
</StackPane>

SecondayController/FirstSceneAfterSplashScreen

import javafx.fxml.FXML;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListView;
import javafx.scene.control.TextArea;

public class SecondaryController {
    @FXML ListView<String> lvMain;
    @FXML ComboBox<String> cbMain;
    @FXML TextArea taMain;

    private DataModel model ;

    public void initModel(DataModel model) {
        if (this.model != null) {
            throw new IllegalStateException("Model can only be initialized once");
        }
        this.model = model ;

        lvMain.setItems(this.model.getListViewData());
        cbMain.setItems(this.model.getComboBoxData());
        taMain.setText(this.model.getTextAreaData());
    }
}

SecondaryFXML

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.layout.VBox?>

<VBox alignment="CENTER" spacing="20.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sed.test.maventtestproject.SecondaryController">
    <children>
      <ListView fx:id="lvMain" prefHeight="200.0" prefWidth="200.0" />
      <ComboBox fx:id="cbMain" prefWidth="150.0" />
      <TextArea fx:id="taMain" prefHeight="200.0" prefWidth="200.0" wrapText="true" />
    </children>
    <padding>
        <Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
    </padding>
</VBox>

Model

import java.util.ArrayList;
import java.util.List;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;

/**
 *
 * @author blj0011
 */
public class DataModel
{
    private final ObservableList<String> listViewData = FXCollections.observableArrayList();
    private final ObservableList<String> comboBoxwData = FXCollections.observableArrayList();
    private final StringProperty textAreaDataProperty = new SimpleStringProperty();

    public DataModel()
    {
    }

    public void loadListViewData()
    {
        listViewData.addAll(retrieveListViewDataFromDB());
    }

    public ObservableList<String> getListViewData()
    {
        return listViewData;
    }

    public void loadComoBoxData()
    {
        comboBoxwData.addAll(retrieveComboBoxDataFromDB());        
    }

    public ObservableList<String> getComboBoxData()
    {
        return comboBoxwData;
    }    

    public void loadTextAreaData()
    {
        textAreaDataProperty.set(retrieveTextAreaDataFromDB());
    }

    public StringProperty getTextAreaDataProperty()
    {
        return textAreaDataProperty;
    }

    public String getTextAreaData()
    {
        return textAreaDataProperty.get();
    }


    //Private methods that fake retrieving data from the database.
    private List<String> retrieveListViewDataFromDB()
    {
        List<String> dataFromDB = new ArrayList();
        for(int i = 0; i < 1000; i++)
        {
            dataFromDB.add(Integer.toString(i));
        }

        return dataFromDB;
    }    

    private List<String> retrieveComboBoxDataFromDB()
    {
        List<String> dataFromDB = new ArrayList();
        dataFromDB.add("A");
        dataFromDB.add("B");
        dataFromDB.add("C");
        dataFromDB.add("D");
        dataFromDB.add("E");
        dataFromDB.add("F");

        return dataFromDB;
    }

    private String retrieveTextAreaDataFromDB()
    {
        return "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.";  
    }
}
SedJ601
  • 12,173
  • 3
  • 41
  • 59