如何在 FXML 控件状态被丢弃之前保存它们?

How to save FXML control states before they're discarded?

这是我的第一个 FXML 应用程序。以前,我对 Swing 非常熟悉。我只是想从舞台上的控件中保存几个值,以便用户输入的值在运行时重新加载。 ReliableOneShotCloseHandler 是我 的 JavaFX 版本。但是,在调用 windowClosing 函数时,控件似乎已经被处理掉了……?我在指定的行收到 NullPointerException:

package testfxmlloader;

import java.net.URL;
import java.util.ResourceBundle;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.fxml.JavaFXBuilderFactory;
import javafx.scene.control.Label;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;

/**
*
* @author maskedcoder
*/
public class FXMLDocumentController implements Initializable {

    @FXML
    private Label label;

    @FXML
    private AnchorPane apMain;

    @FXML
    private void handleButtonAction(ActionEvent event) {
        System.out.println("You clicked me!");
        label.setText("Hello World!");
    }

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        ReliableOneShotCloseHandler roscSaveSettings = new ReliableOneShotCloseHandler(apMain.getScene().getWindow(), new ReliableOneShotCloseHandler.CloseDuties() {

            @Override
            public boolean confirmClose(WindowEvent e) {
                return true;
            }

            @Override
            public void windowClosing(WindowEvent e) {
                saveSettings();
            }
        });
    }   

    public void saveSettings() {
        System.out.println("save setting: " + label.getText());  // gets NullPointerException because label is null.
    }

}

class ReliableOneShotCloseHandler {

    public interface CloseDuties {
        public boolean confirmClose(WindowEvent e);
        public void windowClosing(WindowEvent e);
    }

    private final CloseDuties closeDuties;
    private boolean windowClosingFired = false;
    private final EventHandler<WindowEvent> openEvent;
    private final EventHandler<WindowEvent> closeEvent;

    public ReliableOneShotCloseHandler(Window thisWindow, CloseDuties iniCloseDuties) {
        super();
        closeDuties = iniCloseDuties;
        openEvent = (WindowEvent event) -> {
            windowClosingFired = false;
        };
        closeEvent = (WindowEvent event) -> {
            if(!windowClosingFired) {
                if(closeDuties.confirmClose(event)) {
                    closeDuties.windowClosing(event);
                    windowClosingFired = true;
                }
                else
                    event.consume();
            }
        };

        thisWindow.setOnShowing(openEvent);
        thisWindow.setOnShown(openEvent);
        thisWindow.setOnCloseRequest(closeEvent);
        thisWindow.setOnHiding(closeEvent);
        thisWindow.setOnHidden(closeEvent);
    }
}

public class TestFXMLLoader extends Application {
    FXMLLoader fxmll;
    FXMLDocumentController fdc;

    @Override
    public void start(Stage stage) throws Exception {
        fdc = new FXMLDocumentController();
        fxmll = new FXMLLoader();
        fxmll.setBuilderFactory(new JavaFXBuilderFactory());
        Parent root = fxmll.load(getClass().getResource("FXMLDocument.fxml"));
        fxmll.setController(fdc);

        Scene scene = new Scene(root);

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

    /**
    * @param args the command line arguments
    */
    public static void main(String[] args) {
        launch(args);
    }

}

这是 FXML:

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

<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<AnchorPane id="AnchorPane" fx:id="apMain" prefHeight="200" prefWidth="320" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.40" fx:controller="testfxmlloader.FXMLDocumentController">
    <children>
        <Button fx:id="button" layoutX="126" layoutY="90" onAction="#handleButtonAction" text="Click Me!" />
        <Label fx:id="label" layoutX="126" layoutY="120" minHeight="16" minWidth="69" />
    </children>
</AnchorPane>

windowClosing 函数应该在没有关闭权限之前调用,所以控件应该都可以正常工作。上面的测试代码基于 NetBeans 的默认 JavaFXML 应用程序,只是添加了 ReliableOneShotCloseHandler 和相关代码。我的逻辑有缺陷吗?还有其他方法吗?

更新: 包含了一些不应该包含的代码行。它们已被删除。

嗯,从架构的角度来看,这段代码相当混乱。一般来说,我会明确区分控制器和应用程序 class。因此,应用程序 class 只加载 FXML 文件,控制器 class 从那里引用,然后将其分配给场景或更准确地说是舞台。因此,控制器 class 仅负责将来自您的控制元素的任何事件作为您的 onCloseRequest-事件进行管理。

另一个问题可能是同时使用 EventFilterEventHandler。如果您在舞台上使用 EventFilter,它的所有节点和舞台本身将永远不会被调用它们的 EventHandler

因此,最简单的解决方案是,如果您直接在控制器 class 的舞台上添加一个 EventHandler,您可以从那里直接访问所有其他控件(例如您的标签)。

Main.java

public class Main extends Application {

@Override
public void start(Stage primaryStage) throws Exception{
    FXMLLoader loader = new FXMLLoader(getClass().getResource("sample.fxml"));
    Parent root = (Parent) loader.load();
    primaryStage.setTitle("Hello World");
    primaryStage.setScene(new Scene(root, 300, 275));
    primaryStage.show();

    Controller controller = (Controller)loader.getController();
    controller.setStage(primaryStage);
}


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

} 

sample.fxml

<AnchorPane fx:id="apMain" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller">
   <children>
      <Button fx:id="button" layoutX="39.0" layoutY="35.0" mnemonicParsing="false" onAction="#handleButtonAction" text="Button" AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="10.0" />
      <Label fx:id="label" layoutX="52.0" layoutY="102.0" text="Label" AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="50.0" />
   </children>
</AnchorPane>

Controller.java

public class Controller implements Initializable {

@FXML
public AnchorPane apMain;

@FXML
public Button button;

@FXML
public Label label;

@Override
public void initialize(URL location, ResourceBundle resources) {

}

public void handleButtonAction() {
    System.out.println("You clicked me!");
    label.setText("Hello World!");
}

public void setStage(Stage stage){
    stage.setOnCloseRequest(new EventHandler<WindowEvent>() {
        @Override
        public void handle(WindowEvent event) {
            System.out.println(label.getText());
        }
    });
}

}