diff --git a/tutorials/assets/DialogBoxesIteration2.png b/tutorials/assets/DialogBoxesIteration2.png
index 2d72f1d1..31e8f39e 100644
Binary files a/tutorials/assets/DialogBoxesIteration2.png and b/tutorials/assets/DialogBoxesIteration2.png differ
diff --git a/tutorials/assets/MainWindowController.png b/tutorials/assets/MainWindowController.png
index 26d1a736..54f48f12 100644
Binary files a/tutorials/assets/MainWindowController.png and b/tutorials/assets/MainWindowController.png differ
diff --git a/tutorials/gradleTutorial.md b/tutorials/gradleTutorial.md
index 0a0bb19b..eabe65f0 100644
--- a/tutorials/gradleTutorial.md
+++ b/tutorials/gradleTutorial.md
@@ -164,6 +164,6 @@ Now that you have a general idea of how to accomplish basic tasks with Gradle, h
* [Official Gradle Documentation](https://docs.gradle.org/current/userguide/userguide.html)
-----------------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
**Authors:**
-* Initial Version: Jeffry Lum
\ No newline at end of file
+* Initial Version: Jeffry Lum
diff --git a/tutorials/javaFxTutorialPart1.md b/tutorials/javaFxTutorialPart1.md
index 2f633173..bb132015 100644
--- a/tutorials/javaFxTutorialPart1.md
+++ b/tutorials/javaFxTutorialPart1.md
@@ -10,7 +10,7 @@ A JavaFX application is like a play you are directing. Instead of creating props
## Setting up Java FX
-#### If you are not using Gradle
+### If you are not using Gradle
1. Download the [JavaFX 11 SDK](https://gluonhq.com/products/javafx/) and unzip it.
@@ -23,7 +23,7 @@ A JavaFX application is like a play you are directing. Instead of creating props
`--module-path {JAVAFX_HOME}/lib --add-modules javafx.controls,javafx.fxml`
e.g., `--module-path C:/javafx-sdk-11.0.2/lib --add-modules javafx.controls,javafx.fxml`
-#### If you are using Gradle
+### If you are using Gradle
Update your `build.gradle` to include the following lines:
```groovy
@@ -52,11 +52,9 @@ import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.stage.Stage;
-public class HelloWorld extends Application {
-
- public static void main(String[] args) {
- launch();
- }
+public class Duke extends Application {
+
+ // ...
@Override
public void start(Stage stage) {
@@ -71,7 +69,23 @@ public class HelloWorld extends Application {
Note how we have created a `Label` to contain the text that we want to show. We then create the `Scene` and set its content. Finally, we set the stage and show it.
-Run the program and you should see something like this:
+Next, we create another Java class, `Launcher`, as an entry point to our application.
+The `Launcher` class is reproduced below in its entirety.
+
+```java
+import javafx.application.Application;
+
+/**
+ * A launcher class to workaround classpath issues.
+ */
+public class Launcher {
+ public static void main(String[] args) {
+ Application.launch(Duke.class, args);
+ }
+}
+```
+
+Run `Launcher` and you should see something like this:
![Hello World](assets/HelloWorld.png)
@@ -91,6 +105,6 @@ Congratulations! You have created your first GUI application!
1. Can you have more than one `Stage` an application?
1. Try creating another stage and showing it! What happens?
-----------------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
**Authors:**
-* Initial Version: Jeffry Lum
\ No newline at end of file
+* Initial Version: Jeffry Lum
diff --git a/tutorials/javaFxTutorialPart2.md b/tutorials/javaFxTutorialPart2.md
index f6831d9c..f24a0cd6 100644
--- a/tutorials/javaFxTutorialPart2.md
+++ b/tutorials/javaFxTutorialPart2.md
@@ -49,7 +49,7 @@ import javafx.scene.layout.VBox;
import javafx.stage.Stage;
-public class Main extends Application {
+public class Duke extends Application {
private ScrollPane scrollPane;
private VBox dialogContainer;
@@ -58,7 +58,7 @@ public class Main extends Application {
private Scene scene;
public static void main(String[] args) {
- launch();
+ // ...
}
@Override
@@ -105,34 +105,35 @@ Add the following code to the bottom of the `start` method. You'll have to add `
//...
- //Step 2. Formatting the window to look as expected
- stage.setTitle("Duke");
- stage.setResizable(false);
- stage.setMinHeight(600.0);
- stage.setMinWidth(400.0);
-
- mainLayout.setPrefSize(400.0, 600.0);
-
- scrollPane.setPrefSize(385, 535);
- scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
- scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS);
-
- scrollPane.setVvalue(1.0);
- scrollPane.setFitToWidth(true);
-
- dialogContainer.setPrefHeight(Region.USE_COMPUTED_SIZE);
-
- userInput.setPrefWidth(325.0);
-
- sendButton.setPrefWidth(55.0);
-
- AnchorPane.setTopAnchor(scrollPane, 1.0);
-
- AnchorPane.setBottomAnchor(sendButton, 1.0);
- AnchorPane.setRightAnchor(sendButton, 1.0);
-
- AnchorPane.setLeftAnchor(userInput , 1.0);
- AnchorPane.setBottomAnchor(userInput, 1.0);
+ //Step 2. Formatting the window to look as expected
+ stage.setTitle("Duke");
+ stage.setResizable(false);
+ stage.setMinHeight(600.0);
+ stage.setMinWidth(400.0);
+
+ mainLayout.setPrefSize(400.0, 600.0);
+
+ scrollPane.setPrefSize(385, 535);
+ scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
+ scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS);
+
+ scrollPane.setVvalue(1.0);
+ scrollPane.setFitToWidth(true);
+
+ // You will need to import `javafx.scene.layout.Region` for this.
+ dialogContainer.setPrefHeight(Region.USE_COMPUTED_SIZE);
+
+ userInput.setPrefWidth(325.0);
+
+ sendButton.setPrefWidth(55.0);
+
+ AnchorPane.setTopAnchor(scrollPane, 1.0);
+
+ AnchorPane.setBottomAnchor(sendButton, 1.0);
+ AnchorPane.setRightAnchor(sendButton, 1.0);
+
+ AnchorPane.setLeftAnchor(userInput , 1.0);
+ AnchorPane.setBottomAnchor(userInput, 1.0);
// more code to be added here later
}
@@ -152,6 +153,6 @@ Run the application again. It should now look like this:
1. What happens when you press the `Enter` key or left-click the send button?
1. Why?
-----------------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
**Authors:**
-* Initial Version: Jeffry Lum
\ No newline at end of file
+* Initial Version: Jeffry Lum
diff --git a/tutorials/javaFxTutorialPart3.md b/tutorials/javaFxTutorialPart3.md
index 04f573c4..33c6779e 100644
--- a/tutorials/javaFxTutorialPart3.md
+++ b/tutorials/javaFxTutorialPart3.md
@@ -1,8 +1,6 @@
# JavaFX Tutorial 3 – Interacting with the user
-Picking up from where we left off last tutorial, we have successfully achieved the desired layout. Now let’s make the application respond to user input.
-
-:info: You should substitute the provided `DukeStub` class with your completed Duke.
+Picking up from where we left off last tutorial, we have successfully achieved the desired layout. Now let’s make the application respond to user input.
Rather than to do everything in one try, let’s iterate and build up towards our final goal.
@@ -39,6 +37,7 @@ public void start(Stage stage) {
* @return a label with the specified text that has word wrap enabled.
*/
private Label getDialogLabel(String text) {
+ // You will need to import `javafx.scene.control.Label`.
Label textToAdd = new Label(text);
textToAdd.setWrapText(true);
@@ -103,10 +102,16 @@ import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
```
-Next, add two images:
+Next, add two images to the `main/resources/images` folder.
+For this tutorial, we have two images `DaUser.png` and `DaDuke.png` to represent the user avatar and Duke's avatar respectively.
+
```java
-private Image user = new Image(this.getClass().getResourceAsStream("/images/DaUser.png"));
-private Image duke = new Image(this.getClass().getResourceAsStream("/images/DaDuke.png"));
+public class Duke extends Application {
+ // ...
+ private Image user = new Image(this.getClass().getResourceAsStream("/images/DaUser.png"));
+ private Image duke = new Image(this.getClass().getResourceAsStream("/images/DaDuke.png"));
+ // ...
+}
```
Add a new method to handle user input:
@@ -125,6 +130,14 @@ private void handleUserInput() {
);
userInput.clear();
}
+
+/**
+ * You should have your own function to generate a response to user input.
+ * Replace this stub with your completed method.
+ */
+private String getResponse(String input) {
+ return "Duke heard: " + input;
+}
```
Update the event handler code in the `start` method to use the new `handleUserInput` method:
@@ -154,18 +167,13 @@ Run the program and see how it works.
One additional benefit of defining a custom control is that we can add behavior specific to our `DialogBox`. Let’s add a method to flip a dialog box such that the image on the left to differentiate between user input and Duke’s output.
```java
-// Make the constructor private
-private DialogBox(Label l, ImageView iv) {
- //...
-}
-
/**
* Flips the dialog box such that the ImageView is on the left and text on the right.
*/
private void flip() {
this.setAlignment(Pos.TOP_LEFT);
ObservableList tmp = FXCollections.observableArrayList(this.getChildren());
- Collections.reverse(tmp);
+ FXCollections.reverse(tmp);
this.getChildren().setAll(tmp);
}
@@ -224,6 +232,6 @@ You have successfully implemented a fully functional GUI for Duke!
* What was the development workflow like?
* Is the code base well-organized?
-----------------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
**Authors:**
* Initial Version: Jeffry Lum
diff --git a/tutorials/javaFxTutorialPart4.md b/tutorials/javaFxTutorialPart4.md
index c5d47800..1a6e5bc4 100644
--- a/tutorials/javaFxTutorialPart4.md
+++ b/tutorials/javaFxTutorialPart4.md
@@ -1,7 +1,5 @@
# JavaFX Tutorial 4 – FXML
-You will need to clone the code sample for this tutorial.
-
While we have produced a fully functional prototype, there are a few major problems with our application.
1. The process of visually enhancing the GUI is long and painful:
@@ -35,10 +33,57 @@ Let's return to Duke and convert it to use FXML instead.
# Rebuilding the Scene using FXML
-1. Scene Builder is a tool developed by Oracle and currently maintained by Gluon. It is a What-You-See-Is-What-You-Get GUI creation tool. [Download](https://gluonhq.com/products/scene-builder/#download) the appropriate version for your OS and install it.
+Scene Builder is a tool developed by Oracle and currently maintained by Gluon. It is a What-You-See-Is-What-You-Get GUI creation tool. [Download](https://gluonhq.com/products/scene-builder/#download) the appropriate version for your OS and install it.
+
+Create the following files in `src/main/resources/view`:
+
+**MainWindow.fxml**
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+**DialogBox.fxml**
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
-1. Let’s explore the provided FXML files in Scene Builder. Running the tool brings up the main screen.
-Select `Open Project` > `src/main/resources/view/MainWindow.fxml`. Inspect each control and its properties.
+1. Let’s explore the provided FXML files in Scene Builder.
+
+ Running the tool brings up the main screen.
+ Select `Open Project` > `src/main/resources/view/MainWindow.fxml`. Inspect each control and its properties.
![SceneBuilder opening MainWindow.fxml](assets/SceneBuilder.png)
@@ -56,9 +101,20 @@ We will get to that later.
## Using Controllers
-Let's take a closer look at the `MainWindow` class that we specified as the controller in the FXML file:
+As part of the effort to separate the code handling Duke's logic and UI, let's _refactor_ the UI-related code to its own class.
+We call these UI classes _controllers_.
+
+Let's implement the `MainWindow` controller class that we specified in `MainWindow.fxml`.
+**MainWindow.java**
```java
+import javafx.fxml.FXML;
+import javafx.scene.control.Button;
+import javafx.scene.control.ScrollPane;
+import javafx.scene.control.TextField;
+import javafx.scene.image.Image;
+import javafx.scene.layout.AnchorPane;
+import javafx.scene.layout.VBox;
/**
* Controller for MainWindow. Provides the layout for the other controls.
*/
@@ -72,61 +128,107 @@ public class MainWindow extends AnchorPane {
@FXML
private Button sendButton;
- private Image user = new Image(this.getClass().getResourceAsStream("/images/DaUser.png"));
- private Image duke = new Image(this.getClass().getResourceAsStream("/images/DaDuke.png"));
+ private Duke duke;
+
+ private Image userImage = new Image(this.getClass().getResourceAsStream("/images/DaUser.png"));
+ private Image dukeImage = new Image(this.getClass().getResourceAsStream("/images/DaDuke.png"));
@FXML
public void initialize() {
scrollPane.vvalueProperty().bind(dialogContainer.heightProperty());
}
- /** Creates two dialog boxes, one echoing user input and the other containing Duke's reply and then appends them to
+ public void setDuke(Duke d) {
+ duke = d;
+ }
+
+ /**
+ * Creates two dialog boxes, one echoing user input and the other containing Duke's reply and then appends them to
* the dialog container. Clears the user input after processing.
*/
@FXML
private void handleUserInput() {
String input = userInput.getText();
- String response = DukeStub.getResponse(input);
+ String response = duke.getResponse(input);
dialogContainer.getChildren().addAll(
- DialogBox.getUserDialog(input, user),
- DialogBox.getDukeDialog(response, duke)
+ DialogBox.getUserDialog(input, userImage),
+ DialogBox.getDukeDialog(response, dukeImage)
);
userInput.clear();
}
-
}
```
-As the controller, `MainWindow` is solely responsible for the logic behind the application.
-
-The `@FXML` annotation marks a `private` or `protected` member and makes it accessible to FXML despite its modifier. Without the annotation, we will have to make everything `public` and expose our UI to unwanted changes.
+The `@FXML` annotation marks a `private` or `protected` member and makes it accessible to FXML despite its modifier.
+Without the annotation, we will have to make everything `public` and expose our UI to unwanted changes.
-The `FXMLLoader` will map the a control with a `fx:id` defined in FXML to a variable with the same name in its controller. Notice how in `MainWindow`, we can invoke `TextField#clear()` on `userInput` and access its content just as we did in the previous example. Similarly, methods like private methods like `handleUserInput` can be used in FXML when annotated by `@FXML`.
+The `FXMLLoader` will map the a control with a `fx:id` defined in FXML to a variable with the same name in its controller.
+Notice how in `MainWindow`, we can invoke `TextField#clear()` on `userInput` and access its content just as we did in the previous example.
+Similarly, methods like private methods like `handleUserInput` can be used in FXML when annotated by `@FXML`.
## Using FXML in our application
-We load a FXML file using a `FXMLLoader`. Note how we use `getResource` instead of using `InputStreams` from `java.io` as our resources will no longer be accessible through file IO when we eventually package our application into an executable `jar`.
+Let's create a new `Main` class as the bridge between the existing logic in `Duke` and the UI in `MainWindow`.
+**Main.java**
```java
@Override
-public void start(Stage stage) {
- try {
- FXMLLoader fxmlLoader = new FXMLLoader(Main.class.getResource("/view/MainWindow.fxml"));
- AnchorPane ap = (AnchorPane) fxmlLoader.load();
- Scene scene = new Scene(ap);
- stage.setScene(scene);
- stage.show();
- } catch (IOException e) {
- e.printStackTrace();
+import java.io.IOException;
+
+import javafx.application.Application;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Scene;
+import javafx.scene.layout.AnchorPane;
+import javafx.stage.Stage;
+
+/**
+ * A GUI for Duke using FXML.
+ */
+public class Main extends Application {
+
+ private Duke duke = new Duke();
+
+ @Override
+ public void start(Stage stage) {
+ try {
+ FXMLLoader fxmlLoader = new FXMLLoader(Main.class.getResource("/view/MainWindow.fxml"));
+ AnchorPane ap = fxmlLoader.load();
+ Scene scene = new Scene(ap);
+ stage.setScene(scene);
+ fxmlLoader.getController().setDuke(duke);
+ stage.show();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
}
}
```
Again, we can interact with the `AnchorPane` defined in the FXML as we would have if we created the `AnchorPane` ourselves.
-For our custom `DialogBox`, we did not define a controller. Looking at the code for the `DialogBox` class:
+For our custom `DialogBox`, we did not define a controller so let's create a controller for it.
+**DialogBox.java**
```java
+import java.io.IOException;
+import java.util.Collections;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import javafx.fxml.FXML;
+import javafx.fxml.FXMLLoader;
+import javafx.geometry.Pos;
+import javafx.scene.Node;
+import javafx.scene.control.Label;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+import javafx.scene.layout.HBox;
+
+/**
+ * An example of a custom control using FXML.
+ * This control represents a dialog box consisting of an ImageView to represent the speaker's face and a label
+ * containing text from the speaker.
+ */
public class DialogBox extends HBox {
@FXML
private Label dialog;
@@ -146,13 +248,39 @@ public class DialogBox extends HBox {
dialog.setText(text);
displayPicture.setImage(img);
}
- // ...
+
+ /**
+ * Flips the dialog box such that the ImageView is on the left and text on the right.
+ */
+ private void flip() {
+ ObservableList tmp = FXCollections.observableArrayList(this.getChildren());
+ Collections.reverse(tmp);
+ getChildren().setAll(tmp);
+ setAlignment(Pos.TOP_LEFT);
+ }
+
+ public static DialogBox getUserDialog(String text, Image img) {
+ return new DialogBox(text, img);
+ }
+
+ public static DialogBox getDukeDialog(String text, Image img) {
+ var db = new DialogBox(text, img);
+ db.flip();
+ return db;
+ }
}
```
When we create a new instance of `DialogBox`, we set both the controller and root Node to `DialogBox`.
From this point onwards we can interact with `DialogBox` as we have in the previous tutorials.
+The last change that we have to make is to point our `Launcher` class in the right direction:
+In `Launcher.java`
+```java
+//...
+Application.launch(Main.class, args);
+//...
+```
[todo]: # (Discussion on the fx:root pattern.)
## Exercises
@@ -161,6 +289,6 @@ From this point onwards we can interact with `DialogBox` as we have in the previ
1. Extend `MainWindow` to have a `Stage` as a root Node.
1. Customize the appearance of the application further with CSS.
-----------------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
**Authors:**
-* Initial Version: Jeffry Lum
\ No newline at end of file
+* Initial Version: Jeffry Lum
diff --git a/tutorials/textUiTestingTutorial.md b/tutorials/textUiTestingTutorial.md
index 90a9094d..f397d76a 100644
--- a/tutorials/textUiTestingTutorial.md
+++ b/tutorials/textUiTestingTutorial.md
@@ -67,4 +67,8 @@
1. Update the `javac` and `java` commands in the script to match the name/location of your main class.
1. Add an `EXPECTED.txt` to the same folder, containing the expected output.
1. Add an `input.txt` containing the input commands.
-1. Run the `.bat`/`.sh` file to execute the test.
\ No newline at end of file
+1. Run the `.bat`/`.sh` file to execute the test.
+
+--------------------------------------------------------------------------------
+**Authors:**
+* Initial Version: based on se-edu/addressbook-level2, adapted by Jeffry Lum