首页 > 解决方案 > 来自 PreLoader 的多线程 - JavaFX

问题描述

我正在开发一个 JavaFX 应用程序,该应用程序需要在启动主应用程序阶段之前从文件中加载资源。我完成此任务的解决方案是使用 PreLoader,以便用户在加载资源之前无法与应用程序交互(非常标准的东西)。

我有一个扩展 PreLoader 类的类,它创建一个加载 .fxml 文件的对象,这是 PreLoader 场景将显示的内容。在我尝试插入代码来加载文件之前,它工作得很好而且很花哨。

我想同时执行此操作,以便在加载文件期间将状态更新到屏幕上。我已经研究过在这里使用任务和论坛,但没有帮助,因为我不知道将代码放在哪里。

类扩展 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();
        }
}

场景组件:

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是我在加载文件时要修改的标签。我尝试将任务(线程)放在loadWebDriver()函数的末尾start()和前面的 if 子句中,但它没有产生我想要的结果。

我也觉得很奇怪,因为当我在没有 的情况下尝试这个时task,我在文件加载代码之前有标签更改代码,但它们总是以相反的顺序执行。

我觉得这可以从文档中得到帮助,但这对我来说没有任何意义..有人知道这意味着什么吗?

请注意,预加载器遵循与其他 JavaFX 应用程序相同的规则,包括 FX 线程规则。特别是,类构造函数和 init() 方法将在非 FX 线程上调用,而 start() 将在 FX 应用程序线程上执行。这也意味着应用程序构造函数/init() 将与预加载器 start() 同时运行。

我需要使用 init() 方法吗?

标签: javajavafx

解决方案


这是一个希望对您有所帮助的示例。您可以做的一件事是使用 aTask加载所有数据。完成Task后,使用Applying MVC with JavaFX中的想法将其传递Model给任何Controller需要它的人。

主要的

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();
    }

}

主控制器/闪屏

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;
    }

}

初级FXML

<?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());
    }
}

二级FXML

<?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>

模型

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.";  
    }
}

推荐阅读