From c34648629911e5838831496abe1785029cdc610c Mon Sep 17 00:00:00 2001 From: "github-classroom[bot]" <66690702+github-classroom[bot]@users.noreply.github.com> Date: Thu, 17 Mar 2022 08:42:05 +0000 Subject: [PATCH] Initial commit --- .editorconfig | 29 + .gitattributes | 5 + .gitignore | 70 + README.adoc | 345 + code/Calculator/build.gradle | 45 + .../java/ch/zhaw/prog2/calculator/Main.java | 60 + .../zhaw/prog2/calculator/ValueHandler.java | 139 + code/FXML-Calculator/build.gradle | 53 + .../ch/zhaw/prog2/fxmlcalculator/Main.java | 32 + .../fxmlcalculator/MainWindowController.java | 22 + .../ResultWindowController.java | 25 + .../prog2/fxmlcalculator/ValueHandler.java | 139 + .../zhaw/prog2/fxmlcalculator/MainWindow.fxml | 8 + .../prog2/fxmlcalculator/ResultWindow.fxml | 49 + code/FXML-WordCloud/build.gradle | 53 + .../zhaw/prog2/application/IsObservable.java | 18 + .../ch/zhaw/prog2/application/IsObserver.java | 13 + .../ch/zhaw/prog2/application/WordModel.java | 36 + .../prog2/application/WordModelDecorator.java | 45 + .../zhaw/prog2/fxmlcalculator/MainWindow.fxml | 8 + gradle.properties | 7 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59821 bytes gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 234 + gradlew.bat | 89 + images/AnzeigenHilfeBlau.png | Bin 0 -> 106149 bytes images/FehlenderEintragRot.png | Bin 0 -> 80367 bytes images/KorrekteAusfuehrungGruen.png | Bin 0 -> 104576 bytes images/MenuLoeschen.png | Bin 0 -> 56684 bytes images/PROG2-300x300.png | Bin 0 -> 123565 bytes images/VorgabeApp_Gross.png | Bin 0 -> 78694 bytes images/VorgabeApp_Klein.png | Bin 0 -> 58159 bytes images/VorgabeApp_Schmal.png | Bin 0 -> 65563 bytes settings.gradle | 45 + solutions-exercises/Calculator/build.gradle | 53 + .../java/ch/zhaw/prog2/calculator/Main.java | 242 + .../zhaw/prog2/calculator/ValueHandler.java | 139 + .../FXML-WordCloud/build.gradle | 53 + .../java/ch/zhaw/prog2/application/App.java | 12 + .../zhaw/prog2/application/IsObservable.java | 18 + .../ch/zhaw/prog2/application/IsObserver.java | 13 + .../ch/zhaw/prog2/application/MainWindow.java | 39 + .../application/MainWindowController.java | 50 + .../ch/zhaw/prog2/application/WordModel.java | 36 + .../prog2/application/WordModelDecorator.java | 45 + .../ch/zhaw/prog2/application/MainWindow.fxml | 31 + .../images/LayoutBorderPaneGridPane.png | Bin 0 -> 83097 bytes .../images/LayoutNurVBoxHBox.png | Bin 0 -> 66699 bytes solutions-exercises/images/PROG2-300x300.png | Bin 0 -> 123565 bytes solutions-exercises/solutions-exercises.adoc | 216 + solutions-exercises/solutions-exercises.pdf | 6796 +++++++++++++++++ 51 files changed, 9317 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 README.adoc create mode 100644 code/Calculator/build.gradle create mode 100644 code/Calculator/src/main/java/ch/zhaw/prog2/calculator/Main.java create mode 100644 code/Calculator/src/main/java/ch/zhaw/prog2/calculator/ValueHandler.java create mode 100644 code/FXML-Calculator/build.gradle create mode 100644 code/FXML-Calculator/src/main/java/ch/zhaw/prog2/fxmlcalculator/Main.java create mode 100644 code/FXML-Calculator/src/main/java/ch/zhaw/prog2/fxmlcalculator/MainWindowController.java create mode 100644 code/FXML-Calculator/src/main/java/ch/zhaw/prog2/fxmlcalculator/ResultWindowController.java create mode 100644 code/FXML-Calculator/src/main/java/ch/zhaw/prog2/fxmlcalculator/ValueHandler.java create mode 100644 code/FXML-Calculator/src/main/resources/ch/zhaw/prog2/fxmlcalculator/MainWindow.fxml create mode 100644 code/FXML-Calculator/src/main/resources/ch/zhaw/prog2/fxmlcalculator/ResultWindow.fxml create mode 100644 code/FXML-WordCloud/build.gradle create mode 100644 code/FXML-WordCloud/src/main/java/ch/zhaw/prog2/application/IsObservable.java create mode 100644 code/FXML-WordCloud/src/main/java/ch/zhaw/prog2/application/IsObserver.java create mode 100644 code/FXML-WordCloud/src/main/java/ch/zhaw/prog2/application/WordModel.java create mode 100644 code/FXML-WordCloud/src/main/java/ch/zhaw/prog2/application/WordModelDecorator.java create mode 100644 code/FXML-WordCloud/src/main/resources/ch/zhaw/prog2/fxmlcalculator/MainWindow.fxml create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 images/AnzeigenHilfeBlau.png create mode 100644 images/FehlenderEintragRot.png create mode 100644 images/KorrekteAusfuehrungGruen.png create mode 100644 images/MenuLoeschen.png create mode 100644 images/PROG2-300x300.png create mode 100644 images/VorgabeApp_Gross.png create mode 100644 images/VorgabeApp_Klein.png create mode 100644 images/VorgabeApp_Schmal.png create mode 100644 settings.gradle create mode 100644 solutions-exercises/Calculator/build.gradle create mode 100644 solutions-exercises/Calculator/src/main/java/ch/zhaw/prog2/calculator/Main.java create mode 100644 solutions-exercises/Calculator/src/main/java/ch/zhaw/prog2/calculator/ValueHandler.java create mode 100644 solutions-exercises/FXML-WordCloud/build.gradle create mode 100644 solutions-exercises/FXML-WordCloud/src/main/java/ch/zhaw/prog2/application/App.java create mode 100644 solutions-exercises/FXML-WordCloud/src/main/java/ch/zhaw/prog2/application/IsObservable.java create mode 100644 solutions-exercises/FXML-WordCloud/src/main/java/ch/zhaw/prog2/application/IsObserver.java create mode 100644 solutions-exercises/FXML-WordCloud/src/main/java/ch/zhaw/prog2/application/MainWindow.java create mode 100644 solutions-exercises/FXML-WordCloud/src/main/java/ch/zhaw/prog2/application/MainWindowController.java create mode 100644 solutions-exercises/FXML-WordCloud/src/main/java/ch/zhaw/prog2/application/WordModel.java create mode 100644 solutions-exercises/FXML-WordCloud/src/main/java/ch/zhaw/prog2/application/WordModelDecorator.java create mode 100644 solutions-exercises/FXML-WordCloud/src/main/resources/ch/zhaw/prog2/application/MainWindow.fxml create mode 100644 solutions-exercises/images/LayoutBorderPaneGridPane.png create mode 100644 solutions-exercises/images/LayoutNurVBoxHBox.png create mode 100644 solutions-exercises/images/PROG2-300x300.png create mode 100644 solutions-exercises/solutions-exercises.adoc create mode 100644 solutions-exercises/solutions-exercises.pdf diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..51220d5 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,29 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Default formatting Unix-style newlines with a newline ending every file +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 4 + +# do not trim trailing whitespace in markdown files +[*.md] +trim_trailing_whitespace = false + +# explicit 4 space indentation +[*.py] +indent_size = 4 + +# explicit 2 space indentation +[*.{json, yml, yaml, xml, ddl, sql}] +indent_size = 2 + +# windows specific files +[*.{bat, cmd}] +end_of_line = crlf diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..022b841 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# These are explicitly windows files and should use crlf +*.bat text eol=crlf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9e7c379 --- /dev/null +++ b/.gitignore @@ -0,0 +1,70 @@ +# InelliJ IDEA files +*.iml +*.ipr +*.ids +*.iws +.idea/ + +# Eclipse files +.project +.metadata +.classpath +.settings/ +.loadpath +bin/ + +# Netbeans +nbactions.xml + +# Visual Studio Code +.vscode + +# Maven +target/ + +# gradle files +.gradle +build/ + +# ignore logfiles +*.log* + +# OS dependant files +.DS_Store +.Spotlight-V100 +.Trashes +Thumbs.db +Desktop.ini +*~ +# Thumbnails +._* + +# compiled files +*.com +*.class +*.dll +*.exe +*.o +*.so + +# packages +*.7z +#*.jar +*.rar +*.zip +*.gz +*.bzip +*.xz +*.lzma +*~$* + +# package managment formats +*.dmg +*.xpi +*.gem +*.egg +*.deb +*.rpm + +# databases +*.sqlite diff --git a/README.adoc b/README.adoc new file mode 100644 index 0000000..46df0ab --- /dev/null +++ b/README.adoc @@ -0,0 +1,345 @@ +:source-highlighter: coderay +:icons: font +:experimental: +:!sectnums: +:imagesdir: ./images/ +:handout: ./code/ + +:logo: IT.PROG2 - +ifdef::backend-html5[] +:logo: image:PROG2-300x300.png[IT.PROG2,100,100,role=right,fit=none,position=top right] +endif::[] +ifdef::backend-pdf[] +:logo: +endif::[] +ifdef::env-github[] +:tip-caption: :bulb: +:note-caption: :information_source: +:important-caption: :heavy_exclamation_mark: +:caution-caption: :fire: +:warning-caption: :warning: +endif::[] + +// references +:url-openjfx: https://openjfx.io/ +:url-openjfx-gradle: {url-openjfx}/openjfx-docs/#gradle +:url-openjfx-javadoc: {url-openjfx}/javadoc/17/index.html +:url-scene-builder: https://gluonhq.com/products/scene-builder +:url-openjfx-gradle-plugin: https://plugins.gradle.org/plugin/org.openjfx.javafxplugin + +// images +:AppGross: image:VorgabeApp_Gross.png[APPGROSS,300,role=left,fit=none,position=top left] +:AppKlein: image:VorgabeApp_Klein.png[APPKLEIN,220,role=left,fit=none,position=top left] +:AppSchmal: image:VorgabeApp_Schmal.png[APPSCHMAL,220,role=left,fit=none,position=top left] +:AppHilfe: image:AnzeigenHilfeBlau.png[APPHILFE,220,role=top,fit=none,position=top left] +:AppFehlend: image:FehlenderEintragRot.png[APPFEHLEND,220,role=top,fit=none,position=top left] +:AppKorrekt: image:KorrekteAusfuehrungGruen.png[APPKORREKT,220,role=top,fit=none,position=top left] +:MenuLoeschen: image:MenuLoeschen.png[MENULOESCHEN,175,role=top,fit=none,position=top left] + += {logo} Praktikum GUI + +== Einleitung + +_Das folgende Praktikum ist in zwei Teile unterteilt. + +Im ersten Teil ergänzen Sie das Resultat des Workshops um ein Model. + +Im zweiten Teil wird eine Applikation erstellt. Diese Applikation stellt keine Ansprüche an ein in der Praxis taugliches Tool. Bestimmt gibt es bessere Tools, die in den entsprechenden Bereichen eingesetzt werden. + +Es geht darum, dass Sie die in der Vorlesung vermittelten Inhalte ausprobieren und umsetzten können._ + +=== Ziele + +* Sie können ein einfaches JavaFX GUI nach Vorgabe erstellen. +* Sie setzen die zur Verfügung stehenden Container (Panes) korrekt ein. +* Sie finden die entsprechenden Einstellungen in der Doku. +* Sie testen verschiedene Möglichkeiten für die Darstellung und das Event-Handling aus. +* Sie wenden das Model-View-Control Pattern korrekt an. +* Sie können ein GUI mit dem link:{url-scene-builder}[JavaFX Scene Builder] erstellen und FXML Dateien in die Anwendung integrieren. + +=== Voraussetzungen + +* Vorlesung: GUI Foundations, GUI Toolbox + +=== Tooling + +* Installiertes JDK 17+ +* Gradle 7.3+ +* JavaFX Scene Builder + +Der link:{url-scene-builder}[JavaFX Scene Builder] wird im zweiten Teil des Praktikums verwendet. Hinweise zur Installation und finden sie dort. + +=== Struktur + +Ein Praktikum kann verschiedene Arten von Aufgaben enthalten, die wie folgt gekennzeichnet sind: + +[TU] – Theoretische Übung:: +Dient der Repetition bzw. Vertiefung des Stoffes aus der Vorlesung und als Vorbereitung für die nachfolgenden Übungen. + +[PU] – Praktische Übung:: +Übungsaufgaben zur praktischen Vertiefung von Teilaspekten des behandelten Themas. + +[PA] – Pflichtaufgabe:: +Übergreifende Aufgabe zum Abschluss. Das Lösen dieser Aufgaben ist Pflicht. Sie muss bis zum definierten Zeitpunkt abgegeben werden, wird bewertet und ist Teil der Vornote. + +=== Zeit und Bewertung + +Für dieses Praktikum stehen 2 Wochen in den Praktikumslektionen und im Selbststudium zur Verfügung. + +Je nach Kenntniss- und Erfahrungsstufe benötigen Sie mehr oder weniger Zeit. +Nutzen Sie die Gelegenheit den Stoff zu vertiefen, Auszuprobieren, Fragen zu stellen und Lösungen zu diskutieren (Intensive-Track). + +Falls Sie das Thema schon beherrschen, müssen Sie nur die Pflichtaufgaben lösen und bis zum angegebenen Zeitpunkt abgeben (Fast-Track). + +Die Pflichtaufgabe wird mit 0 bis 2 Punkten bewertet (siehe _Leistungsnachweise_ auf Moodle). + +=== Referenzen + +* link:{handout}[Praktikumsverzeichnis – Quellcode, Projektstruktur] +* link:{url-openjfx}[OpenJFX Webseite] +* link:{url-openjfx-gradle}[OpenJFX Gradle Konfiguration] +* link:{url-openjfx-gradle-plugin}[OpenJFX Gradle Plugin] +* link:{url-openjfx-javadoc}[JavaFX 17 Javadoc Dokumentation] +* link:{url-scene-builder}[JavaFX Scene Builder Website] + +:sectnums: +:sectnumlevels: 2 +// Beginn des Aufgabenblocks + +== Erweitern des Workshops um ein Model [PU] + +Setzen Sie aufbauend auf der Applikation aus dem Workshop eine Modellklasse (Model) ein und verbinden Sie dieses gemäss den Vorgaben von MVC mit der Applikation. + +=== Ziel + +Die Applikation WordCloud soll, beim Eingeben von Texten, diese einzelnen Wörter nicht mehr direkt in der History anzeigen. + +* In der View wird ein Button geklickt. +** Die Aktion am Controller setzt nicht mehr die View direkt, sondern ändert nur noch das Model. +* Gemäss MVC darf das Model nicht auf die View zugreifen: +** Wir brauchen eine Möglichkeit, den Controller zu informieren, wenn er den Inhalt der View auf den neuesten Stand bringen soll. + +=== Basis + +* Verwenden Sie als Model die im link:{handout}[Praktikumsverzeichnis] zur Verfügung gestellten Klassen `WordModel`, `WordModelDecorator` sowie die beiden Interfaces `IsObservable` und `IsObserver` aus dem Projekt FXML-WordCloud. + +=== Aufgabenstellung + +* Öffnen Sie das mit dem Workshop erstellte Projekt (oder arbeiten Sie den Workshop durch, um das Projekt zu erhalten). +* Ergänzen Sie das Projekt um die Model Klasse `WordModel`, den Decorator `WordModelDecorator` und die beiden Interfaces. +* Studieren Sie die zur Verfügung gestellten Klassen und Interfaces. *Sie sollen nicht geändert werden!* +** `WordModel` enthält keine Hinweise auf das verwendete UI. +** `WordModelDecorator` fügt die Möglichkeit zum Beobachten eines `WordModel` Objekts hinzu (Beobachter hinzufügen und entfernen). +** Überlegen Sie sich, wie hier das Observer-Pattern umgesetzt wurde. +* Ändern Sie den Controller so ab, dass die Vorgaben im Ziel erreicht sind. + +[NOTE] +Um die einzelnen Worte ins WordModel zu schreiben, müssen Sie die Methode `addWord(String word)` für jedes eingegebene Wort einzeln aufrufen. Am einfachsten nehmen Sie die Worte in Kleinbuchstaben. + + +== Die Übungsapplikation – ROI Calculator + +Die Aufgaben in diesem Teil basieren auf der nachfolgend beschriebenen Übungsapplikation, die sie auf verschiedene Arten umsetzen. + +=== Kurzbeschrieb + +Der Capital Assets Calculator soll auf Basis von 4 Eingabewerten das voraussichtliche Kontovermögen für die kommenden Jahre berechnen. Die Eingabewerte sind: + +* Kontovermögen beim Start (Initial amount) +* Jahreszins, der erwartet wird (Return in %) +* Kontoverwaltungskosten pro Jahr (Annual cost) +* Anzahl der Jahre, die berechnet werden sollen (Number of years) + +Die daraus errechnete Vermögensentwicklung (pro Jahr eine Zeile) soll im Resultatbereich ausgegeben werden. + +* Die Applikation soll die Eingabewerte prüfen und im Falle von ungültigen Werten eine Fehlermeldung im Resultatbereich ausgeben. +* Die Applikation soll ein Menü zur Verfügung stellen, welches ermöglicht: +** die eingegebenen Werte zu löschen +** den Resultatbereich zu löschen +** einen Hilfetext im Resultatbereich anzuzeigen. + + +=== Anforderungen an die Applikation + +NOTE: Bitte beachten Sie die weiter unten folgenden Bilder der Applikation in den verschiedenen Zuständen. + +* Nach dem Start der Anwendung wird das Hauptfenster angezeigt, in welchem der Benutzer folgende Aktionen auführen kann: +** Die 4 Werte eingeben. +** Mit btn:[Calculate] die Berechnung starten +** Nach der Berechnung wird das Resultat im Resultatbereich ausgegeben +** Der Rahmen des Resultatbereichs wird grün, wenn alles i.O. war +** Ist ein Eingabewert falsch oder unvollständig, so wird eine Meldung ausgegeben (roter Rahmen) +*** Initial amount: > 0 +*** Return in %: Darf nicht leer sein +*** Annual cost: > 0 und darf nicht leer sein +*** Number of years: >0 und < 99, nur ganze Zahlen +** Das Menü `menu:Clear[]` enthält 6 Einträge: +*** Je einen Eintrag pro Eingabewert (diese können gewählt oder abgewählt werden), z.B. `menu:Clear[✓ Initial amount]` +*** Einen Eintrag `menu:Clear[Clear values]`, der alle Eingabewerte, die im Menu gewählt sind, wieder auf leer setzt +*** Einen Eintrag `menu:Clear[Clear results]`, der den Resultatbereich wieder zurücksetzt. +** Das Menü `menu:?[]` enthält einen Eintrag: +*** `menu:?[Show help]` zeigt einen Hilfetext im Resultatfenster an (blauer Rahmen). +*** Dies soll auch über die Taste kbd:[F1] ausgelöst werden können. +** Mit btn:[Close] wird die Applikation geschlossen. + +TIP: Berechnen Sie den Wert des Vermögens für jedes Jahr mit: `Amount = Amount * (100% + ReturnIn%) - AnnualCost` oder verwenden Sie die bereitgestellte Klasse `ValueHandler`. + +==== Bilder als Vorlage + +[.float-group] +-- +{AppKorrekt} {AppFehlend} {AppHilfe} {MenuLoeschen} +-- + + +// Beginn des Aufgabenblocks + +=== Überlegungen zum Einsatz von Containern (Panes) + +In der Vorlesung wurden verschiedene Container vorgestellt, die Controls oder weitere Container enthalten können. +Die Kombination dieser Container trägt massgeblich zum Verhalten und zur Gestaltung eine Applikation bei. Vorgestellt wurden z.B.: + +**** +[horizontal] +GridPane:: Ordnet die Inhalte in Zeilen und Spalten an +HBox:: Ordnet die Inhalte in einer Zeile +VBox:: Ordnet die Inhalte in einer Spalte +BorderPane:: Ordnet die Inhalte in 5 Bereichen: Left, Right, Top, Bottom und Center +...:: weitere können Sie im Manual oder im Unterrichtsstoff nachschlagen... +**** + +Für die Benutzeroberfläche der zu erstellende Applikation sind die folgenden Anforderungen bekannt: + +[cols="50,~",frame=none,grid=none] +|=== +| {AppGross} +| Der Resultatbereich füllt den Platz des Fensters aus. + +Die Eingabefelder und die Beschriftungen sind links oben angeordnet und bleiben in der Grösse konstant. + +Das Menü ist oben-links angeordnet, die Buttons bleiben zentriert. +|=== + +[cols="40,~",frame=none,grid=none] +|=== +| {AppKlein} +| Beim Verkleinern des Fensters verkleinert sich der Resultatbereich. + +Die Breite des Fensters soll limitiert werden, so dass die Beschriftungen lesbar bleiben. +|=== + +[cols="40,~",frame=none, grid=none] +|=== +| {AppSchmal} +| Wird die Höhe des Fensters vergrössert, wächst der Resultatbereich mit. +|=== + +== Erstellen des ROI-Calculators mit Object-SceneGraph + +=== Planung des Layouts [TU] + +[loweralpha] +. Überlegen Sie sich, welche Container sie verwenden möchten, um die Anforderungen zu erfüllen +. Zeichnen Sie mindestens zwei Möglichkeiten auf +. Beschreiben Sie die Vor- und Nachteile, die sich aus Ihren Überlegungen ergeben könnten + +TIP: Beachten Sie vor allem die Positionierung, die Abstände und die Ausrichtung der Controls. Vergessen Sie auch das Menü nicht. + +=== Erstellen der Applikation [PU] + +==== Basis + +Als Basis finden Sie im link:{handout}[Praktikumsverzeichnis] das Projekt *Calculator* mit zwei bereitgestellten Klassen. +Sie sind nicht verpflichtet, diese Klassen zu verwenden. +Es steht Ihnen frei, eine eigene Struktur aufzubauen oder die Struktur zu erweitern. + +`Main`:: +Diese Klasse enthält das Grundgerüst der Hauptanwendung und muss erweitert werden. +`ValueHandler`:: +Diese Klasse bietet Ihnen Hilfsfunktionen für die Prüfung der Eingabewerte und die Berechnung der Resultate. + +==== Umsetzung + +[loweralpha] +. Ergänzen Sie die Projektkonfiguration (Gradle) für den Einsatz von link:{url-openjfx}[JavaFX] ++ +[NOTE] +==== +Seit Java 11 ist JavaFX nicht mehr Teil der Java Standard Edition und wird unabhängig davon im link:{url-openjfx}[OpenJFX-Projekt] weiterentwickelt. + +Für Projekte die Java 11 und neuer verwenden, muss es deshalb explizit zum Projekt hinzugefügt werden. + +Da JavaFX neben generischen Libraries (API, Controls, FXML) auch Plattform-/Betriebssystemabhängige Komponenten benötigt, wird für Gradle (und Maven) ein PlugIn zur Verfügung gestellt, welches die notwendigen Komponenten (Dependencies) lädt und in den Prozess einbindet. +==== +** Deklarieren und konfigurieren Sie das JavaFX-Plugin gemäss der link:{url-openjfx-gradle}[OpenJFX Anleitung für Gradle]. +** Verwenden Sie die JavaFX-Version `'17'` (oder neuer) + +. Erstellen Sie das Layout für die Applikation +** Bauen Sie den SceneGraph für die Anwendung gemäss Ihren Überlegungen aus der theoretischen Aufgabe zusammen und binden Sie diesen ins Hauptfenster ein. +. Fügen Sie die Handler für die benötigten Events hinzu. +** Entscheiden Sie, ob Sie innere Klassen verwenden wollen oder ob Sie mit anonymen Klassen arbeiten. +** Auch eine Kombination ist möglich (und eventuell sinnvoll?). +. Probieren Sie verschiedene Lösungsansätze und hinterfragen Sie die Vor- und Nachteile der gewählten Lösung. + +== Erstellen des ROI-Calculators mit FXML [PA] + +Nachdem Sie sich bis hierher einige Gedanken zur Verwendung der Container (Panes) gemacht haben und praktische Erfahrung mit dem Aufbau des User Interfaces gesammelt haben, sind Sie nun bereit für die Umsetzung der Benutzeroberfläche mit FXML unter Verwendung des link:{url-scene-builder}[Scene Builder]. + +TIP: FXML unterstützt die Trennung von View und Controller. +Die Behandlung von Aktionen sollen deshalb konsequent in Methoden des Controllers ausgelagert werden und die Aktualisierung der GUI-Komponenten mittels JavaFX-Properties erfolgen. + +**** +SceneBuilder + +SceneBuilder ist ein Werkzeug zur Bearbeitung von FXML-Dateien und ermöglicht somit die Erstellung und Bearbeitung eines SceneGraphs und die Verknüpfung mit einer zugehörigen Controller-Klasse. + +SceneBuilder muss als eigenständiges Werkzeug link:{url-scene-builder}[heruntergeladen und installiert] werden. + +[NOTE] +IDE's können den SceneBuilder einbinden (FXML-Code-Ansicht & Scene Builder-Ansicht), bzw. liefern bereits eine Version von SceneBuilder mit (IntelliJ). +Diese bietet jedoch meist nur einen eingeschränkten Funktionsumfang, weshalb es bei intensiver Arbeit trotzdem Sinn macht, die unabhängige Version zu installieren und aus der IDE aufzurufen (Dateipfad in Einstellungen konfigurieren und mit "Open In SceneBuilder" öffnen) +**** + +=== Basis + +Als Basis finden Sie im link:{handout}[Praktikumsverzeichnis] das Projekt *FXML-Calculator* mit zwei bereitgestellten Klassen und einer leeren FXML-Datei `src/main/resources/ch/zhaw/prog2/fxmlcalculator/MainWindow.fxml`. +[IMPORTANT] +FXML-Dateien werden nicht kompiliert, müssen jedoch zur Laufzeit im Klassenpfad zur Verfügung stehen. +Gradle (und auch Maven) erwartet deshalb, dass Sie im Ressourcen-Ordner (default: `src/main/resources/`) abgelegt werden (am besten im gleichen Package bzw. Unterverzeichnis wie die zugehörigen Klassen `ch/zhaw/prog2/fxmlcalculator/`). Beim Erstellen der Anwendung werden Sie automatisch kopiert und beim Start in den Klassenpfad integriert. + +Sie sind nicht verpflichtet, diese Klassen zu verwenden. +Es steht Ihnen frei, eine eigene Struktur aufzubauen oder die Struktur zu erweitern. + +`Main`:: +Diese Klasse enthält das Grundgerüst der Hauptanwendung und muss erweitert werden. +`MainWindowController`:: +Diese Klasse ist fast leer und muss erweitert werden. Sie ist insbesondere noch nicht mit dem Layout (`MainWindow.fxml`) verbunden. +`ValueHandler`:: +Diese Klasse enthält Hilfsfunktionen für die Prüfung der Eingabewerte und die Berechnung der Resultate. + +TIP: Falls Ihre IDE bereits ein Start-Projekt für FXML anbietet, können Sie auch dieses verwenden. Nehmen Sie sich die Zeit, die beiden Ansätze zu vergleichen und zu verstehen. + +=== Umsetzung + +[loweralpha] +. Verifizieren Sie die Projektkonfiguration in `build.gradle` für FXML. + +Da für FXML ein zusätzliches JavaFX-Modul (`javafx.fxml`) benötigt wird, muss dieses in in der Konfiguration des Plugins hinzugefügt werden. +. Erstellen Sie den SceneGraph mit Hilfe des SceneBuilder als FXML-Spezifikation, laden diesen und binden ihn im Hauptfenster ein. +** Als Vorlage können Sie die vorhandene `MainWindow.fxml` im Ressourcen-Ordner und die Hauptanwendungsklasse verwenden. +. Erstellen Sie die Controllerklasse und verknüpfen Sie die Controls und Actions mit dem FXML-SceneGraph +** Als Vorlage finden Sie im Praktiumsvezeichnis bereits eine Klasse für den Controller. +. Überlegen Sie sich, wie das vorhandene Datenfeld `resultBound` der Hilfsklasse `ValueHandler` verwenden werden kann, um die View (z.B. Textfeld im MainWindow) mittels Observer-Pattern zu aktualisieren, damit `ValueHandler` selbst keine Referenz auf die View oder den Controller benötigt. +** Welche Hilfsmittel bietet JavaFX dazu an? +** Passen Sie ihre Anwendung entsprechend an. +. Fügen Sie im MainWindow einen weiteren Button und zugehörige Aktion (e.g. `openResultWindow`) ein. Diese soll ein zusätzliches einfaches Resultatfenster öffnen. +** Als Vorlage finden Sie im Praktikumsverzeichnis bereits eine FXML-Datei (`ResultWindow.fxml`) und einen leeren Controller. +** Erweitern Sie den Controller von ResultWindow so, dass auch hier das Resultat angezeigt wird, sobald es in `ValueHandler` neu gerechnet wird. +** Verwenden Sie in beiden Views das gleiche `ValueHandler` Objekt. + +=== Hinweise + +Die Layout-Einstellungen für die verschiedenen Panes sind grundsätzlich so vorkonfiguriert, dass sich die Grösse der Panes nach dem vorhandenen und nach dem verwendeten Platz richtet. +Für die meisten Fälle ist das ok und Sie müssen nur an wenigen Stellen eingreifen. +Zum Beispiel um die Mindestbreite festzulegen. + +WARNING: Die Anzeige des Previews stimmt nicht genau mit der echten Anzeige überein. Starten Sie zwischendurch das Programm neu, um die echte Ansicht zu sehen. + + +// Ende des Aufgabenblocks +:!sectnums: + +== Abschluss + +Stellen Sie sicher, dass die Pflichtaufgabe mittels `gradle run` gestartet werden kann und pushen Sie die Lösung vor der Deadline in Ihr Abgaberepository. diff --git a/code/Calculator/build.gradle b/code/Calculator/build.gradle new file mode 100644 index 0000000..444bae7 --- /dev/null +++ b/code/Calculator/build.gradle @@ -0,0 +1,45 @@ +/* + * Gradle build configuration for specific lab module / exercise + */ +// the Java plugin is added by default in the main lab configuration +plugins { + id 'java' + // Apply the application plugin to add support for building a CLI application. + id 'application' +} + +// Project/Module information +description = 'Lab02 Calculator' +group = 'ch.zhaw.prog2' +version = '2022.1' + +// Dependency configuration +// repositories to download dependencies from +repositories { + mavenCentral() +} +// required dependencies +dependencies { + +} + +// Configuration for Application plugin +application { + // Define the main class for the application. + mainClass = 'ch.zhaw.prog2.calculator.Main' +} + +// Java plugin configuration +java { + // By default the Java version of the gradle process is used as source/target version. + // This can be overridden, to ensure a specific version. Enable only if required. + // sourceCompatibility = JavaVersion.VERSION_17 // ensure Java source code compatibility + // targetCompatibility = JavaVersion.VERSION_17 // version of the created byte-code + + // Java compiler specific options + compileJava { + // source files should be UTF-8 encoded + options.encoding = 'UTF-8' + // for more options see https://docs.gradle.org/current/dsl/org.gradle.api.tasks.compile.CompileOptions.html + } +} diff --git a/code/Calculator/src/main/java/ch/zhaw/prog2/calculator/Main.java b/code/Calculator/src/main/java/ch/zhaw/prog2/calculator/Main.java new file mode 100644 index 0000000..a37942e --- /dev/null +++ b/code/Calculator/src/main/java/ch/zhaw/prog2/calculator/Main.java @@ -0,0 +1,60 @@ +package ch.zhaw.prog2.calculator; + + +import javafx.application.Application; +import javafx.application.Platform; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Scene; +import javafx.scene.control.*; +import javafx.scene.input.KeyCombination; +import javafx.scene.layout.*; +import javafx.scene.paint.Color; +import javafx.stage.Stage; + +public class Main extends Application { + + private Stage primaryStage; + + public static void main(String[] args) { + launch(args); + } + + @Override + public void start(Stage primaryStage) throws Exception { + this.primaryStage = primaryStage; + mainWindow(); + } + + /* + * Create the window, call methods to create the different parts of the + * scene graph. Put the parts together in appropriate panes. + */ + private void mainWindow() { + try { + BorderPane rootPane = new BorderPane(); + //BorderPane top + MenuBar menuBar = new MenuBar(); + + // Create scene with root node with size + Scene scene = new Scene(rootPane, 600, 400); + // scene.getStylesheets().add(getClass().getResource("MyLabel.css").toExternalForm()); + primaryStage.setMinWidth(280); + // Set stage properties + primaryStage.setTitle("Return on Investment Calculator"); + // Add scene to the stage and make it visible + primaryStage.setScene(scene); + primaryStage.show(); + + } catch(Exception e) { + e.printStackTrace(); + } + } + + +} + diff --git a/code/Calculator/src/main/java/ch/zhaw/prog2/calculator/ValueHandler.java b/code/Calculator/src/main/java/ch/zhaw/prog2/calculator/ValueHandler.java new file mode 100644 index 0000000..4c4283a --- /dev/null +++ b/code/Calculator/src/main/java/ch/zhaw/prog2/calculator/ValueHandler.java @@ -0,0 +1,139 @@ +package ch.zhaw.prog2.calculator; + +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; + +/** + * Handles the values from the input form. + * Offers the {@link #resultBound} StringProperty to listen from a view (bind to a field in the view or add a listener) + * @author bles + * @version 1.1 + */ +public class ValueHandler { + + private double initialAmount; + private double returnInPercent; + private double annualCost; + private int numberOfYears; + private boolean valuesOk = false; + // Solution with bound properties + private StringProperty resultBound = new SimpleStringProperty(); + + /** + * Check the input values are valid (can be improved) + * If not ok, return an error message + * If ok, set the data fields and return an empty string + * @return empty string on success or error message on invalid value + */ + private String checkAndSetValues(String initialAmount, String returnInPercent, String annualCost, String numberOfYears) { + StringBuilder sb = new StringBuilder(); + valuesOk = true; + if ("".equals(initialAmount) || Double.parseDouble(initialAmount)<=0) { + sb.append("Please specify a positive initial amount!\n"); + valuesOk = false; + } else { + this.initialAmount = Double.parseDouble(initialAmount); + } + if ("".equals(returnInPercent)) { + sb.append("Please specify the annual return rate in %!\n"); + valuesOk = false; + } else { + this.returnInPercent = Double.parseDouble(returnInPercent)/100; + } + if ("".equals(annualCost) || Double.parseDouble(annualCost)<0) { + sb.append("Please specify the annual cost!\n"); + valuesOk = false; + } else { + this.annualCost = Double.parseDouble(annualCost); + } + if ("".equals(numberOfYears) || + Double.parseDouble(numberOfYears) < 1 || + Double.parseDouble(numberOfYears) > 99 || + Math.round(Double.parseDouble(numberOfYears))!=Double.parseDouble(numberOfYears)) { + sb.append("Please enter a time period in years!"); + valuesOk = false; + } else { + this.numberOfYears = Integer.parseInt(numberOfYears); + } + return sb.toString(); + } + + /** + * If the values checked by {@link #checkAndSetValues(String, String, String, String)} are ok, the return is true + * @return true, if values are ok + */ + public boolean areValuesOk() { + return valuesOk; + } + + /** + * Calculates the result + * @return the result as a String + */ + private String calculateResult() { + StringBuilder resultSB = new StringBuilder(); + double val = initialAmount; + for(int i = 1; i <= numberOfYears; i++) { + resultSB.append("After "); + resultSB.append(i).append(" year(s): "); + val = val * (1 + returnInPercent) - annualCost; + resultSB.append(Math.round(val)).append("\n"); + } + return resultSB.toString(); + } + + + // Solution with bound properties + /** + * String containing the result of the calculation + * Can be "", if no calculation or check is done or could contain the error message on invalid values + * @return String with the result of the value checking or the calculation + */ + public String getResultBound() { + return resultBound.get(); + } + + /** + * Sets the result of the calculation (or error message). + * @param infoText + */ + public void setResultBound(String infoText) { + resultBound.set(infoText); + } + + + /** + * Gives access to the StringProperty holding the result of the calculation + * @return result String property which can be bound to UI elements + */ + public StringProperty resultBoundProperty() { + return resultBound; + } + + /** + * Checks the values and calculates the result. All values as String (from the Text-Fields) + * If the check fails, an error message is set to the bound result property + * @param initialAmount + * @param returnInPercent + * @param annualCost + * @param numberOfYears + */ + public void checkValuesAndCalculateResult(String initialAmount, String returnInPercent, String annualCost, String numberOfYears) { + setResultBound(checkAndSetValues(initialAmount, returnInPercent, annualCost, numberOfYears)); + if(valuesOk) { + setResultBound(calculateResult()); + } + } + + /** + * clears result and resets all values + */ + public void clearResult() { + initialAmount = 0; + returnInPercent = 0; + annualCost = 0; + numberOfYears = 0; + setResultBound(""); + valuesOk = false; + } +} diff --git a/code/FXML-Calculator/build.gradle b/code/FXML-Calculator/build.gradle new file mode 100644 index 0000000..9801fef --- /dev/null +++ b/code/FXML-Calculator/build.gradle @@ -0,0 +1,53 @@ +/* + * Gradle build configuration for specific lab module / exercise + */ +// the Java plugin is added by default in the main lab configuration +plugins { + id 'java' + // Apply the application plugin to add support for building a CLI application. + id 'application' + // Adding JavaFX support and dependencies + id 'org.openjfx.javafxplugin' version '0.0.12' +} + +// Project/Module information +description = 'Lab02 FXML Calculator' +group = 'ch.zhaw.prog2' +version = '2022.1' + +// Dependency configuration +// repositories to download dependencies from +repositories { + mavenCentral() +} +// required dependencies +dependencies { + +} + +// Configuration for Application plugin +application { + // Define the main class for the application. + mainClass = 'ch.zhaw.prog2.fxmlcalculator.Main' +} + +// Configuration for JavaFX plugin +javafx { + version = '17' + modules = [ 'javafx.controls' ] +} + +// Java plugin configuration +java { + // By default the Java version of the gradle process is used as source/target version. + // This can be overridden, to ensure a specific version. Enable only if required. + // sourceCompatibility = JavaVersion.VERSION_17 // ensure Java source code compatibility + // targetCompatibility = JavaVersion.VERSION_17 // version of the created byte-code + + // Java compiler specific options + compileJava { + // source files should be UTF-8 encoded + options.encoding = 'UTF-8' + // for more options see https://docs.gradle.org/current/dsl/org.gradle.api.tasks.compile.CompileOptions.html + } +} diff --git a/code/FXML-Calculator/src/main/java/ch/zhaw/prog2/fxmlcalculator/Main.java b/code/FXML-Calculator/src/main/java/ch/zhaw/prog2/fxmlcalculator/Main.java new file mode 100644 index 0000000..19e0863 --- /dev/null +++ b/code/FXML-Calculator/src/main/java/ch/zhaw/prog2/fxmlcalculator/Main.java @@ -0,0 +1,32 @@ +package ch.zhaw.prog2.fxmlcalculator; + +import javafx.application.Application; +import javafx.fxml.FXMLLoader; +import javafx.scene.Scene; +import javafx.scene.layout.Pane; +import javafx.stage.Stage; +/** + * Main-Application. Opens the first window (MainWindow) and the common ValueHandler + * @author + * @version 1.0 + */ +public class Main extends Application { + + private ValueHandler valueHandler; + + public static void main(String[] args) { + launch(args); + } + + @Override + public void start(Stage primaryStage) { + valueHandler = new ValueHandler(); + mainWindow(primaryStage); + } + + private void mainWindow(Stage primaryStage) { + //load main window + } + +} + diff --git a/code/FXML-Calculator/src/main/java/ch/zhaw/prog2/fxmlcalculator/MainWindowController.java b/code/FXML-Calculator/src/main/java/ch/zhaw/prog2/fxmlcalculator/MainWindowController.java new file mode 100644 index 0000000..dff952f --- /dev/null +++ b/code/FXML-Calculator/src/main/java/ch/zhaw/prog2/fxmlcalculator/MainWindowController.java @@ -0,0 +1,22 @@ +package ch.zhaw.prog2.fxmlcalculator; + + +import javafx.scene.Scene; +import javafx.scene.control.CheckMenuItem; +import javafx.scene.control.TextArea; +import javafx.scene.control.TextField; +import javafx.scene.layout.*; +import javafx.scene.paint.Color; +import javafx.stage.Stage; + +/** + * Controller for the MainWindow. One controller per mask (or FXML file) + * Contains everything the controller has to reach in the view (controls) + * and all methods the view calls based on events. + * @author + * @version 1.0 + */ +public class MainWindowController { + // please complete + +} diff --git a/code/FXML-Calculator/src/main/java/ch/zhaw/prog2/fxmlcalculator/ResultWindowController.java b/code/FXML-Calculator/src/main/java/ch/zhaw/prog2/fxmlcalculator/ResultWindowController.java new file mode 100644 index 0000000..2077a37 --- /dev/null +++ b/code/FXML-Calculator/src/main/java/ch/zhaw/prog2/fxmlcalculator/ResultWindowController.java @@ -0,0 +1,25 @@ +package ch.zhaw.prog2.fxmlcalculator; + +import javafx.scene.control.TextArea; +import javafx.stage.Stage; + +/** + * Controller for the MainWindow. One controller per mask (or FXML file) + * Contains everything the controller has to reach in the view (controls) + * and all methods the view calls based on events. + * @author + * @version 1.0 + */ +public class ResultWindowController { + // add datafields + + //@FXML + private TextArea results; + + //@FXML + private void closeWindow() { + Stage stage = (Stage) results.getScene().getWindow(); + stage.close(); + } + +} diff --git a/code/FXML-Calculator/src/main/java/ch/zhaw/prog2/fxmlcalculator/ValueHandler.java b/code/FXML-Calculator/src/main/java/ch/zhaw/prog2/fxmlcalculator/ValueHandler.java new file mode 100644 index 0000000..7782abc --- /dev/null +++ b/code/FXML-Calculator/src/main/java/ch/zhaw/prog2/fxmlcalculator/ValueHandler.java @@ -0,0 +1,139 @@ +package ch.zhaw.prog2.fxmlcalculator; + +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; + +/** + * Handles the values from the input form. + * Offers the {@link #resultBound} StringProperty to listen from a view (bind to a field in the view or add a listener) + * @author bles + * @version 1.1 + */ +public class ValueHandler { + + private double initialAmount; + private double returnInPercent; + private double annualCost; + private int numberOfYears; + private boolean valuesOk = false; + // Solution with bound properties + private StringProperty resultBound = new SimpleStringProperty(); + + /** + * Check the input values are valid (can be improved) + * If not ok, return an error message + * If ok, set the data fields and return an empty string + * @return empty string on success or error message on invalid value + */ + private String checkAndSetValues(String initialAmount, String returnInPercent, String annualCost, String numberOfYears) { + StringBuilder sb = new StringBuilder(); + valuesOk = true; + if ("".equals(initialAmount) || Double.parseDouble(initialAmount)<=0) { + sb.append("Please specify a positive initial amount!\n"); + valuesOk = false; + } else { + this.initialAmount = Double.parseDouble(initialAmount); + } + if ("".equals(returnInPercent)) { + sb.append("Please specify the annual return rate in %!\n"); + valuesOk = false; + } else { + this.returnInPercent = Double.parseDouble(returnInPercent)/100; + } + if ("".equals(annualCost) || Double.parseDouble(annualCost)<0) { + sb.append("Please specify the annual cost!\n"); + valuesOk = false; + } else { + this.annualCost = Double.parseDouble(annualCost); + } + if ("".equals(numberOfYears) || + Double.parseDouble(numberOfYears) < 1 || + Double.parseDouble(numberOfYears) > 99 || + Math.round(Double.parseDouble(numberOfYears))!=Double.parseDouble(numberOfYears)) { + sb.append("Please enter a time period in years!"); + valuesOk = false; + } else { + this.numberOfYears = Integer.parseInt(numberOfYears); + } + return sb.toString(); + } + + /** + * If the values checked by {@link #checkAndSetValues(String, String, String, String)} are ok, the return is true + * @return true, if values are ok + */ + public boolean areValuesOk() { + return valuesOk; + } + + /** + * Calculates the result + * @return the result as a String + */ + private String calculateResult() { + StringBuilder resultSB = new StringBuilder(); + double val = initialAmount; + for(int i = 1; i <= numberOfYears; i++) { + resultSB.append("After "); + resultSB.append(i).append(" year(s): "); + val = val * (1 + returnInPercent) - annualCost; + resultSB.append(Math.round(val)).append("\n"); + } + return resultSB.toString(); + } + + + // Solution with bound properties + /** + * String containing the result of the calculation + * Can be "", if no calculation or check is done or could contain the error message on invalid values + * @return String with the result of the value checking or the calculation + */ + public String getResultBound() { + return resultBound.get(); + } + + /** + * Sets the result of the calculation (or error message). + * @param infoText + */ + public void setResultBound(String infoText) { + resultBound.set(infoText); + } + + + /** + * Gives access to the StringProperty holding the result of the calculation + * @return result String property which can be bound to UI elements + */ + public StringProperty resultBoundProperty() { + return resultBound; + } + + /** + * Checks the values and calculates the result. All values as String (from the Text-Fields) + * If the check fails, an error message is set to the bound result property + * @param initialAmount + * @param returnInPercent + * @param annualCost + * @param numberOfYears + */ + public void checkValuesAndCalculateResult(String initialAmount, String returnInPercent, String annualCost, String numberOfYears) { + setResultBound(checkAndSetValues(initialAmount, returnInPercent, annualCost, numberOfYears)); + if(valuesOk) { + setResultBound(calculateResult()); + } + } + + /** + * clears result and resets all values + */ + public void clearResult() { + initialAmount = 0; + returnInPercent = 0; + annualCost = 0; + numberOfYears = 0; + setResultBound(""); + valuesOk = false; + } +} diff --git a/code/FXML-Calculator/src/main/resources/ch/zhaw/prog2/fxmlcalculator/MainWindow.fxml b/code/FXML-Calculator/src/main/resources/ch/zhaw/prog2/fxmlcalculator/MainWindow.fxml new file mode 100644 index 0000000..5599e8b --- /dev/null +++ b/code/FXML-Calculator/src/main/resources/ch/zhaw/prog2/fxmlcalculator/MainWindow.fxml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/code/FXML-Calculator/src/main/resources/ch/zhaw/prog2/fxmlcalculator/ResultWindow.fxml b/code/FXML-Calculator/src/main/resources/ch/zhaw/prog2/fxmlcalculator/ResultWindow.fxml new file mode 100644 index 0000000..88b59f2 --- /dev/null +++ b/code/FXML-Calculator/src/main/resources/ch/zhaw/prog2/fxmlcalculator/ResultWindow.fxml @@ -0,0 +1,49 @@ + + + + + + + + + + +
+ + + + + + + + +