英文:
how can i use function from another class?
问题
尝试将项目设置为只有一个控制器处理导航,并可以在另一个独立的控制器中调用导航到另一页的功能。
这是我为导航创建的控制器:
package system.test.librarysystem;
import javafx.event.ActionEvent;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.io.IOException;
import java.util.Objects;
public class sceneController {
// 省略其他代码...
public void switchToLogin(ActionEvent event) throws IOException {
Parent root = FXMLLoader.load(Objects.requireNonNull(getClass().getResource("Login.fxml")));
// 省略其他代码...
}
// 省略其他跳转页面的函数...
}
目前我正尝试使其与此登录控制器一起工作:
package system.test.librarysystem;
import java.io.IOException;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.PasswordField;
import javafx.scene.input.KeyEvent;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
public class LoginController {
// 省略其他代码...
public void loginButtonAction(ActionEvent e) {
if (usernameEntry.getText().isBlank() == false && passwordEntry.getText().isBlank() == false) {
// 省略其他代码...
validateLogin();
} else {
loginLabel.setText("请输入用户名和密码");
}
}
public void validateLogin() {
// 省略其他代码...
while(queryResult.next()) {
if (queryResult.getInt(1) == 1 ) {
// 省略其他代码...
FXMLLoader loader = new FXMLLoader(getClass().getResource("Book.FXML"));
sceneController sceneController = loader.getController();
ActionEvent actionEvent = new ActionEvent();
sceneController.switchToBook(actionEvent);
} else {
loginLabel.setText("无效登录");
}
}
// 省略其他代码...
}
}
如何让这个工作?
英文:
tying to set my project up so that just one controller handles the navigation and that i can just call the function for navigating to another page in a sperate controller and atach that function to a button.
this is the controller i have for navigation
package system.test.librarysystem;
import javafx.event.ActionEvent;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.io.IOException;
import java.util.Objects;
public class sceneController {
private Stage stage;
private Scene scene;
private Parent root;
public void switchToLogin(ActionEvent event) throws IOException {
Parent root = FXMLLoader.load(Objects.requireNonNull(getClass().getResource("Login.fxml")));
stage = (Stage)((Node)event.getSource()).getScene().getWindow();
scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
public void switchToBook(ActionEvent event) throws IOException {
Parent root = FXMLLoader.load(Objects.requireNonNull(getClass().getResource("Book.fxml")));
stage = (Stage)((Node)event.getSource()).getScene().getWindow();
scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
public void switchToDVD(ActionEvent event) throws IOException {
Parent root = FXMLLoader.load(Objects.requireNonNull(getClass().getResource("DVD.fxml")));
stage = (Stage) ((Node) event.getSource()).getScene().getWindow();
scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
}
at the moment im trying to get it to work with this login controller
package system.test.librarysystem;
import java.io.IOException;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.PasswordField;
import javafx.scene.input.KeyEvent;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
public class LoginController {
@FXML
private Label loginLabel;
@FXML
private TextField usernameEntry;
@FXML
private PasswordField passwordEntry;
public void loginButtonAction(ActionEvent e) {
if (usernameEntry.getText().isBlank() == false && passwordEntry.getText().isBlank() == false) {
//loginLabel.setText("login successful");
validateLogin();
} else {
loginLabel.setText("Please enter username and password");
}
}
public void validateLogin() {
DatabaseConnection connectNow = new DatabaseConnection();
Connection connectDB = connectNow.getConnection();
String verifyLogin = "SELECT count(1) FROM users WHERE username = '" + usernameEntry.getText() + "' AND password = '" + passwordEntry.getText() + "'";
try {
Statement statement = connectDB.createStatement();
ResultSet queryResult = statement.executeQuery(verifyLogin);
while(queryResult.next()) {
if (queryResult.getInt(1) ==1 ) {
//loginLabel.setText("Welcome");
FXMLLoader loader = new FXMLLoader(getClass().getResource("Book.FXML"));
sceneController sceneController = loader.getController();
ActionEvent actionEvent = new ActionEvent();
sceneController.switchToBook(actionEvent);
} else {
loginLabel.setText("Invalid login");
}
}
} catch (Exception e) {
}
}
}
how can i get this working?
答案1
得分: 1
你需要确保你的 LoginController
拥有对 在加载对应 FXML 时创建的 SceneController
实例的引用。在 switchToLogin()
方法中,你可以通过将对该实例的引用传递给 LoginController
,来实现这个目的。
简单来说,代码如下:
public void switchToLogin(ActionEvent event) throws IOException {
FXMLLoader loader = new FXMLLoader(Objects.requireNonNull(getClass().getResource("Login.fxml")));
Parent root = loader.load();
LoginController controller = loader.getController();
controller.setSceneController(this);
stage = (Stage)((Node)event.getSource()).getScene().getWindow();
scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
LoginController
中的代码如下:
public class LoginController {
private SceneController sceneController ;
public void setSceneController(SceneController sceneController){
this.sceneController = sceneController;
}
// ...
if (queryResult.getInt(1) == 1) {
sceneController.switchToBook(null);
}
// ...
}
然而,这样做实际上不是一个很好的做法,最好使用 MVC 设计。在 MVC 设计中,控制器应仅更新模型。通常,视图会观察模型并根据模型数据的更改更新其控件。 JavaFX FXML 设计实际上更像是 MVP 设计,其中 "Presenter" 与视图(FXML)更紧密地协作。然而,Presenter/Controller 应仅更新其直接负责的 UI 控件;它不应更新其他 UI 控件或与其他控制器通信。 例如,你的登录控制器试图与场景控制器通信,而场景控制器试图修改场景(我几乎可以确定没有在相应的 FXML 中定义)。
相比于在控制器之间进行通信,你应该更新和观察一个模型,将所有控制器彼此解耦。
这里有一个非常简单的示例。此代码也作为 gist 发布。
模型将存储和公开 User
和当前的 BrowseState
(基本上只是 "用户正在浏览 DVD 还是图书"):
User.java
:
package org.jamesd.examples.library;
public record User(String username) {}
和枚举:
BrowseState.java
:
package org.jamesd.examples.library;
public enum BrowseState { BOOK, DVD }
然后模型是:
Model.java
package org.jamesd.examples.library;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
public class Model {
private final ObjectProperty<User> user = new SimpleObjectProperty<>(null);
public User getUser() {
return user.get();
}
public ObjectProperty<User> userProperty() {
return user;
}
public void setUser(User user) {
this.user.set(user);
}
private final ObjectProperty<BrowseState> browseState = new SimpleObjectProperty<>(BrowseState.BOOK);
public BrowseState getBrowseState() {
return browseState.get();
}
public ObjectProperty<BrowseState> browseStateProperty() {
return browseState;
}
public void setBrowseState(BrowseState browseState) {
this.browseState.set(browseState);
}
public void logout() {
setUser(null);
}
}
这个思想是所有 MVC 类型设计共有的,即控制器可以更新模型,任何感兴趣的方可观察模型中的属性,如果需要根据值的变化更新 UI。
这是 Login.fxml
:
<?xml version="1.0" encoding="UTF-8"?>
...
和它的控制器
LoginController.java
:
...
请注意,这个控制器没有无参数构造函数,我们稍后会处理它。
更重要的是,请注意,它不会尝试在用户登录时更改视图。 它只是用新用户更新模型。
这是一个 Book.fxml
:
...
和它的控制器:
BookController.java
:
...
就像 LoginController
一样,注意它没有无参数构造函数,并且注意它不尝试更改除了它直接负责的那些以外的任何 UI 元素(在 Book.fxml
中定义的那些)。 它只是调用模型上的方法。
为了演示目的,DVD 也基本相同:
Dvd.fxml
:
...
DvdController.java
:
...
现在我们需要做的就是组装应用程序。 应用程序类应该实例化模型、视图管理器和场景,并将场景的根绑定到视图管理器中的当前视图:
HelloApplication.java
:
...
这就是全部,现在我们只需要实例化模型、视图管理器和场景,并将场景的根绑定到视图管理器的当前视图。
英文:
You would need your LoginController
to have a reference to the instance of SceneController
that was created when the corresponding FXML was loaded. You could in theory do this by passing a reference to that instance to the LoginController
when you load Login.fxml
in the switchToLogin()
method.
Briefly, this would look like:
public void switchToLogin(ActionEvent event) throws IOException {
FXMLLoader loader = new FXMLLoader(Objects.requireNonNull(getClass().getResource("Login.fxml")));
Parent root = loader.load();
LoginController controller = loader.getController();
controller.setSceneController(this);
stage = (Stage)((Node)event.getSource()).getScene().getWindow();
scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
and in the LoginController
:
public class LoginController {
private SceneController sceneController ;
public void setSceneController(SceneController sceneController){
this.sceneController = sceneController;
}
// ...
if (queryResult.getInt(1) ==1 ) {
sceneController.switchToBook(null);
}
// ...
}
However, it's not really good practice to have controllers having dependencies on each other. A better approach is to use a MVC design. In a MVC design, the controller should only update the model. Typically, a view would observe the model and update its controls based on changes in the model data. The JavaFX FXML design is really more of a MVP design, where the "Presenter" collaborates more closely with the view (the FXML). However, the presenter/controller should only update the UI controls for which it is directly responsible; it should not update other UI controls or communicate with other controllers. For example, your login controller is trying to communicate with the scene controller, and your scene controller is trying to modify the scene (which I am fairly sure is not defined in the corresponding FXML).
Instead of communicating between controllers, you should update and observe a model, decoupling all controllers from each other.
Here is a very quick example. This code is also posted as a gist.
The model will store and expose a User
and the current BrowseState
(which is basically just "is the user browsing DVDs or Books"). For this we have a very simple record
:
User.java
:
package org.jamesd.examples.library;
public record User(String username) {}
and enum:
BrowseState.java
:
package org.jamesd.examples.library;
public enum BrowseState { BOOK, DVD }
And then the model is:
Model.java
package org.jamesd.examples.library;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
public class Model {
private final ObjectProperty<User> user = new SimpleObjectProperty<>(null);
public User getUser() {
return user.get();
}
public ObjectProperty<User> userProperty() {
return user;
}
public void setUser(User user) {
this.user.set(user);
}
private final ObjectProperty<BrowseState> browseState = new SimpleObjectProperty<>(BrowseState.BOOK);
public BrowseState getBrowseState() {
return browseState.get();
}
public ObjectProperty<BrowseState> browseStateProperty() {
return browseState;
}
public void setBrowseState(BrowseState browseState) {
this.browseState.set(browseState);
}
public void logout() {
setUser(null);
}
}
The idea, common to all MVC-type designs, is that the controllers can update the model and any interested actors can observe the properties in the model if they need to update the UI as a result of values changing.
Here is the Login.fxml
:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.PasswordField?>
<?import javafx.scene.control.Tooltip?>
<GridPane xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
hgap="5" vgap="5"
alignment="CENTER"
fx:controller="org.jamesd.examples.library.LoginController">
<Label fx:id="loginLabel" text="Please enter user name and password"
GridPane.rowIndex="0" GridPane.columnIndex="0" GridPane.columnSpan="2"/>
<Label text="Username:" GridPane.rowIndex="1" GridPane.columnIndex="0"/>
<Label text="Password:" GridPane.rowIndex="2" GridPane.columnIndex="0"/>
<TextField fx:id="usernameEntry" GridPane.rowIndex="1" GridPane.columnIndex="1"/>
<PasswordField fx:id="passwordEntry" GridPane.rowIndex="2" GridPane.columnIndex="1"
promptText="Password is 'secret'">
<tooltip>
<Tooltip text="Password is 'secret'"/>
</tooltip>
</PasswordField>
<HBox alignment="CENTER" GridPane.rowIndex="3" GridPane.columnIndex="0" GridPane.columnSpan="2">
<Button text="Login" onAction="#loginButtonAction" />
</HBox>
</GridPane>
and its controller
LoginController.java
:
package org.jamesd.examples.library;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
public class LoginController {
private final Model model;
@FXML
private Label loginLabel;
@FXML
private TextField usernameEntry;
@FXML
private PasswordField passwordEntry;
public LoginController(Model model) {
this.model = model;
}
public void loginButtonAction() {
String username = usernameEntry.getText();
String password = passwordEntry.getText();
if (username.isBlank() || password.isBlank()) {
loginLabel.setText("Please enter username and password");
} else {
if (validateLogin(username, password)) {
model.setUser(new User(username));
} else {
loginLabel.setText("Invalid username and/or password");
}
}
}
public boolean validateLogin(String username, String password) {
// dummy implementation
return "secret".equals(password);
}
}
Note this controller does not have a no-arg constructor, which we will deal with later.
More importantly, note that it does not attempt to change the view when the user logs in. All it does is update the model with the new user.
Here is a Book.fxml
:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.control.Button?>
<VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
spacing="20"
alignment="CENTER"
fx:controller="org.jamesd.examples.library.BookController">
<padding><Insets topRightBottomLeft="20"/></padding>
<Label fx:id="welcomeLabel"/>
<Label text="Please browse our books..."/>
<HBox spacing="10" alignment="CENTER">
<Button text="Switch to DVDs" onAction="#switchToDvds"/>
<Button text="Logout" onAction="#logout"/>
</HBox>
</VBox>
and its controller:
BookController.java
:
package org.jamesd.examples.library;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
public class BookController {
private final Model model;
@FXML
private Label welcomeLabel;
public BookController(Model model) {
this.model = model;
}
@FXML
private void initialize() {
welcomeLabel.textProperty().bind(
model.userProperty()
.map( u -> String.format("Welcome %s", u.username()))
.orElse("Welcome")
);
}
public void switchToDvds() {
model.setBrowseState(BrowseState.DVD);
}
public void logout(ActionEvent actionEvent) {
model.logout();
}
}
As with the LoginController
, note there is no no-arg constructor, and note it doesn't try to change any UI elements except the ones for which it is directly responsible (the ones defined in Book.fxml
). It just calls methods on the model.
The DVD, for demo purposes, is basically identical:
Dvd.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
spacing="20"
alignment="CENTER"
fx:controller="org.jamesd.examples.library.DvdController">
<padding><Insets topRightBottomLeft="20"/></padding>
<Label fx:id="welcomeLabel"/>
<Label text="Please browse our DVDs..."/>
<HBox spacing="10" alignment="CENTER">
<Button text="Switch to Books" onAction="#switchToBooks"/>
<Button text="Logout" onAction="#logout"/>
</HBox>
</VBox>
DvdController.java
:
package org.jamesd.examples.library;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
public class DvdController {
private final Model model;
@FXML private Label welcomeLabel;
public DvdController(Model model) {
this.model = model;
}
@FXML
private void initialize() {
welcomeLabel.textProperty().bind(
model.userProperty()
.map( u -> String.format("Welcome %s", u.username()))
.orElse("Welcome")
);
}
public void switchToBooks() {
model.setBrowseState(BrowseState.BOOK);
}
public void logout() {
model.logout();
}
}
Now all we need to do is assemble the application. The application class should instantiate the model and scene and tie everything together. I will use one additional class: a ViewManager
class. The responsibility of this class is to observe the model properties and expose a property with the current view that should appear in the scene. This avoids the need for the model (which should just represent data) from having to be aware of any UI details (such as FXML files, scene roots, etc.).
This also uses a controller factory, which is a function the FXMLLoader
can use to create controller objects from their classes. This uses a little reflection to check if the controller class defines a constructor taking a single parameter of type Model
: if so, it uses that constructor. (Otherwise it just uses the default constructor.)
ViewManager.java
.
package org.jamesd.examples.library;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.value.ChangeListener;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.util.Callback;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.net.URL;
public class ViewManager {
private final Model model ;
private final Callback<Class<?>, Object> controllerFactory ;
private final ReadOnlyObjectWrapper<Parent> currentView = new ReadOnlyObjectWrapper<>();
public Parent getCurrentView() {
return currentView.get();
}
public ReadOnlyObjectProperty<Parent> currentViewProperty() {
return currentView.getReadOnlyProperty();
}
public ViewManager(Model model) {
this.model = model;
controllerFactory = type -> {
try {
for (Constructor<?> c : type.getConstructors()) {
if (c.getParameterCount() == 1 && c.getParameterTypes()[0].equals(Model.class)) {
return c.newInstance(model);
}
}
return type.getConstructor().newInstance();
} catch (Exception e) {
throw e instanceof RuntimeException re ? re : new RuntimeException(e);
}
};
ChangeListener<Object> listener = (obs, oldValue, newValue) -> updateView();
model.userProperty().addListener(listener);
model.browseStateProperty().addListener(listener);
updateView();
}
private void updateView() {
try {
URL resource;
if (model.getUser() == null) {
resource = LoginController.class.getResource("Login.fxml");
} else {
resource = switch(model.getBrowseState()) {
case DVD -> DvdController.class.getResource("Dvd.fxml");
case BOOK -> BookController.class.getResource("Book.fxml");
};
}
FXMLLoader loader = new FXMLLoader(resource);
loader.setControllerFactory(controllerFactory);
currentView.set(loader.load());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Now the application class just creates the model, view manager, and scene, and binds the scene's root to the current view in the view manager:
HelloApplication.java
:
package org.jamesd.examples.library;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class HelloApplication extends Application {
@Override
public void start(Stage stage) {
Model model = new Model();
ViewManager viewManager = new ViewManager(model);
Scene scene = new Scene(viewManager.getCurrentView(), 800, 500);
scene.rootProperty().bind(viewManager.currentViewProperty());
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论