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
readycondition with whatever “allowed right now” means in your app (a row selected, two fields filled, a total above zero). - Rename
updateButtonsand 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:idvalues in the layout (txtName,btnSave) must match the@FXMLfield names exactly, and the method named inonKeyReleased(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)andsetManaged(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.5is just “half visible”. Use any value between0.0(invisible) and1.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:idbtnSubmitin the layout must match the@FXMLfield 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
Tooltipon the control’s parent container (for example the surroundingVBox) usingTooltip.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
txtUsernameto 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:idtxtUsernamein the layout must match the@FXMLfield name exactly, or the field will benullandrequestFocus()will throw. - Occasionally the focus does not stick because the scene is still being built when
initialize()runs. If that happens, wrap the call inPlatform.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 exampleKeyCode.ESCAPEto 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:idtxtSearchmust match the@FXMLfield name, and the method named inonKeyPressed(handleKeyPressed) must exist in the controller with aKeyEventparameter, or the screen will not load. - Put
onKeyPressedon 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
Buttonalready 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.