App structure and navigation
IA tooling, not an exam topic. These are JavaFX patterns to adapt for your IA. Nothing here is examinable, and your IA must be your own work.
This page is about the shape of a whole app: when setup code runs, menus, quitting cleanly, opening a second window, passing data between screens, sharing data across the app, splitting into tabs, and a coat of CSS to make it look finished.
Run setup code when a screen loads
What this does: uses the initialize() method, which JavaFX calls automatically once a screen’s FXML has loaded, to do any opening setup.
When you would use it in your IA: filling a table or combo box with starting data, setting a default selection, loading saved records, or focusing the first field. Anything that should be ready before the user sees the screen.
The pattern:
import javafx.fxml.FXML;
import javafx.scene.control.Label;
@FXML private Label lblWelcome;
@FXML
public void initialize() {
// Runs once, automatically, after the controls are built and injected.
lblWelcome.setText("Welcome back");
loadSavedData(); // your own setup
}
Make it your own:
- Put your column setup, data loading, and default selections here.
- The method must be named
initializefor JavaFX to call it. The@FXMLannotation lets it be private.
Watch out for:
- The
@FXMLfields arenulluntil just beforeinitialize()runs, so do not try to use the controls in the controller’s constructor.initialize()is the earliest safe place. - It runs once per screen load, not every time the window is shown again.
Mix with: Put the cursor where the user should start, Display rows in a scrollable table.
Add actions to the menu bar
What this does: puts a menu bar at the top of the window with items that run your methods when clicked.
When you would use it in your IA: the standard File, Edit, Help menus, or any grouped set of commands you do not want as on-screen buttons.
The pattern:
FXML:
<MenuBar>
<menus>
<Menu text="File">
<items>
<MenuItem text="Save" onAction="#handleSave" />
<MenuItem text="Exit" onAction="#handleExit" />
</items>
</Menu>
<Menu text="Help">
<items>
<MenuItem text="About" onAction="#handleAbout" />
</items>
</Menu>
</menus>
</MenuBar>
Controller:
@FXML
private void handleSave() {
// save the data
}
@FXML
private void handleExit() {
// quit the app
}
@FXML
private void handleAbout() {
// show an information dialog
}
Make it your own:
- Rename the menus and items, and point each
onActionat a method that exists in your controller. - A
MenuItem’s handler works exactly like a button’s, so you can reuse the same method for a menu item and a toolbar button.
Watch out for:
- Each
onActionname must match an@FXMLmethod exactly, including the#. A typo gives a load-time error.
Mix with: Quit the app cleanly, Tell the user something.
Quit the app cleanly
What this does: closes the application. There are two ways, and the difference matters when you have cleanup to do.
When you would use it in your IA: an Exit menu item or a Quit button.
The pattern:
import javafx.application.Platform;
// Cleaner: shuts JavaFX down, running your Application.stop() first.
private void quit() {
Platform.exit();
}
// Harder stop: ends the program immediately.
private void forceQuit() {
System.exit(0);
}
Make it your own:
- Prefer
Platform.exit()for a normal quit. If yourApplicationclass overridesstop(), that is where you save data or release resources, andPlatform.exit()makes sure it runs. - Use
System.exit(0)only when you genuinely need an immediate stop without that cleanup.
Watch out for:
System.exit(0)does not runApplication.stop(), so any save-on-exit code there is skipped. The0means “ended normally”.- Closing the last window can also end the app, depending on your setup, so test which path your Exit control actually takes.
Mix with: Add actions to the menu bar, Ask before doing something destructive.
Open a second window
What this does: loads another FXML screen into a new Stage and shows it as a separate window.
When you would use it in your IA: an Add or Edit dialog, a details window, or a settings window that sits on top of the main screen.
The pattern:
Imports:
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.io.IOException;
Method:
private void openDetailsWindow() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("Details.fxml"));
Parent root = loader.load();
Stage stage = new Stage();
stage.setTitle("Details");
stage.setScene(new Scene(root));
stage.show();
} catch (IOException e) {
// handle the error
}
}
Make it your own:
- Change
"Details.fxml"to your second screen’s FXML file, and the title to suit. - Keep the example to two windows. More than that is usually better handled with tabs or by swapping scenes.
Watch out for:
getResource("Details.fxml")looks for the file next to your class. If it returnsnull, the path or file name is wrong.- A new
Stageopens alongside the first window. If you want the second window to block the first until it is closed, callstage.initModality(Modality.APPLICATION_MODAL)before showing it.
Mix with: Pass information between screens, Share data across the whole app.
Pass information between screens
What this does: hands data to a second screen by getting that screen’s controller from the FXMLLoader and calling a public setter on it.
When you would use it in your IA: opening an edit window for the row the user selected, or sending a chosen value into a details screen.
The pattern: this builds on the previous recipe. The new lines are getting the controller and calling its setter.
private void editItem(Item selected) {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("Edit.fxml"));
Parent root = loader.load();
// Get the controller of the screen we just loaded, then pass data in.
EditController controller = loader.getController();
controller.setItem(selected);
Stage stage = new Stage();
stage.setScene(new Scene(root));
stage.show();
} catch (IOException e) {
// handle the error
}
}
The second screen’s controller has a public setter:
public class EditController {
private Item item;
public void setItem(Item item) {
this.item = item;
// fill the fields on this screen with the item's details
}
}
Make it your own:
- Replace
EditControllerandsetItem(...)with your own controller and a setter that takes whatever data the screen needs. - The setter is also the natural place to fill in the second screen’s fields from the data you passed in.
Watch out for:
- Call
loader.getController()afterloader.load(), not before. Before the load, there is no controller yet and it returnsnull. - Pass data through a public setter, not by reaching into the other controller’s fields directly. The setter keeps the screens loosely connected.
Mix with: Open a second window, Find out which row the user clicked.
Share data across the whole app
What this does: keeps one shared object that every screen can read from and write to, using a small Singleton class so there is only ever one of it.
When you would use it in your IA: the logged-in user, app-wide settings, or a single shopping cart that several screens all work with.
The pattern:
public class AppSettings {
private static AppSettings instance; // the one and only instance
private String currentUser;
private AppSettings() {
// private constructor: nothing outside can make a second one
}
public static AppSettings getInstance() {
if (instance == null) {
instance = new AppSettings();
}
return instance;
}
public String getCurrentUser() {
return currentUser;
}
public void setCurrentUser(String currentUser) {
this.currentUser = currentUser;
}
}
Any screen uses the same shared object:
AppSettings.getInstance().setCurrentUser("Sam"); // on the login screen
String user = AppSettings.getInstance().getCurrentUser(); // on any other screen
Make it your own:
- Add fields and getters and setters for whatever you need to share across screens.
- The two parts that make it a Singleton are the
privateconstructor and thestatic getInstance()method. Keep both.
Watch out for:
- A Singleton is shared global state. It is handy for genuinely app-wide data, but do not put everything in it, or screens become hard to follow. Most data should still travel through the setter approach above.
- This is the same Singleton idea from the HL design-patterns material. SL students can use it as a recipe without needing the full theory.
Mix with: Pass information between screens, Save and load text.
Split a big app into tabs
What this does: puts several sections in one window as tabbed pages, so the user switches between them without opening new windows.
When you would use it in your IA: separating, say, a data-entry section, a list view, and a settings section in a single window.
The pattern:
FXML:
<TabPane>
<tabs>
<Tab text="Items" closable="false">
<content>
<!-- the controls for this section go here -->
</content>
</Tab>
<Tab text="Settings" closable="false">
<content>
<!-- the controls for this section go here -->
</content>
</Tab>
</tabs>
</TabPane>
Make it your own:
- Add one
Tabper section and put that section’s controls inside its<content>. - Set
closable="false"on tabs the user should not be able to close.
Watch out for:
- All the tabs share one window and, by default, one controller. For a large app, you can load a separate FXML into each tab’s content so each section has its own controller.
- Tabs suit sections of one task. For genuinely separate jobs, a second window is often clearer.
Mix with: Open a second window, Make your app look finished.
Make your app look finished
What this does: attaches a CSS stylesheet to the scene so you can improve spacing and colour without changing your Java code.
When you would use it in your IA: the cheap, high-impact step near the end of a build, to move the app from “default grey” to something that looks considered.
The pattern:
Attach the stylesheet when you build the scene:
Scene scene = new Scene(root, 600, 400);
scene.getStylesheets().add(getClass().getResource("style.css").toExternalForm());
A small style.css next to your classes:
.root {
-fx-font-family: "Segoe UI", sans-serif;
-fx-background-color: #f4f6fb;
}
.button {
-fx-background-color: #2d6cdf;
-fx-text-fill: white;
-fx-padding: 8 16 8 16;
-fx-background-radius: 6;
}
.primary-heading {
-fx-font-size: 18px;
-fx-font-weight: bold;
}
Give a node a style class in code so the matching rule applies:
lblTitle.getStyleClass().add("primary-heading");
Make it your own:
- Change the colours, fonts, and spacing to suit your app. Keep the palette small, two or three colours is plenty.
- Style whole control types (
.button) for a consistent look, and use named style classes (.primary-heading) for specific nodes.
Watch out for:
- JavaFX CSS is similar to web CSS but not identical: the property names start with
-fx-(for example-fx-background-color, notbackground-color). Web CSS property names will be ignored. - If the stylesheet seems to have no effect, check that
getResource("style.css")is finding the file. Anullthere means the path is wrong.
Mix with: Split a big app into tabs, Put an image on a button.