如何在 javafx 中关闭弹出窗口 windows?

How to close pop up windows in javafx?

我有两个场景 - 登录提示和主屏幕。 openLoginPrompt() 函数打开登录提示。然后我调用 handleLogin() 函数来尝试对用户进行身份验证。登录成功后,我想关闭登录提示并 return 到主屏幕。但是,如果我不将 primaryStage 设置为静态 primaryStage,则在调用 handleLogin() 函数时似乎为空。为什么会出现这种情况,是否有更好的关闭阶段的替代方法?

    /*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package chatapplication;

import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;

/**
 *
 * @author babaji
 */
public class Controller {


private static Stage primaryStage;

@FXML
private Button openLoginPrompt;
@FXML
private Button login;
@FXML
private TextField username;
@FXML
private PasswordField password;
@FXML
private Label loginMessage;
@FXML
private AnchorPane loginPrompt;

@FXML
private void openLoginPrompt() {
    try {
        primaryStage = new Stage();
        // Load root layout from fxml file.
        FXMLLoader loader = new FXMLLoader();
        loader.setLocation(ChatApplication.class.getResource("LoginPrompt.fxml"));
        AnchorPane rootLayout = (AnchorPane) loader.load();

        // Show the scene containing the root layout.
        Scene scene = new Scene(rootLayout);
        primaryStage.setScene(scene);
        primaryStage.show();

    } catch (IOException e) {
        e.printStackTrace();
    }
}

@FXML 
private void handleLogin() {
    Database dbObj = new Database();
    Connection conn = dbObj.connectToDb("chat");
    String uname = username.getText();
    String psswd = password.getText();
    ResultSet rs = null;
    try {
        String sql = "SELECT * FROM user WHERE user_name=? AND password=?";
        PreparedStatement ps = conn.prepareStatement(sql);
        ps.setString(1, uname);
        ps.setString(2, psswd);
        rs = ps.executeQuery();
    }
    catch(SQLException e) {
        e.printStackTrace();
    }
    int count = 0;
    try {
        if(rs != null) while(rs.next()) count++;
    }
    catch(SQLException e) {
        count = 0;

    }
    if(count == 1) {
        System.out.println("Successfully Logged in");
        /*This is where the problem Lies. If I dont't set primaryStage as
        static, primaryStage returns null. Why does this happen and is there
        some other way to close the window?s
        */
        if(primaryStage != null) primaryStage.close();
    }
    else {
        System.out.println("Failed to login.");
        loginMessage.setText("Incorrect username or password");
    }
}

}

您有两个控制器实例,一个用于 LoginPrompt.fxml,一个用于您未显示的另一个 FXML(我们称它为 "main" fxml)。 openLoginPrompt 在 "main" 实例上调用,并初始化 primaryStage,并加载 LoginPrompt.fxml。当您加载 FXML 时,FXMLLoader 会创建一个新的控制器实例,它没有 primaryStage 初始化(假设它不是 static);所以当在 LoginPrompt.fxml 控制器上调用 handleLogin() 时,它是 null.

对两个不同的 FXML 文件使用同一个控制器 class 是一个非常糟糕的主意,因为它会导致各种令人困惑的场景,例如在 class。您应该为每个 FXML 文件使用不同的控制器 class。

在这种情况下,您可以定义一个 LoginController class。编辑 LoginPrompt.fxml 以使用此控制器 class。您可以从 class 公开各种属性,并在打开登录 window:

时从主控制器观察它们
public class LoginController {

    @FXML
    private Label loginMessage ;

    private final BooleanProperty loggedIn = new SimpleBooleanProperty();
    public BooleanProperty loggedInProperty() {
        return loggedIn ;
    }

    public final boolean isLoggedIn() {
        return loggedInProperty().get();
    }

    public final void setLoggedIn(boolean loggedIn) {
        loggedInProperty().set(loggedIn);
    }

    @FXML
    private void handleLogin() {
        // existing code you had in the previous version...
        if (count == 1) {
            System.out.println("Successfully logged in");
            setLoggedIn(true);
        } else {
            System.out.println("Failed to login.");
            loginMessage.setText("Incorrect username or password");
        }
    }   

}

然后在你的主控制器中你做

@FXML
private void openLoginPrompt() {
    try {
        Stage loginStage = new Stage();
        // Load root layout from fxml file.
        FXMLLoader loader = new FXMLLoader();
        loader.setLocation(ChatApplication.class.getResource("LoginPrompt.fxml"));
        AnchorPane rootLayout = (AnchorPane) loader.load();

        LoginController loginController = loader.getController();
        loginController.loggedInProperty().addListener((obs, wasLoggedIn, isNowLoggedIn) -> {
            if (isNowLoggedIn) {
                loginStage.hide();
            }
        });

        // Show the scene containing the root layout.
        Scene scene = new Scene(rootLayout);
        loginStage.setScene(scene);
        loginStage.show();

    } catch (IOException e) {
        e.printStackTrace();
    }
}

此版本无需将 Stage 设为字段。

我还应该提到有一种 "quick and dirty" 方法可以直接从 LoginController:

关闭舞台
loginMessage.getScene().getWindow().hide();

这将避免需要 属性 甚至从主控制器获取对 LoginController 的引用。然而,您经常遇到这样的情况,您可能需要来自另一个控制器的数据,而第一种技术是一种更普遍有用的方法。