refactor: removed MainFXMLController from dependencies

Replaced MainFXMLController-based scene-changing with event-based scene-changing to remove cyclic dependency
This commit is contained in:
David Guler 2022-11-15 08:40:42 +01:00
parent 5ef3f6c587
commit 2b7cec7e6a
10 changed files with 110 additions and 40 deletions

View File

@ -1,8 +1,7 @@
package ch.zhaw.gartenverwaltung; package ch.zhaw.gartenverwaltung;
import ch.zhaw.gartenverwaltung.bootstrap.AppLoader;
import javafx.application.Application; import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage; import javafx.stage.Stage;
import java.io.IOException; import java.io.IOException;
@ -10,10 +9,11 @@ import java.io.IOException;
public class HelloApplication extends Application { public class HelloApplication extends Application {
@Override @Override
public void start(Stage stage) throws IOException { public void start(Stage stage) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("MainFXML.fxml")); AppLoader appLoader = new AppLoader();
Scene scene = new Scene(fxmlLoader.load());
appLoader.loadSceneToStage("MainFXML.fxml", stage);
stage.setTitle("Gartenverwaltung"); stage.setTitle("Gartenverwaltung");
stage.setScene(scene);
stage.show(); stage.show();
} }

View File

@ -1,21 +1,25 @@
package ch.zhaw.gartenverwaltung; package ch.zhaw.gartenverwaltung;
import ch.zhaw.gartenverwaltung.bootstrap.AfterInject;
import ch.zhaw.gartenverwaltung.bootstrap.AppLoader; import ch.zhaw.gartenverwaltung.bootstrap.AppLoader;
import ch.zhaw.gartenverwaltung.bootstrap.ChangeViewEvent;
import ch.zhaw.gartenverwaltung.bootstrap.Inject;
import javafx.event.EventHandler;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.layout.AnchorPane; import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane; import javafx.scene.layout.Pane;
import java.io.IOException; import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
public class MainFXMLController implements Initializable { public class MainFXMLController {
private static final Logger LOG = Logger.getLogger(MainFXMLController.class.getName()); private static final Logger LOG = Logger.getLogger(MainFXMLController.class.getName());
private final AppLoader appLoader = new AppLoader(this);
@Inject
AppLoader appLoader;
@FXML @FXML
private Button home_button; private Button home_button;
@ -31,29 +35,26 @@ public class MainFXMLController implements Initializable {
@FXML @FXML
private Button plants_button; private Button plants_button;
public MainFXMLController() throws IOException {
}
@FXML @FXML
void goToHome() throws IOException { void goToHome() {
showPaneAsMainView("Home.fxml"); showPaneAsMainView("Home.fxml");
styleChangeButton(home_button); styleChangeButton(home_button);
} }
@FXML @FXML
void goToMyPlants() throws IOException { void goToMyPlants() {
showPaneAsMainView("MyGarden.fxml"); showPaneAsMainView("MyGarden.fxml");
styleChangeButton(myGarden_button); styleChangeButton(myGarden_button);
} }
@FXML @FXML
void goToMySchedule() throws IOException { void goToMySchedule() {
showPaneAsMainView("MySchedule.fxml"); showPaneAsMainView("MySchedule.fxml");
styleChangeButton(mySchedule_button); styleChangeButton(mySchedule_button);
} }
@FXML @FXML
void goToPlants() throws IOException { void goToPlants() {
showPaneAsMainView("Plants.fxml"); showPaneAsMainView("Plants.fxml");
styleChangeButton(plants_button); styleChangeButton(plants_button);
} }
@ -63,15 +64,22 @@ public class MainFXMLController implements Initializable {
* set HGrow and VGrow to parent AnchorPane. * set HGrow and VGrow to parent AnchorPane.
* Sends MainController to other Controllers. * Sends MainController to other Controllers.
* @param fxmlFile string of fxml file * @param fxmlFile string of fxml file
* @throws IOException exception when file does not exist
*/ */
public void showPaneAsMainView(String fxmlFile) throws IOException { public void showPaneAsMainView(String fxmlFile) {
Pane anchorPane = appLoader.loadPane(fxmlFile); try {
mainPane.getChildren().setAll(anchorPane); Pane anchorPane = appLoader.loadPane(fxmlFile);
anchorPane.prefWidthProperty().bind(mainPane.widthProperty()); mainPane.getChildren().setAll(anchorPane);
anchorPane.prefHeightProperty().bind(mainPane.heightProperty()); anchorPane.prefWidthProperty().bind(mainPane.widthProperty());
anchorPane.prefHeightProperty().bind(mainPane.heightProperty());
anchorPane.removeEventHandler(ChangeViewEvent.CHANGE_MAIN_VIEW, changeMainViewHandler);
anchorPane.addEventHandler(ChangeViewEvent.CHANGE_MAIN_VIEW, changeMainViewHandler);
} catch (IOException e) {
LOG.log(Level.SEVERE, "Could not load pane.", e);
}
} }
private final EventHandler<ChangeViewEvent> changeMainViewHandler = (ChangeViewEvent event) -> showPaneAsMainView(event.view());
private void preloadPanes() throws IOException { private void preloadPanes() throws IOException {
appLoader.loadAndCacheFxml("MyGarden.fxml"); appLoader.loadAndCacheFxml("MyGarden.fxml");
@ -87,8 +95,9 @@ public class MainFXMLController implements Initializable {
* loads the default FXML File * loads the default FXML File
* {@inheritDoc} * {@inheritDoc}
*/ */
@Override @AfterInject
public void initialize(URL url, ResourceBundle resourceBundle) { @SuppressWarnings("unused")
public void init() {
try { try {
preloadPanes(); preloadPanes();
showPaneAsMainView("Home.fxml"); showPaneAsMainView("Home.fxml");

View File

@ -2,6 +2,7 @@ package ch.zhaw.gartenverwaltung;
import ch.zhaw.gartenverwaltung.bootstrap.AfterInject; import ch.zhaw.gartenverwaltung.bootstrap.AfterInject;
import ch.zhaw.gartenverwaltung.bootstrap.AppLoader; import ch.zhaw.gartenverwaltung.bootstrap.AppLoader;
import ch.zhaw.gartenverwaltung.bootstrap.ChangeViewEvent;
import ch.zhaw.gartenverwaltung.bootstrap.Inject; import ch.zhaw.gartenverwaltung.bootstrap.Inject;
import ch.zhaw.gartenverwaltung.io.PlantList; import ch.zhaw.gartenverwaltung.io.PlantList;
import ch.zhaw.gartenverwaltung.models.Garden; import ch.zhaw.gartenverwaltung.models.Garden;
@ -17,6 +18,7 @@ import javafx.scene.control.Button;
import javafx.scene.control.ButtonType; import javafx.scene.control.ButtonType;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.image.ImageView; import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority; import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
@ -30,16 +32,15 @@ import java.util.logging.Logger;
public class MyGardenController { public class MyGardenController {
private static final Logger LOG = Logger.getLogger(MyGardenController.class.getName()); private static final Logger LOG = Logger.getLogger(MyGardenController.class.getName());
@Inject @Inject
AppLoader appLoader; AppLoader appLoader;
@Inject @Inject
MainFXMLController mainController;
@Inject
private Garden garden; private Garden garden;
@Inject @Inject
private PlantList plantList; private PlantList plantList;
@FXML
public AnchorPane myGardenRoot;
@FXML @FXML
private VBox myPlants_vbox; private VBox myPlants_vbox;
@ -61,8 +62,8 @@ public class MyGardenController {
} }
@FXML @FXML
void addPlant(ActionEvent event) throws IOException { void addPlant() {
mainController.showPaneAsMainView("Plants.fxml"); myGardenRoot.fireEvent(new ChangeViewEvent(ChangeViewEvent.CHANGE_MAIN_VIEW, "Plants.fxml"));
} }
private void createPlantView(List<Crop> crops) throws HardinessZoneNotSetException, IOException { private void createPlantView(List<Crop> crops) throws HardinessZoneNotSetException, IOException {

View File

@ -2,6 +2,7 @@ package ch.zhaw.gartenverwaltung;
import ch.zhaw.gartenverwaltung.bootstrap.AfterInject; import ch.zhaw.gartenverwaltung.bootstrap.AfterInject;
import ch.zhaw.gartenverwaltung.bootstrap.AppLoader; import ch.zhaw.gartenverwaltung.bootstrap.AppLoader;
import ch.zhaw.gartenverwaltung.bootstrap.ChangeViewEvent;
import ch.zhaw.gartenverwaltung.bootstrap.Inject; import ch.zhaw.gartenverwaltung.bootstrap.Inject;
import ch.zhaw.gartenverwaltung.io.HardinessZoneNotSetException; import ch.zhaw.gartenverwaltung.io.HardinessZoneNotSetException;
import ch.zhaw.gartenverwaltung.models.PlantListModel; import ch.zhaw.gartenverwaltung.models.PlantListModel;
@ -16,6 +17,7 @@ import javafx.geometry.Insets;
import javafx.scene.control.*; import javafx.scene.control.*;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import javafx.scene.image.ImageView; import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import javafx.stage.Modality; import javafx.stage.Modality;
import javafx.stage.Stage; import javafx.stage.Stage;
@ -39,6 +41,9 @@ public class PlantsController {
// TODO: move to model // TODO: move to model
private final ListProperty<Plant> plantListProperty = new SimpleListProperty<>(FXCollections.observableArrayList()); private final ListProperty<Plant> plantListProperty = new SimpleListProperty<>(FXCollections.observableArrayList());
@FXML
public AnchorPane plantsRoot;
@FXML @FXML
private VBox seasons; private VBox seasons;
@ -73,6 +78,8 @@ public class PlantsController {
stage.initModality(Modality.APPLICATION_MODAL); stage.initModality(Modality.APPLICATION_MODAL);
stage.setResizable(false); stage.setResizable(false);
stage.showAndWait(); stage.showAndWait();
// TODO: change to dialog
plantsRoot.fireEvent(new ChangeViewEvent(ChangeViewEvent.CHANGE_MAIN_VIEW, "MyGarden.fxml"));
} }
/** /**

View File

@ -5,6 +5,10 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
/**
* Annotates a method to be executed after all dependencies annotates with {@link Inject}
* have been injected.
*/
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD) @Target(ElementType.METHOD)
public @interface AfterInject { } public @interface AfterInject { }

View File

@ -1,7 +1,6 @@
package ch.zhaw.gartenverwaltung.bootstrap; package ch.zhaw.gartenverwaltung.bootstrap;
import ch.zhaw.gartenverwaltung.HelloApplication; import ch.zhaw.gartenverwaltung.HelloApplication;
import ch.zhaw.gartenverwaltung.MainFXMLController;
import ch.zhaw.gartenverwaltung.io.CropList; import ch.zhaw.gartenverwaltung.io.CropList;
import ch.zhaw.gartenverwaltung.io.JsonCropList; import ch.zhaw.gartenverwaltung.io.JsonCropList;
import ch.zhaw.gartenverwaltung.io.JsonPlantList; import ch.zhaw.gartenverwaltung.io.JsonPlantList;
@ -13,7 +12,6 @@ import ch.zhaw.gartenverwaltung.models.GardenSchedule;
import ch.zhaw.gartenverwaltung.models.PlantListModel; import ch.zhaw.gartenverwaltung.models.PlantListModel;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane; import javafx.scene.layout.Pane;
import javafx.stage.Stage; import javafx.stage.Stage;
@ -39,14 +37,19 @@ public class AppLoader {
private final GardenSchedule gardenSchedule = new GardenSchedule(taskList, plantList); private final GardenSchedule gardenSchedule = new GardenSchedule(taskList, plantList);
private final Garden garden = new Garden(gardenSchedule, cropList); private final Garden garden = new Garden(gardenSchedule, cropList);
private final MainFXMLController mainController;
public AppLoader(MainFXMLController mainController) throws IOException { public AppLoader() throws IOException {
this.mainController = mainController;
} }
/**
* Loads and returns a {@link Pane} (cached).
*
* @param fxmlFile The file name to be loaded
* @return The loaded Pane
* @throws IOException if the file could not be loaded
*/
public Pane loadPane(String fxmlFile) throws IOException { public Pane loadPane(String fxmlFile) throws IOException {
Pane pane = panes.get(fxmlFile); Pane pane = panes.get(fxmlFile);
if (pane == null) { if (pane == null) {
@ -56,6 +59,16 @@ public class AppLoader {
return pane; return pane;
} }
/**
* Loads the given fxml-file from resources (no caching) and creates a new {@link Scene},
* which is then appended to the given {@link Stage}.
* Performs dependency-injection.
*
* @param fxmlFile The file name to be loaded
* @param appendee The {@link Stage} to which the new {@link Scene} is appended.
* @return The controller of the loaded scene.
* @throws IOException if the file could not be loaded
*/
public Object loadSceneToStage(String fxmlFile, Stage appendee) throws IOException { public Object loadSceneToStage(String fxmlFile, Stage appendee) throws IOException {
FXMLLoader loader = new FXMLLoader(Objects.requireNonNull(HelloApplication.class.getResource(fxmlFile))); FXMLLoader loader = new FXMLLoader(Objects.requireNonNull(HelloApplication.class.getResource(fxmlFile)));
Pane root = loader.load(); Pane root = loader.load();
@ -65,13 +78,27 @@ public class AppLoader {
return controller; return controller;
} }
/**
* Loads the given fxml-file from resources and caches the pane.
* Performs dependency-injection.
*
* @param fxmlFile The file name to be loaded
* @throws IOException if the file could not be loaded
*/
public void loadAndCacheFxml(String fxmlFile) throws IOException { public void loadAndCacheFxml(String fxmlFile) throws IOException {
FXMLLoader loader = new FXMLLoader(Objects.requireNonNull(HelloApplication.class.getResource(fxmlFile))); FXMLLoader loader = new FXMLLoader(Objects.requireNonNull(HelloApplication.class.getResource(fxmlFile)));
AnchorPane anchorPane = loader.load(); Pane pane = loader.load();
panes.put(fxmlFile, anchorPane); panes.put(fxmlFile, pane);
annotationInject(loader.getController()); annotationInject(loader.getController());
} }
/**
* Injects the applications dependencies into the given object's fields annotated with {@link Inject}.
* Afterwards, all methods on the objects annotated with {@link AfterInject} are executed.
* (Success of the injections is not guaranteed!)
*
* @param controller The class containing the injectable fields
*/
public void annotationInject(Object controller) { public void annotationInject(Object controller) {
Arrays.stream(controller.getClass().getDeclaredFields()) Arrays.stream(controller.getClass().getDeclaredFields())
.filter(field -> field.isAnnotationPresent(Inject.class)) .filter(field -> field.isAnnotationPresent(Inject.class))
@ -104,7 +131,6 @@ public class AppLoader {
case "PlantList" -> plantList; case "PlantList" -> plantList;
case "PlantListModel" -> new PlantListModel(plantList); case "PlantListModel" -> new PlantListModel(plantList);
case "GardenSchedule" -> gardenSchedule; case "GardenSchedule" -> gardenSchedule;
case "MainFXMLController" -> mainController;
case "AppLoader" -> this; case "AppLoader" -> this;
default -> null; default -> null;
}; };

View File

@ -0,0 +1,19 @@
package ch.zhaw.gartenverwaltung.bootstrap;
import javafx.event.Event;
import javafx.event.EventType;
public class ChangeViewEvent extends Event {
private final String view;
public static final EventType<ChangeViewEvent> CHANGE_MAIN_VIEW = new EventType<>("CHANGE_MAIN_VIEW");
public ChangeViewEvent(EventType<? extends Event> eventType, String view) {
super(eventType);
this.view = view;
}
public String view() {
return view;
}
}

View File

@ -5,6 +5,9 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
/**
* Annotates a Field to be injected from the application-dependencies
*/
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD) @Target(ElementType.FIELD)
public @interface Inject { } public @interface Inject { }

View File

@ -7,7 +7,7 @@
<?import javafx.scene.layout.VBox?> <?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?> <?import javafx.scene.text.Font?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="655.0" prefWidth="1175.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ch.zhaw.gartenverwaltung.MyGardenController"> <AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="655.0" prefWidth="1175.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ch.zhaw.gartenverwaltung.MyGardenController" fx:id="myGardenRoot">
<children> <children>
<VBox layoutY="49.0" prefHeight="655.0" prefWidth="1175.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> <VBox layoutY="49.0" prefHeight="655.0" prefWidth="1175.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children> <children>

View File

@ -15,7 +15,8 @@
<AnchorPane maxHeight="-Infinity" maxWidth="1.7976931348623157E308" minHeight="-Infinity" minWidth="1000.0" prefHeight="853.0" <AnchorPane maxHeight="-Infinity" maxWidth="1.7976931348623157E308" minHeight="-Infinity" minWidth="1000.0" prefHeight="853.0"
prefWidth="1219.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" prefWidth="1219.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="ch.zhaw.gartenverwaltung.PlantsController"> fx:controller="ch.zhaw.gartenverwaltung.PlantsController"
fx:id="plantsRoot">
<children> <children>
<SplitPane dividerPositions="0.7377363661277062" layoutX="539.0" layoutY="266.0" prefHeight="853.0" prefWidth="1219.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> <SplitPane dividerPositions="0.7377363661277062" layoutX="539.0" layoutY="266.0" prefHeight="853.0" prefWidth="1219.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<items> <items>