From 2b7cec7e6a59f6daf28899f3097a72d5b6090ab8 Mon Sep 17 00:00:00 2001 From: David Guler Date: Tue, 15 Nov 2022 08:40:42 +0100 Subject: [PATCH] refactor: removed MainFXMLController from dependencies Replaced MainFXMLController-based scene-changing with event-based scene-changing to remove cyclic dependency --- .../gartenverwaltung/HelloApplication.java | 10 ++-- .../gartenverwaltung/MainFXMLController.java | 49 +++++++++++-------- .../gartenverwaltung/MyGardenController.java | 11 +++-- .../gartenverwaltung/PlantsController.java | 7 +++ .../bootstrap/AfterInject.java | 4 ++ .../gartenverwaltung/bootstrap/AppLoader.java | 42 +++++++++++++--- .../bootstrap/ChangeViewEvent.java | 19 +++++++ .../gartenverwaltung/bootstrap/Inject.java | 3 ++ .../ch/zhaw/gartenverwaltung/MyGarden.fxml | 2 +- .../ch/zhaw/gartenverwaltung/Plants.fxml | 3 +- 10 files changed, 110 insertions(+), 40 deletions(-) create mode 100644 src/main/java/ch/zhaw/gartenverwaltung/bootstrap/ChangeViewEvent.java diff --git a/src/main/java/ch/zhaw/gartenverwaltung/HelloApplication.java b/src/main/java/ch/zhaw/gartenverwaltung/HelloApplication.java index fd31017..9018a31 100644 --- a/src/main/java/ch/zhaw/gartenverwaltung/HelloApplication.java +++ b/src/main/java/ch/zhaw/gartenverwaltung/HelloApplication.java @@ -1,8 +1,7 @@ package ch.zhaw.gartenverwaltung; +import ch.zhaw.gartenverwaltung.bootstrap.AppLoader; import javafx.application.Application; -import javafx.fxml.FXMLLoader; -import javafx.scene.Scene; import javafx.stage.Stage; import java.io.IOException; @@ -10,10 +9,11 @@ import java.io.IOException; public class HelloApplication extends Application { @Override public void start(Stage stage) throws IOException { - FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("MainFXML.fxml")); - Scene scene = new Scene(fxmlLoader.load()); + AppLoader appLoader = new AppLoader(); + + appLoader.loadSceneToStage("MainFXML.fxml", stage); + stage.setTitle("Gartenverwaltung"); - stage.setScene(scene); stage.show(); } diff --git a/src/main/java/ch/zhaw/gartenverwaltung/MainFXMLController.java b/src/main/java/ch/zhaw/gartenverwaltung/MainFXMLController.java index 9fe278a..fca8b69 100644 --- a/src/main/java/ch/zhaw/gartenverwaltung/MainFXMLController.java +++ b/src/main/java/ch/zhaw/gartenverwaltung/MainFXMLController.java @@ -1,21 +1,25 @@ package ch.zhaw.gartenverwaltung; +import ch.zhaw.gartenverwaltung.bootstrap.AfterInject; 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.Initializable; import javafx.scene.control.Button; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.Pane; import java.io.IOException; -import java.net.URL; -import java.util.ResourceBundle; import java.util.logging.Level; import java.util.logging.Logger; -public class MainFXMLController implements Initializable { +public class MainFXMLController { private static final Logger LOG = Logger.getLogger(MainFXMLController.class.getName()); - private final AppLoader appLoader = new AppLoader(this); + + @Inject + AppLoader appLoader; + @FXML private Button home_button; @@ -31,29 +35,26 @@ public class MainFXMLController implements Initializable { @FXML private Button plants_button; - public MainFXMLController() throws IOException { - } - @FXML - void goToHome() throws IOException { + void goToHome() { showPaneAsMainView("Home.fxml"); styleChangeButton(home_button); } @FXML - void goToMyPlants() throws IOException { + void goToMyPlants() { showPaneAsMainView("MyGarden.fxml"); styleChangeButton(myGarden_button); } @FXML - void goToMySchedule() throws IOException { + void goToMySchedule() { showPaneAsMainView("MySchedule.fxml"); styleChangeButton(mySchedule_button); } @FXML - void goToPlants() throws IOException { + void goToPlants() { showPaneAsMainView("Plants.fxml"); styleChangeButton(plants_button); } @@ -63,15 +64,22 @@ public class MainFXMLController implements Initializable { * set HGrow and VGrow to parent AnchorPane. * Sends MainController to other Controllers. * @param fxmlFile string of fxml file - * @throws IOException exception when file does not exist */ - public void showPaneAsMainView(String fxmlFile) throws IOException { - Pane anchorPane = appLoader.loadPane(fxmlFile); - mainPane.getChildren().setAll(anchorPane); - anchorPane.prefWidthProperty().bind(mainPane.widthProperty()); - anchorPane.prefHeightProperty().bind(mainPane.heightProperty()); + public void showPaneAsMainView(String fxmlFile) { + try { + Pane anchorPane = appLoader.loadPane(fxmlFile); + mainPane.getChildren().setAll(anchorPane); + 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 changeMainViewHandler = (ChangeViewEvent event) -> showPaneAsMainView(event.view()); private void preloadPanes() throws IOException { appLoader.loadAndCacheFxml("MyGarden.fxml"); @@ -87,8 +95,9 @@ public class MainFXMLController implements Initializable { * loads the default FXML File * {@inheritDoc} */ - @Override - public void initialize(URL url, ResourceBundle resourceBundle) { + @AfterInject + @SuppressWarnings("unused") + public void init() { try { preloadPanes(); showPaneAsMainView("Home.fxml"); diff --git a/src/main/java/ch/zhaw/gartenverwaltung/MyGardenController.java b/src/main/java/ch/zhaw/gartenverwaltung/MyGardenController.java index 8f0d3ae..b002e00 100644 --- a/src/main/java/ch/zhaw/gartenverwaltung/MyGardenController.java +++ b/src/main/java/ch/zhaw/gartenverwaltung/MyGardenController.java @@ -2,6 +2,7 @@ package ch.zhaw.gartenverwaltung; import ch.zhaw.gartenverwaltung.bootstrap.AfterInject; import ch.zhaw.gartenverwaltung.bootstrap.AppLoader; +import ch.zhaw.gartenverwaltung.bootstrap.ChangeViewEvent; import ch.zhaw.gartenverwaltung.bootstrap.Inject; import ch.zhaw.gartenverwaltung.io.PlantList; import ch.zhaw.gartenverwaltung.models.Garden; @@ -17,6 +18,7 @@ import javafx.scene.control.Button; import javafx.scene.control.ButtonType; import javafx.scene.control.Label; import javafx.scene.image.ImageView; +import javafx.scene.layout.AnchorPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; @@ -30,16 +32,15 @@ import java.util.logging.Logger; public class MyGardenController { private static final Logger LOG = Logger.getLogger(MyGardenController.class.getName()); - @Inject AppLoader appLoader; @Inject - MainFXMLController mainController; - @Inject private Garden garden; @Inject private PlantList plantList; + @FXML + public AnchorPane myGardenRoot; @FXML private VBox myPlants_vbox; @@ -61,8 +62,8 @@ public class MyGardenController { } @FXML - void addPlant(ActionEvent event) throws IOException { - mainController.showPaneAsMainView("Plants.fxml"); + void addPlant() { + myGardenRoot.fireEvent(new ChangeViewEvent(ChangeViewEvent.CHANGE_MAIN_VIEW, "Plants.fxml")); } private void createPlantView(List crops) throws HardinessZoneNotSetException, IOException { diff --git a/src/main/java/ch/zhaw/gartenverwaltung/PlantsController.java b/src/main/java/ch/zhaw/gartenverwaltung/PlantsController.java index 5a691dd..0b66618 100644 --- a/src/main/java/ch/zhaw/gartenverwaltung/PlantsController.java +++ b/src/main/java/ch/zhaw/gartenverwaltung/PlantsController.java @@ -2,6 +2,7 @@ package ch.zhaw.gartenverwaltung; import ch.zhaw.gartenverwaltung.bootstrap.AfterInject; import ch.zhaw.gartenverwaltung.bootstrap.AppLoader; +import ch.zhaw.gartenverwaltung.bootstrap.ChangeViewEvent; import ch.zhaw.gartenverwaltung.bootstrap.Inject; import ch.zhaw.gartenverwaltung.io.HardinessZoneNotSetException; import ch.zhaw.gartenverwaltung.models.PlantListModel; @@ -16,6 +17,7 @@ import javafx.geometry.Insets; import javafx.scene.control.*; import javafx.scene.image.Image; import javafx.scene.image.ImageView; +import javafx.scene.layout.AnchorPane; import javafx.scene.layout.VBox; import javafx.stage.Modality; import javafx.stage.Stage; @@ -39,6 +41,9 @@ public class PlantsController { // TODO: move to model private final ListProperty plantListProperty = new SimpleListProperty<>(FXCollections.observableArrayList()); + @FXML + public AnchorPane plantsRoot; + @FXML private VBox seasons; @@ -73,6 +78,8 @@ public class PlantsController { stage.initModality(Modality.APPLICATION_MODAL); stage.setResizable(false); stage.showAndWait(); + // TODO: change to dialog + plantsRoot.fireEvent(new ChangeViewEvent(ChangeViewEvent.CHANGE_MAIN_VIEW, "MyGarden.fxml")); } /** diff --git a/src/main/java/ch/zhaw/gartenverwaltung/bootstrap/AfterInject.java b/src/main/java/ch/zhaw/gartenverwaltung/bootstrap/AfterInject.java index c8e5f3f..72a88fd 100644 --- a/src/main/java/ch/zhaw/gartenverwaltung/bootstrap/AfterInject.java +++ b/src/main/java/ch/zhaw/gartenverwaltung/bootstrap/AfterInject.java @@ -5,6 +5,10 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * Annotates a method to be executed after all dependencies annotates with {@link Inject} + * have been injected. + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface AfterInject { } diff --git a/src/main/java/ch/zhaw/gartenverwaltung/bootstrap/AppLoader.java b/src/main/java/ch/zhaw/gartenverwaltung/bootstrap/AppLoader.java index e341258..f55f6d5 100644 --- a/src/main/java/ch/zhaw/gartenverwaltung/bootstrap/AppLoader.java +++ b/src/main/java/ch/zhaw/gartenverwaltung/bootstrap/AppLoader.java @@ -1,7 +1,6 @@ package ch.zhaw.gartenverwaltung.bootstrap; import ch.zhaw.gartenverwaltung.HelloApplication; -import ch.zhaw.gartenverwaltung.MainFXMLController; import ch.zhaw.gartenverwaltung.io.CropList; import ch.zhaw.gartenverwaltung.io.JsonCropList; import ch.zhaw.gartenverwaltung.io.JsonPlantList; @@ -13,7 +12,6 @@ import ch.zhaw.gartenverwaltung.models.GardenSchedule; import ch.zhaw.gartenverwaltung.models.PlantListModel; import javafx.fxml.FXMLLoader; import javafx.scene.Scene; -import javafx.scene.layout.AnchorPane; import javafx.scene.layout.Pane; import javafx.stage.Stage; @@ -39,14 +37,19 @@ public class AppLoader { private final GardenSchedule gardenSchedule = new GardenSchedule(taskList, plantList); private final Garden garden = new Garden(gardenSchedule, cropList); - private final MainFXMLController mainController; - public AppLoader(MainFXMLController mainController) throws IOException { - this.mainController = mainController; + public AppLoader() throws IOException { } + /** + * 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 { Pane pane = panes.get(fxmlFile); if (pane == null) { @@ -56,6 +59,16 @@ public class AppLoader { 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 { FXMLLoader loader = new FXMLLoader(Objects.requireNonNull(HelloApplication.class.getResource(fxmlFile))); Pane root = loader.load(); @@ -65,13 +78,27 @@ public class AppLoader { 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 { FXMLLoader loader = new FXMLLoader(Objects.requireNonNull(HelloApplication.class.getResource(fxmlFile))); - AnchorPane anchorPane = loader.load(); - panes.put(fxmlFile, anchorPane); + Pane pane = loader.load(); + panes.put(fxmlFile, pane); 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) { Arrays.stream(controller.getClass().getDeclaredFields()) .filter(field -> field.isAnnotationPresent(Inject.class)) @@ -104,7 +131,6 @@ public class AppLoader { case "PlantList" -> plantList; case "PlantListModel" -> new PlantListModel(plantList); case "GardenSchedule" -> gardenSchedule; - case "MainFXMLController" -> mainController; case "AppLoader" -> this; default -> null; }; diff --git a/src/main/java/ch/zhaw/gartenverwaltung/bootstrap/ChangeViewEvent.java b/src/main/java/ch/zhaw/gartenverwaltung/bootstrap/ChangeViewEvent.java new file mode 100644 index 0000000..4388ebe --- /dev/null +++ b/src/main/java/ch/zhaw/gartenverwaltung/bootstrap/ChangeViewEvent.java @@ -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 CHANGE_MAIN_VIEW = new EventType<>("CHANGE_MAIN_VIEW"); + + public ChangeViewEvent(EventType eventType, String view) { + super(eventType); + this.view = view; + } + + public String view() { + return view; + } +} diff --git a/src/main/java/ch/zhaw/gartenverwaltung/bootstrap/Inject.java b/src/main/java/ch/zhaw/gartenverwaltung/bootstrap/Inject.java index be044c8..02409d5 100644 --- a/src/main/java/ch/zhaw/gartenverwaltung/bootstrap/Inject.java +++ b/src/main/java/ch/zhaw/gartenverwaltung/bootstrap/Inject.java @@ -5,6 +5,9 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * Annotates a Field to be injected from the application-dependencies + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Inject { } diff --git a/src/main/resources/ch/zhaw/gartenverwaltung/MyGarden.fxml b/src/main/resources/ch/zhaw/gartenverwaltung/MyGarden.fxml index f098b9c..26ecaa7 100644 --- a/src/main/resources/ch/zhaw/gartenverwaltung/MyGarden.fxml +++ b/src/main/resources/ch/zhaw/gartenverwaltung/MyGarden.fxml @@ -7,7 +7,7 @@ - + diff --git a/src/main/resources/ch/zhaw/gartenverwaltung/Plants.fxml b/src/main/resources/ch/zhaw/gartenverwaltung/Plants.fxml index 18af41a..6f959d7 100644 --- a/src/main/resources/ch/zhaw/gartenverwaltung/Plants.fxml +++ b/src/main/resources/ch/zhaw/gartenverwaltung/Plants.fxml @@ -15,7 +15,8 @@ + fx:controller="ch.zhaw.gartenverwaltung.PlantsController" + fx:id="plantsRoot">