---
title: "Jam 08 - Exercise 2"
tags:
- 2 ๐ in writing
- 3 ๐งช in testing
- 4 ๐ฅณ done
---
<!-- markdownlint-disable line-length single-h1 no-inline-html -->
<!-- markdownlint-configure-file { "ul-indent": { "indent": 4 }, "link-fragments": {"ignore_case": true} } -->
{%hackmd dJZ5TulxSDKme-3fSY4Lbw %}
# Exercise 2 - JavaFX Foundations
## Overview - Exercise 2
:::warning
๐ **Prerequisites for Exercise 2**
Before starting Exercise 2, you must (in this order):
1. Complete the JavaFX chapters in zyBooks (ZB16):
- Chapter 26: Graphical User Interfaces (FX)
- Chapter 27: Advanced User Interfaces (FX)
2. Watch the 27-minute video lecture on MVC (Model-View-Controller) design pattern in the Videos section of Moodle.
These resources provide essential background knowledge for implementing the JavaFX application structure in this exercise.
:::
## JavaFX Reference Material
The following are links you should bookmark, as you will be referring to them a LOT throughout the rest of the semester:
- JavaFX 21 API - The following is the complete API for JavaFX 21: <https://openjfx.io/javadoc/21/>
These links are to Oracle's official introductory material on helping users get started with JavaFX. Though these are written for JavaFX 8, they are still very much relevant. The most important of these is the master page:
- <https://docs.oracle.com/javase/8/javase-clienttechnologies.htm>
Only pay attention to the first column under JavaFX. Ignore the other columns. You'll see references to software called Scene Builder. It is tempting you to install it, but the Scene Builder documentation on this page is entirely out of date. A future jam will introduce you to SceneBuilder.
Minimally, glance through the following pages before continuing, as it will give you a bit of an overview of JavaFX. It has a lot of information. It's dense. But it has the core essentials that can really make your life easier as you wrestle with understanding JavaFX:
- Understanding the JavaFX Architecture - <https://docs.oracle.com/javase/8/javafx/get-started-tutorial/jfx-architecture.htm>
## Setting Up Your JavaFX Project
Now that we have our data model, we'll create the foundation for our JavaFX application following the MVC pattern. We'll build a professional-grade stock market visualization tool that allows users to:
- View stock price trends over time
- Filter data by date ranges and symbols
- Compare multiple stocks simultaneously
- Analyze price movements and patterns
Let's start by creating the basic structure for our JavaFX application:
1. **Update module-info.java**
The module-info.java file configures your application's module system. For JavaFX applications, you need to:
1. Declare which JavaFX modules your application requires
2. Export your package so JavaFX can access your classes
```java
module csci205_jams {
requires javafx.controls;
requires javafx.fxml;
exports jam08;
}
```
:::info
๐ง **Understanding module-info.java**
- **module csci205_jams**: Declares your module name (matches your project)
- **requires** statements:
- `javafx.controls`: Needed for all JavaFX UI components
- `javafx.fxml`: Needed for FXML support (will use in later exercises)
- **exports** statements:
- EVERY package containing JavaFX classes must be exported
- This includes:
- Packages with Application classes
- Packages with Controller classes
- Packages with custom JavaFX components
- Any subpackages using JavaFX
- Parent package exports DO NOT automatically export child packages
- Example: `exports jam08;` does not export `jam08.view` - you need both statements
:::warning
๐ง **Common Export Mistakes**
1. **Missing Subpackage Exports**
- If you create `jam08.view` and `jam08.controller` packages
- You must export each one separately
- `exports jam08;` alone is not sufficient
2. **Forgetting New Package Exports**
- When you create a new package using JavaFX
- You must add a new exports statement
- This is a common source of "class not found" errors
If you get errors like "module not found" or "class not accessible":
1. Check if the package containing the error is exported
2. Add any missing exports statements
3. Make sure your build.gradle includes JavaFX dependencies
:::
2. **Create a Test Application**
- First, let's verify our JavaFX setup with a simple test application
- Right-click on your `jam08` package in IntelliJ
- Select New โ JavaFX Application
- Name it `HelloFX`
- This will create a basic JavaFX application template
- Update your importts to the below code: ๐จ IMPORTANT: If you choose to import using the context menu after adding the code, be sure import from the javafx package! There are other libraries that classes with the same name.
```java
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
```
- Update the start method to the below code.
```java
public void start(Stage primaryStage) {
// Grab some property values from System
String javaVersion = System.getProperty("java.version");
String javafxVersion = System.getProperty("javafx.version");
// Create a Label from a String showing these properties
Label label = new Label("Hello, JavaFX " + javafxVersion + ", running on Java " + javaVersion + ".");
// Set up our Scene Graph. Use a StackPane layout manager to manage the scene
StackPane pane = new StackPane();
pane.getChildren().add(label);
Scene scene = new Scene(pane,640,480);
// Set the scene to render for the stage and show it!
primaryStage.setScene(scene);
primaryStage.show();
}
```
3. **Configure JavaFX Run Tasks**
Due to JavaFX's module requirements, we need to set up special Gradle tasks to run our application. Add these tasks to the bottom of your `build.gradle` file:
First, add the helper function:
```gradle
// Helper function to create JavaFX-enabled run tasks
def createJavaFXTask(String taskName, String description, String mainClassName) {
tasks.register(taskName, JavaExec) {
group = 'application'
classpath = sourceSets.main.runtimeClasspath
mainClass = mainClassName
jvmArgs = [
'--module-path', classpath.asPath,
'--add-modules', javafx.modules.join(',')
]
}
}
```
Then, create a task for your HelloFX application:
```gradle
// Create a task for the HelloFX test application
createJavaFXTask('runHelloFX',
'Runs the HelloFX application',
'jam08.HelloFX')
```
Let's understand this task:
**runHelloFX**: A Custom Application Runner
- This is an example of creating a dedicated task for a specific application
- It's hardcoded to run `jam08.HelloFX`
- This pattern is important to remember - you'll need to create similar tasks for any JavaFX application you build where you don't want to update the main run method (changing `mainClass.set("jamXX.XX")` as we've done in previous Jams).
- Think of it as your "production-ready" runner
:::info
๐ง **Creating Run Tasks for JavaFX Applications**
You will probably want to to create a run task for EVERY JavaFX application you create in this jam. This includes:
1. The HelloFX test application (shown above)
2. The HelloMain example application (you'll create this next)
3. The StockVisualizerApp (your main application)
When you create a new JavaFX application, follow this pattern:
```gradle
createJavaFXTask('runYourAppName',
'Description of what this app does',
'jam08.YourAppClassName')
```
Remember:
- The task name should be descriptive (e.g., `runHelloFX`, `runStockVisualizer`)
- The description should clearly explain what the task does
- The mainClassName must match your JavaFX application's fully qualified class name
- Always use the `jam08` package prefix for your class names
The alternative is to always maintain your mainClass:
```gradle
application {
mainModule.set("csci205_jams")
mainClass.set("jamXX.XXXXX") // UPDATE THIS LINE
if (project.hasProperty("mainClass")) {
mainClass.set(project.property("mainClass").toString())
}
}
```
:::
4. **Verify Setup**
- Rebuild your IntelliJ Project (Build โ Rebuild Project)
- Rebuild your Gradle project:
- Click the "Gradle" tool window on the right side of IntelliJ
- Click the "Reload All Gradle Projects" button (circular arrows icon)
- To verify the setup, lets running the program from the command line. Run `./gradlew runHelloFX`. We'll setup intelliJ to use these new tasks in a moment.
- If you get any errors, double-check your module-info.java and build.gradle files
5. **Running the Application**
You can run the application in several ways:
a. **Using the Gradle Tool Window**:
- Open the Gradle tool window (View โ Tool Windows โ Gradle)
- Navigate to Tasks โ application
- Double-click the task you want to run:
- `runHelloFX` to verify your setup
- (More tasks will appear as you create them)

b. **Pinning for Easy Access**:
- After running the task once, find it in the run configuration dropdown
- Hover over the configuration and click the pin icon

- The configuration will now stay at the top of the list

Note: Always use these Gradle tasks to run JavaFX applications. The standard IntelliJ "Run" button won't work due to module requirements.
## Understanding the JavaFX Scene Graph
Before we dive into implementation, let's understand some key JavaFX concepts:
:::info
๐ **Key JavaFX Concepts**
1. **Stage**: The top-level container (window)
2. **Scene**: The container for all content
3. **Scene Graph**: The hierarchical structure of UI elements
4. **Nodes**: Individual UI elements (controls, shapes, etc.)
5. **Layout Panes**: Containers that manage the arrangement of nodes
:::
Let's create a simple application that will help us understand these concepts. We'll build it step by step, and you'll learn how each piece fits together.
### Building HelloMain: A Single-File Example
Let's create a simple application that displays a button in the center of the window. When clicked, it shows the current time.
1. First, create a new JavaFX Application class:
- Right-click on your `jam08` package in IntelliJ
- Select New โ JavaFX Application
- Name it `HelloMain`
- This will create a basic JavaFX application template
- **Template Walkthrough:**
- The `HelloMain` class for a JavaFX application extends the `javafx.application.Application` class
- The `main` method includes a single call to the static method `Application.launch()`. This is a standard JavaFX method that starts up your application. It does not return until your application terminates.
- The overridden `start()` method is the main entry point for all JavaFX applications. It is the only abstract method in the `Application` class, and therefore you must define your own behavior for this method.
- The JavaFX framework provides the primary Stage instance (called `primaryStage` in the generated code) as a parameter to start(), representing the top-most window that will contain your scene that you create.
- The `stage` given to `start()` is empty. You need a scene to be placed on the stage, using the `setScene()` method (which we'll do shortly.)
- You must create the `scene` by creating an instance of `Scene`, that will set up the size of the scene, and encapsulate the root of the scene graph โ the container for all content that is to be rendered in the scene.
- It's worth repeating - a `scene` encapsulates what is perhaps the most important data structure in JavaFX, the `scene` graph.
2. A stage needs a scene. And, a scene needs a root node for the scene graph. Let's start simple. We'll use a StackPane for our root node. In the `start` method:
```java
// Create the root node of our scene graph
StackPane root = new StackPane();
```
This creates a `StackPane` as our root node. A `StackPane` is a layout container that stacks its children on top of each other.
3. Create the scene:
```java
// Create the scene with our root node and set its size
Scene scene = new Scene(root, 300, 200);
```
This creates a scene with our `StackPane` as the root node and sets its size to 300x200 pixels.
4. Set up the stage:
```java
// Set the scene on the stage
primaryStage.setScene(scene);
// Set the title for the main window
primaryStage.setTitle("Hello JavaFX");
// Display the scene
primaryStage.show();
```
This:
- Sets the window title
- Adds our scene to the stage
- Makes the window visible
5. At this point you should be able to run the JavaFX Application. Make sure it runs before continuing. It should be a small empty box.
6. Let's add some more life to our empty box! Before your code that displays the scene, create a button and set its text:
```java
// Create a button and set its text
Button timeButton = new Button("Show Current Time");
```
This creates a button that will be our only child node in the scene graph.
7. Set up the event handler for the button:
```java
// Set up the event handler for the button
timeButton.setOnAction(event -> {
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss");
timeButton.setText(now.format(formatter) + " (Click to update)");
});
```
This adds an event handler that will run when the button is clicked. It:
- Gets the current time
- Formats it nicely
- Updates the button's text to show the time
8. Add the button to our scene graph:
```java
// Add the button to our scene graph
root.getChildren().add(timeButton);
```
This adds our button as a child of the `StackPane`.
9. Run the application:
- Click the button to see it change to display the current time
- Try resizing the window - notice how the button stays centered!
:::info
๐ **Understanding What's Happening**
When you run the application, you'll see:
- A window with a centered button
- Clicking the button changes its text to show the current time
- The window can be resized, and the button stays centered
This is because:
1. The `StackPane` automatically centers its children
2. The button is our only child node
3. The `StackPane` handles all the layout management for us
4. The button's text is updated directly in the UI when clicked
:::
:::warning
โ ๏ธ **Limitations of Single-File Applications**
While this example works, it has some limitations:
- All UI creation, event handling, and logic are mixed together
- The code isn't very reusable
- It would be hard to maintain as the application grows
- Testing would be difficult
These limitations motivate our next step: breaking down the application into MVC components.
:::
## Implementing the MVC Pattern
First, let's organize our code to follow the MVC pattern:
```plantuml
@startuml
skinparam classAttributeIconSize 0
skinparam class {
BackgroundColor White
ArrowColor Black
BorderColor Black
}
class StockVisualizerApp {
-model: StockModel
-view: StockView
-controller: StockViewController
+start(stage: Stage): void
}
class StockView {
-root: BorderPane
-chart: LineChart
-filterControls: VBox
-tableView: TableView<Stock>
+getRoot(): Parent
}
class StockViewController {
-model: StockModel
-view: StockView
+initialize(): void
+handleFilterAction(): void
}
StockVisualizerApp --> StockModel
StockVisualizerApp --> StockView
StockVisualizerApp --> StockViewController
StockViewController --> StockModel
StockViewController --> StockView
@enduml
```
Let's implement each component:
### Refactoring the Model
When implementing MVC, there are two common organizational patterns:
1. **MVC Package Pattern**
- All MVC-related code lives in an `mvc` package
- Non-MVC code lives outside this package
- Good for:
- Projects where MVC is just one part of a larger system
- Simple MVC implementations where each component (M/V/C) is a single file
- When the UI and business logic are relatively straightforward
- Example structure:
```bash
com.myapp/
โโโ mvc/
โ โโโ Model.java # Core data structure
โ โโโ View.java # Main application window
โ โโโ Controller.java # Primary controller
โโโ other packages...
โโโ App.java # Main application class
```
2. **Separate MVC Packages Pattern**
- Model, View, and Controller each get their own top-level package
- Used when:
- Most/all of the application follows MVC
- Some of the components (M/V/C) consists of multiple related files
- The application has complex UI or business logic requiring multiple classes
- The pattern we'll use for this project
- Example structure:
```bash
com.myapp/
โโโ model/
โ โโโ DataModel.java # Core data structure
โ โโโ DataLoader.java # Data loading utilities
โ โโโ ModelValidator.java # Data validation logic
โโโ view/
โ โโโ MainView.java # Main application window
โ โโโ ComponentView.java # Reusable UI component
โ โโโ DialogView.java # Custom dialog windows
โโโ controller/
โ โโโ MainController.java # Primary controller
โ โโโ ComponentController.java # Component-specific logic
โโโ App.java # Main application class
```
Which pattern should you use for your own projects? Well... "It depends". ๐
For this project, we'll use separate packages since:
- Our application is primarily MVC-driven
- We already have multiple model files from Exercise 1 (`Stock`, `StockModel`, and `StockDataLoader`)
- We'll be adding multiple view components for charts and controls
- Our controllers will handle different aspects of user interaction
Let's start by refactoring our existing code to follow this pattern:
1. Create a new `model` package in `jam08` in your src/main/java folder
2. Create a new `model` package in `jam08` in your src/test/java folder
3. Move our existing model classes using one of the two following options:
- Manually - Use File Explorer/Finder/Etc.:
- Move `Stock.java` to `jam08.model` in `main`
- Move `StockModel.java` to `jam08.model` in `main`
- Move `StockDataLoader.java` to `jam08.model` in `main`
- Move `StockModelTest.java` to `jam08.model` in `test`
- Update the package declarations in each file
- Update imports in `StockModelTest.java`
- Using IntelliJ
- Highlight the files you want to move and then drag them into the `model` folders (src in src, test in test)
- Click on Refactor in the pop-up

4. Verify your tests still pass
### Creating the View
1. Create a new `view` package in `jam08`
2. Create `StockView.java` in the new package (a regular Java file. Not a JavaFX Application):
Important: Note how the view is handling every about the actual display itself!
```java
public class StockView {
private BorderPane root;
private LineChart<String, Number> priceChart;
private TableView<Stock> stockTable;
public StockView() {
initializeView();
}
public BorderPane getRoot() {
return root;
}
private void initializeView() {
root = new BorderPane();
// setupChart(); // TODO But Not Yet
// setupTable(); // TODO But Not Yet
// setupControls(); // TODO But Not Yet
}
// ... more methods to come
}
```
### Adding the Controller
1. Create a new `controller` package in `jam08`
2. Create `StockViewController.java`:
Important: Note how the controller is handling every about the actual behaviour itself!
Also Important: Note how the controller is connected to both the model AND the view! This particular association can be troublesome to keepup with. What this means is the model doesn't have access to the view and the view doesn't have access to the model but the controller has access to both.
```java
public class StockViewController {
private StockModel model;
private StockView view;
public StockViewController(StockModel model, StockView view) {
this.model = model;
this.view = view;
initialize();
}
private void initialize() {
// setupEventHandlers(); // TODO But Not Yet
// setupBindings(); // TODO But Not Yet
}
// ... more methods to come
}
```
:::info
๐ง **JavaFX Best Practices**
1. **Separation of Concerns**
- Keep UI logic in the View
- Keep business logic in the Model
- Keep coordination logic in the Controller
2. **Event Handling**
- Use lambda expressions for simple events
- Create separate methods for complex event handling
- Consider using JavaFX Properties for data binding
3. **Resource Management**
- Load CSS files from resources
- Use FXML for complex layouts (coming in later exercises)
- Follow JavaFX naming conventions
:::
### Updating the Main Application
1. **Create the Main Application**
- Right-click on your `jam08` package again
- Select New โ JavaFX Application
- Name it `StockVisualizerApp`
- We'll implement this fully in the next section
2. Update `StockVisualizerApp.java` to use the new package structure:
Important: Note hoas the App itself *creates* all the components of the MVC. It creates the model, it creates the view and then it passes references to the model and the view to the controller. This means the App has access to all three.
```java
public class StockVisualizerApp extends Application {
private StockModel model;
private StockView view;
private StockViewController controller;
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
// Initialize MVC components
model = new StockModel();
view = new StockView();
controller = new StockViewController(model, view);
// Set up the stage
Scene scene = new Scene(view.getRoot(), 800, 600);
primaryStage.setTitle("Stock Market Visualizer");
primaryStage.setScene(scene);
primaryStage.show();
}
}
```
3. Update `module-info.java` to export the new packages.
4. Run your StockVisualizerApp. You should see an empty box.
## Creating a Professional Layout
Now that we have our basic MVC structure in place, let's enhance the visual layout of our application. Since we want to update the visual layout, we can find that code in the view section.
Let's enhance our `StockView` class with a professional layout. We'll build this step by step:
1. **Understanding the Layout Components**
- Chart area for price visualization
- Control panel for filters and options
- Data table for detailed information
2. **Setting Up the Chart**
First, let's create the price chart. Look up the `LineChart` class in the JavaFX documentation:
- What kind of axes do we need for time-based data vs numeric data?
- How do we set the chart title?
- Where in a BorderPane should the chart go?
Add a method called `setupChart()` that:
- Creates appropriate axes for time and price data
- Initializes a LineChart with these axes
- Sets a descriptive title
- Places the chart in the center of the BorderPane
3. **Creating the Control Panel**
For our filter controls, we need a vertical layout. Research the `VBox` layout in JavaFX:
- How do we set spacing between elements?
- What's the purpose of padding and how do we add it?
- Where should controls go in the BorderPane for best UX?
Add a method called `setupControls()` that:
- Creates a VBox with appropriate spacing
- Adds padding using `javafx.geometry.Insets`
- Places it in the left side of the BorderPane
4. **Implementing the Data Table**
For displaying detailed stock data, we'll use a TableView:
- Look up TableView in the JavaFX documentation
- What columns do we need for stock data?
- Where should the table go in the BorderPane?
- For now the table will be empty.
Add a method called `setupTable()` that:
- Creates a TableView for Stock objects
- Places it at the bottom of the BorderPane
5. **Putting It All Together**
Update your `initializeView()` method to:
```java
private void initializeView() {
root = new BorderPane();
setupChart();
setupTable();
setupControls();
}
```
:::info
๐ง **Layout Tips**
- Use BorderPane regions effectively:
- CENTER for main content (chart)
- LEFT for controls
- BOTTOM for data table
- Set appropriate spacing and padding
- Consider window resizing behavior
:::
Try implementing these methods based on the documentation. Run your program. You should see something similar to this (but yours might be different):

> ๐ **Checkpoint**: Exercise 2
>
> - JavaFX environment is properly configured and verified
> - MVC pattern is implemented with proper separation of concerns
> - Professional layout is implemented with chart, controls, and table
> - Application launches and responds to window resizing
## Save Your Work - Exercise 2
Verify what files are uncommited:
```bash
git status
```
Stage your changes using `git add`
Commit your work:
```bash
git commit -m "jam08: Implement JavaFX application structure with MVC pattern"
```