Creating a static class to open a new window in Java FXML - java

I am working on a project where there's a lot of windows being opened and closed and would like to create a static class which only takes in a few parameters and then does the rest.
The problem is that "controller" will need to be different types of declaration, depending on what controller is needed. For instance; FXMLControllerAdd or FXMLControllerHome.
I tried to pass the type to the method with a parameter. That did not work, neither did using var as declaration (it's coded in Java11) because then i got a "cannot find symbol"-error for initData() on the next line.
public static void nySide(Class c, String controllerPath, Dataset dataset, String tittel, Window window) {
try {
FXMLLoader loader = new FXMLLoader(c.getResource(controllerPath));
Parent root = (Parent) loader.load();
//THIS IS WHERE TO PROBLEM IS
FXMLControllerAdd controller = loader.getController();
controller.initData(dataset);
//This line gets the Stage information
Stage st = new Stage();
st.setTitle(tittel);
st.setScene(new Scene(root));
st.show();
Stage stage = (Stage) window;
stage.close();
} catch (Exception e) {
e.printStackTrace();
}
}
Also; does it exists another way which requires less parameters?

I figured it out thanks to Slaw. Making an interface E.g (FXMLInitData) and implementing that in every FXMLController.java and declaring controller as that interface did the trick.
Interface:
public interface FXMLInitData {
public void initData(Dataset dataset);
}
Method:
public static void nySide(Class c, String controllerPath, Dataset dataset, String tittel, Window window){
try {
FXMLLoader loader = new FXMLLoader(c.getResource(controllerPath));
Parent root = (Parent) loader.load();
FXMLInitData controller = loader.getController();
controller.initData(dataset);
//This line gets the Stage information
Stage st = new Stage();
st.setTitle(tittel);
st.setScene(new Scene(root));
st.show();
Stage stage = (Stage) window;
stage.close();
} catch (Exception e) {
e.printStackTrace();
}
}
Class:
public class FXMLControllerHome implements Initializable, FXMLInitData{
#Override
public void initData(Dataset dataset){
}
}

Try letting every controller extend or implement a parent Controller-class. Make the parent Controller a parameter, and pass the child-controller as a parameter when calling the method instead of String controllerPath.

Related

javafx scene builder controller unit test

Good day. I am a new learner of Java and I am currently stucking in performing a unit test for javafx scene builder controller.
I have an application class,
public class WebScraperApplication extends Application{
#Override
public void start(Stage stage) throws Exception{
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("/ui.fxml"));
VBox root = (VBox) loader.load();
Scene scene = new Scene(root);
stage.setScene(scene);
stage.setTitle("WebScraper");
stage.show();
}
public static void main(String args[]){
Application.launch(args);
}
}
ui.fxml stores the GUI information and Controller class is the javafx scene builder controller,
public class Controller{
#FXML
//variable and constructor
...
#FXML
//function
public int size(List<Object> x){
return x.size();
}
...
}
I have no idea to create a unit test to test the Controller class since I cannot create an object of it and implement the function inside.
You can take a look at TestFX. It's a testing framework for Java FX

JavaFX - Objects stuck in the memory

I have a huge application built using JavaFX,, as there are a lot of stages to show i created a class to load views [more like utils class] called FXMLManager(i think the problem is there),, To show a single stage i should create an instance of FXMLManager,, after the stage is closed FXMLManager instances remains in the memory,,, i tried setting the object to null after the stage is closed with no luck..
FXMLManager.java:
public class FXMLManager<T extends AbstractController> {
/**
*
*/
private static ClassLoader cachingClassLoader = new FXMLlightweightLoader(FXMLLoader.getDefaultClassLoader());
private final Class<T> clazzType;
#Getter private T controller;
private FXMLLoader fxmlLoader;
#Getter private Parent root;
public FXMLManager(Class<T> clazz) {
this.clazzType = clazz;
Annotation annotation = this.clazzType.getAnnotation(FXMLController.class);
if (annotation == null)
throw new NullPointerException(
"controller type provided is not annotated with FXMLController : " + clazzType);
FXMLController fxmlController = (FXMLController) annotation;
this.fxmlLoader = constructFXMLLoader();
this.fxmlLoader.setLocation(getURL(fxmlController.value()));
try {
AbstractController abstractController = (AbstractController) clazzType.newInstance();
this.fxmlLoader.setController(abstractController);
this.root = (Parent) this.fxmlLoader.load();
} catch (Exception e) {
e.printStackTrace();
}
this.controller = this.fxmlLoader.getController();
}
private FXMLLoader constructFXMLLoader() {
FXMLLoader fxmlLoader = new FXMLLoader();
fxmlLoader.setClassLoader(cachingClassLoader);
fxmlLoader.setResources(I18NSupport.getBundle());
return fxmlLoader;
}
public T getController(Class<T> controllerType) {
T controller = controllerType.cast(this.fxmlLoader.getController());
return controller;
}
public static URL getURL(String url) {
return FXMLManager.class.getResource(url);
}
private Stage stage;
public Stage getStage(String title) {
if (stage == null) {
Scene scene = new Scene(this.root);
stage = new Stage();
stage.initModality(Modality.APPLICATION_MODAL);
stage.setResizable(false);
stage.setScene(scene);
stage.getIcons().add(ImageLoader.loadImage("icon"));
}
stage.setTitle(title);
return stage;
}
}
i am using FXMLManager as follows:
public static void openRandomForm() {
FXMLManager<Controller> fxmlManager = new FXMLManager<>(Controller.class);
//fxmlManager.getController(); do stuff with the controller
Stage stage = fxmlManager.getStage("title");
stage.showAndWait();
fxmlManager = null;
}
Am i doing something wrong? as far as i know FXMLManager should be cleared,, no?
Screen shots of heap dump after showing couple of stages then performing GC:

How to pass an object between windows in JavaFX [duplicate]

This question already has answers here:
Passing Parameters JavaFX FXML
(10 answers)
Closed 6 years ago.
I am new to java and need help understanding objects. I'm attempting to make an application in javaFx but having trouble to pass objects between the windows. Currently, there is a login window, if the login details are correct via checking the database, the dashboard window opens. However, I now need the BackendInterface object I just created when pressing the login button in the dashboard window to call its methods from the BackendInterface class. What I have now gives a null pointer exception.
Adding a constructor to the LoginController class which passes this.backendInterface yields a fxml load exception
So my question is how do you pass an instantiated object to another window to use it's methods?
Main
public class MainGui extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("login.fxml"));
primaryStage.setTitle("Login");
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
LoginController for login button
public class LoginController {
BackendInterface backendInterface;
#FXML
TextField username;
#FXML
PasswordField password;
#FXML
Button loginButton;
#FXML
Label loginLabel;
#FXML
public void loginButtonPress(){
if (username.getText().isEmpty() == true || password.getText().isEmpty() == true ) {
loginLabel.setText("Please enter data in the fields below");
} else {
//initialises backend interface with username and password
backendInterface = new BackendInterface(username.getText(), password.getText().toCharArray());
//opens a connection to the database
backendInterface.openConnection();
if (backendInterface.getConnectionResponse() == "success"){
//return and print response
System.out.println(backendInterface.getConnectionResponse());
//directs the user to the dashboard after successful login
try{
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("dashboard.fxml"));
Parent root1 = (Parent) fxmlLoader.load();
Stage stage = new Stage();
stage.initModality(Modality.APPLICATION_MODAL);
stage.setTitle("Welcome " + username.getText());
stage.setScene(new Scene(root1));
stage.show();
//Closes the login screen window
Stage stage2 = (Stage) loginButton.getScene().getWindow();
stage2.hide();
} catch (Exception e){
e.printStackTrace();
}
} else {
//warns user of invalid login
loginLabel.setText(backendInterface.getConnectionResponse());
}
}
}
}
DashboardController for button on dashboard
public class DashboardController {
#FXML
public void doStuff(){
LoginController loginController = new LoginController();
loginController.backendInterface.printSomething();
}
}
UPDATED SOLUTION based on Clayns response:
First Controller load new page and pass object
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("dashboard.fxml"));
loader.load();
Parent p = loader.getRoot();
Stage stage = new Stage();
stage.setScene(new Scene(p));
DashboardController dashboardController = loader.getController();
dashboardController.setBackendInterface(backendInterface);
Second Controller retrieve object method
public void setBackendInterface(BackendInterface backendInterface) {
this.backendInterface = backendInterface;
}
You are creating a new LoginControllerin your DashboardController that has nothing to do with the one that was used for the login.
You should give your DashboardController a method to set the BackendInterface and than in the LoginController use fxmlLoader.getController() to get the DashboardController and pass the BackendInterface to it.
Edit:
The answer from fabian works the same way

JavaFX - How to set values during in fxml controller initialize

I tried to load an FXML and set the controller with Java code (not with the FXML tag). I have different fields in the FXML. I tried to load (for example textfields, buttons...).
Here is the example:
Tab tab = new Tab();
tab.setText("TesetTabAdd");
tabpane.getTabs().add(tab);
FXMLLoader loader = new FXMLLoader(getClass().getResource("tab.fxml"));
TabController tabCont = new TabController();
tabCont.setName("Sandro");
loader.setController(tabCont);
try {
tab.setContent((Node)loader.load(getClass().getResource("tab.fxml")));
} catch (IOException ex) {
System.out.println(ex);
}
As you can see I create a new Tab, set the text for it, add it to the tabpane, load the fxml and then I create a new controller and set it as controller for the FXML. After this, I tried to set a value in the fxml before initialize so that I can use it in my controller to update a textfield or button.
Here is my controller, I tried to set:
public class TabController implements Initializable {
#FXML private TextField name;
private final StringProperty nameProp = new SimpleStringProperty();
public String getNameProp() {
return nameProp.get();
}
public void setNameProp(String value) {
nameProp.set(value);
}
public StringProperty namePropProperty() {
return nameProp;
}
public void setName(String name){
nameProp.setValue(name);
}
public String getName(){
return nameProp.get();
}
#Override
public void initialize(URL url, ResourceBundle rb) {
name.textProperty().bind(nameProp);
}
}
I tried it with binding, but it doesn't work.
I edit my createTab() method a littel bit. Here i set the controller and then use the setName method. But i the textfield dont display anything. The System.out.println(tabCont.getName()); method prints out "Sandro"!!!
public void createTab(){
try{
Tab tab = new Tab();
tab.setText("TesetTabAdd");
tabpane.getTabs().add(tab);
FXMLLoader loader = new FXMLLoader();
TabController tabCont = new TabController();
loader.setController(tabCont);
tabCont.setName("Sandro");
tab.setContent((Node)loader.load(getClass().getResource("tab.fxml")));
System.out.println(tabCont.getName());
} catch (IOException ex) {
System.out.println(ex);
}
}
1) the FXML file is not loaded until the load() method is invoked.
(as per your comment "... load the fxml and then I create a new controller ..."). So just initiating the FXMLLoader will not load the given fxml file.
2) You are invoking a wrong load method. You should use the load method of instantiated FXMLLoader. But you used static load method of the FXMLLoader class. This static version will ignore the controller class set through setController(). Try:
FXMLLoader loader = new FXMLLoader(getClass().getResource("tab.fxml"));
TabController tabCont = new TabController();
tabCont.setName("Sandro");
loader.setController(tabCont);
try {
tab.setContent((Node) loader.load());
} catch (IOException ex) {
System.out.println(ex);
}

Set Reference Application from Controller?

New to JAVAFX so this maybe a simple fix, but I have controllers in my application setup using FXML files. I reference the controller to use via the FXML file and to load the file i use the following code in my Application class
private void replaceScene(String resource) {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource(resource));
Pane screen = (Pane) loader.load();
Scene scene = new Scene(screen);
scene.getStylesheets().addAll(getClass().getResource("/css/application.css").toExternalForm());
stage.setScene(scene);
stage.sizeToScene();
IControlledScreen controller = (IControlledScreen) loader.getController();
controller.setApp(this);
} catch (Exception e) {
System.out.println("Cannot load resource " + resource);
System.out.println(e.getMessage());
}
}
And here is a basic controller
public class MyController implements IControlledScreen {
MyApplication app;
public void setApp(MyApplication application) {
app = application;
}
#FXML
public Button btnStart;
// Initialises the controller class.
#FXML
protected void initialize() {
btnStart.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent arg0) {
// code here
}
});
}
}
I have also got an interface called IControlledScreen to set the reference to the application
public interface IControlledScreen {
// ALlows us a reference to the application
public void setApp(MyApplication app);
}
Now this all works fine, until i try to access the app variable during the initialize event. So changing the above controller to this now breaks, because app = NULL.
public class MyController implements IControlledScreen {
MyApplication app;
public void setApp(MyApplication application) {
app = application;
}
#FXML
public Button btnStart;
// Initialises the controller class.
#FXML
protected void initialize() {
// HERE app = NULL
app.GetSomeProperty = "";
}
}
How can i get round this?
Well I think you have to change your design.
The initialize method is called during FXMLLoader.load()
So the call stack would be something like
..replaceScene
..loader.load
....MyController.initialize()
..loader.getController
..controller.setApp(app)
If you really have to access the application from inside your controller you would need to make it a singleton.

Categories