Controlling UI state

IA tooling, not an exam topic. These recipes are JavaFX patterns to adapt for your own Internal Assessment app. Nothing here is examinable. Your IA must be your own work, so treat each snippet as a part to change, not a feature to copy.

This page is about controlling what the user can do right now: turning controls on and off, showing why something is off, putting the cursor in the right place, and letting the Enter key do the obvious thing.

New here? Read Start here: how every recipe fits together first; it explains the two places you edit (the SceneBuilder layout and the controller) and the fx:id and onAction links every recipe relies on.


Turn a control on or off depending on what is happening

What this does: switches a control between usable and not usable, so the user can only take actions that make sense at the moment.

When you would use it in your IA: a Save button that stays off until the form has the data it needs; a Delete button that only works once a row is selected; a Next button that waits until the current step is complete.

In SceneBuilder (the layout): add the input the user types into and the button you want to guard. Give the field an fx:id of txtName and set its onKeyReleased to updateButtons so the button state refreshes as the user types. Give the button an fx:id of btnSave.

<TextField fx:id="txtName" onKeyReleased="#updateButtons" />
<Button fx:id="btnSave" text="Save" onAction="#handleSave" />

In the controller (the .java file):

Imports:

import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;

Fields and logic:

@FXML private Button btnSave;
@FXML private TextField txtName;

@FXML
public void initialize() {
    btnSave.setDisable(true);          // start off until there is something to save
}

// Runs whenever the field changes, because of onKeyReleased in the layout.
@FXML
private void updateButtons() {
    // your IA logic here: decide what "ready" means for your app
    boolean ready = !txtName.getText().trim().isEmpty();
    btnSave.setDisable(!ready);        // on when ready, off when not
}

Where your own logic goes: the boolean ready = ... line marked // your IA logic here. That condition is the rule for when the action is allowed; everything around it is reusable wiring.

Make it your own:

  • Replace the ready condition with whatever “allowed right now” means in your app (a row selected, two fields filled, a total above zero).
  • Rename updateButtons and call it from every place that changes those inputs.
  • setDisable(true) turns a control off, setDisable(false) turns it on. The value is “is it disabled”, so it reads backwards from “is it ready”.

Watch out for:

  • The fx:id values in the layout (txtName, btnSave) must match the @FXML field names exactly, and the method named in onKeyReleased (updateButtons) must exist in the controller, or the screen will not load.
  • A disabled control still takes up space and stays visible, just greyed out and unclickable. If you want it gone entirely, that is setVisible(false) and setManaged(false) instead, which is a different choice.
  • Remember to call your update method from every input, not just one, or the button state will lag behind the data.

Mix with: Show the user why a control is off, Catch and explain bad input, Find out which row the user clicked.


Show the user why a control is off

What this does: makes a disabled control look clearly inactive and gives the reason on hover, so the user is not left guessing why a button does nothing.

When you would use it in your IA: any time a control is off for a reason the user might not work out on their own. A faded Submit button with a tooltip saying “Choose a date first” is friendlier than a button that silently ignores clicks.

In SceneBuilder (the layout): add the button you want to guard and give it an fx:id of btnSubmit. The opacity and tooltip are set in code, so there is nothing else to add here.

<Button fx:id="btnSubmit" text="Submit" onAction="#handleSubmit" />

In the controller (the .java file):

Imports:

import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Tooltip;

Controller:

@FXML private Button btnSubmit;

private void setSubmitEnabled(boolean enabled) {
    btnSubmit.setDisable(!enabled);
    btnSubmit.setOpacity(enabled ? 1.0 : 0.5);   // full strength on, half faded off
}

@FXML
public void initialize() {
    btnSubmit.setTooltip(new Tooltip("Fill in every field before submitting."));
    setSubmitEnabled(false);
    // your IA logic here: call setSubmitEnabled(true) once your form is ready
}

Where your own logic goes: the spot marked // your IA logic here, where you decide when the form is ready and call setSubmitEnabled(true). The fading and the tooltip are reusable wiring.

Make it your own:

  • Change the tooltip text to name the exact reason the control is off.
  • 0.5 is just “half visible”. Use any value between 0.0 (invisible) and 1.0 (solid) to taste.
  • Call setSubmitEnabled(true) from your “is it ready” check, the same place as the previous recipe.

Watch out for:

  • The fx:id btnSubmit in the layout must match the @FXML field name exactly, or the screen will not load.
  • Tooltips only appear on hover, so they are a hint, not a substitute for a visible message when something is genuinely wrong.
  • A control that is fully disabled does not receive mouse events, so a tooltip set directly on it may not show while it is disabled. If you need the hint to appear in the disabled state, put the Tooltip on the control’s parent container (for example the surrounding VBox) using Tooltip.install(parentNode, tip), or keep the control enabled and show a message on click instead.

Mix with: Turn a control on or off, Tell the user something.


Put the cursor where the user should start

What this does: moves the keyboard focus to a chosen control when the screen opens, so the user can start typing straight away without clicking first.

When you would use it in your IA: a login screen that should land on the username field; a search screen that should land on the search box; any form with an obvious first field.

In SceneBuilder (the layout): add the field that should be active first and give it an fx:id of txtUsername. Nothing else to add here.

<TextField fx:id="txtUsername" />

In the controller (the .java file):

Imports:

import javafx.fxml.FXML;
import javafx.scene.control.TextField;

Controller:

@FXML private TextField txtUsername;

@FXML
public void initialize() {
    // your IA logic here: choose which control should be active first
    txtUsername.requestFocus();      // this field is active when the window opens
}

Where your own logic goes: the choice of which control to focus, marked // your IA logic here. For most screens that is the first field; the requestFocus() call itself is wiring.

Make it your own:

  • Change txtUsername to whichever control should be active first.
  • initialize() is the method JavaFX calls once the FXML has loaded, which is why it is the right place for opening setup.

Watch out for:

  • The fx:id txtUsername in the layout must match the @FXML field name exactly, or the field will be null and requestFocus() will throw.
  • Occasionally the focus does not stick because the scene is still being built when initialize() runs. If that happens, wrap the call in Platform.runLater(() -> txtUsername.requestFocus()); so it runs just after the window is shown. That line uses a lambda, which is outside the exam-style Java subset but is the standard way to do this in JavaFX.
  • Only one control can hold focus at a time, so calling requestFocus() on several controls means the last one wins.

Mix with: Run setup code when a screen loads, React to the Enter key.


React to the Enter key

What this does: runs an action when the user presses Enter in a control, so they can submit a form or move on without reaching for the mouse.

When you would use it in your IA: pressing Enter in a search box to run the search; pressing Enter in the last field of a form to submit it; pressing Enter in a chat-style input to send.

In SceneBuilder (the layout): add the field you want to listen to, give it an fx:id of txtSearch, and set its onKeyPressed to handleKeyPressed.

<TextField fx:id="txtSearch" onKeyPressed="#handleKeyPressed" />

In the controller (the .java file):

Imports:

import javafx.fxml.FXML;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;

Controller:

@FXML private TextField txtSearch;

@FXML
private void handleKeyPressed(KeyEvent event) {
    if (event.getCode() == KeyCode.ENTER) {
        // your IA logic here: what Enter should trigger
        runSearch();
    }
}

Where your own logic goes: inside the if (Enter) block, on the line marked // your IA logic here. The key check is reusable wiring; the action is yours.

Make it your own:

  • Replace runSearch() with the action you want Enter to trigger.
  • To react to a different key, compare against another KeyCode, for example KeyCode.ESCAPE to clear the field.
  • If you prefer to wire it in code rather than FXML, use txtSearch.setOnKeyPressed(e -> { ... });. That form uses a lambda, which is outside the exam-style subset but common in JavaFX.

Watch out for:

  • The fx:id txtSearch must match the @FXML field name, and the method named in onKeyPressed (handleKeyPressed) must exist in the controller with a KeyEvent parameter, or the screen will not load.
  • Put onKeyPressed on the control you want to listen to. If you put it on a container, key events only arrive when that container itself has focus.
  • A Button already fires its action on Enter when it has focus, so you usually only need this recipe for text fields and other inputs.

Mix with: Put the cursor where the user should start, Filter the table as the user types, Ask before doing something destructive.


© EduCS.me — A resource hub for Computer Science education

This site uses Just the Docs, a documentation theme for Jekyll.