1. Ciclo de vida de la aplicación
JavaFX es el framework de interfaces gráficas moderno para Java. Su arquitectura sigue un modelo de árbol de nodos renderizado sobre un escenario.

La clase principal debe extender javafx.application.Application e implementar start(). El flujo de ejecución es el siguiente:
| Fase | Hilo | Descripción |
|---|---|---|
main() |
Hilo principal de Java | Punto de entrada |
Application.launch() |
Hilo principal de Java | Inicia el toolkit JavaFX |
init() |
Hilo de la aplicación (no JavaFX Thread) | Pre-inicialización. No crear nodos UI aquí |
start(Stage stage) |
JavaFX Application Thread | Construye y muestra la UI |
stop() |
JavaFX Application Thread | Limpieza al cerrar la ventana |
Importante:
init()se ejecuta en el hilo de la aplicación, no en el JavaFX Thread. Todo lo visual debe ir enstart().
Ejemplo mínimo
Añade las siguientes dependencias:
1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>21.0.2</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>21.0.2</version>
</dependency>
Y crea la clase Launcher
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class Launcher extends Application {
// Este `main` es siempre igual
public static void main(String[] args) {
Application.launch(HelloApplication.class, args);
}
@Override // Método definido en `javafx.application.Application`
public void start(Stage stage) {
Label label = new Label("Hola, JavaFX"); // Crea un texto no editable
Scene scene = new Scene(new StackPane(label), 400, 300); // Define la ventana o Scene con una distribución y un tamaño
stage.setScene(scene); // El escenario es la aplicación
stage.setTitle("Mi primera app"); // Le ponemos un título a la ventana o Stage
stage.show(); // La mostramos
}
}
2. La jerarquía Stage → Scene → Node

Stagees la ventana nativa del sistema operativo.Scenevive dentro del Stage y aloja el grafo de nodos.- El nodo raíz es siempre un contenedor, y a partir de él se ramifica el Scene Graph: un árbol de nodos que JavaFX recorre para renderizar.
3. Contenedores principales (layouts)
El primer paso para diseñar el UI es pensar qué distribución (layouts) vamos a usar. Los layouts se crean con contenedores que son los elementos que integran el resto de controles.
Los contenedores organizan sus nodos hijos según reglas de posicionamiento distintas.

VBox
Apila los nodos hijos en vertical, uno debajo del otro.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override // Método definido en `javafx.application.Application`
public void start(Stage stage) {
VBox vbox = new VBox(10); // Una caja vertical con 10px de separación entre cada uno de los hijos
// Y añadimos los controles necesarios, que en este caso son anónimos,
// es decir, no se ha creado una variable, aunque lo habitual es crearla
vbox.getChildren().addAll(new Label("Uno"), new TextField(), new Button("Tres"));
Scene scene = new Scene(vbox, 400, 300); // Define la ventana o Scene con una distribución y un tamaño
// Estos tres pasos también suelen ser igual
stage.setScene(scene); // El escenario es la aplicación
stage.setTitle("Mi primera app"); // Le ponemos un título a la ventana o Stage
stage.show(); // La mostramos
}
HBox
Apila los nodos hijos en horizontal, uno al lado del otro.
1
2
3
// Refactoriza VBox del ejemplo anterior
HBox hbox = new HBox(8); // En este caso, es horizontal
hbox.getChildren().addAll(new Label("Nombre:"}), new TextField());
GridPane
Organiza los nodos en una cuadrícula de filas y columnas. Ideal para formularios.
1
2
3
4
5
6
7
8
9
GridPane grid = new GridPane();
grid.setHgap(10); // Fijamos la separación horizontal de cada una de las celdas
grid.setVgap(8); // Y, ahora, la vertical
// Las celdas se numeran desde el vértice superior-izquierdo
grid.add(new Label("Usuario:"), 0, 0); // columna 0, fila 0
grid.add(new TextField(), 1, 0); // columna 1, fila 0
grid.add(new Label("Email:"), 0, 1); // columna 0, fila 1
grid.add(new TextField(), 1, 1); // columna 1, fila1
BorderPane
Divide el espacio en 5 regiones fijas: Top, Bottom, Left, Right y Center. Ideal para la estructura general de una ventana (barra de menú arriba, barra de estado abajo, panel lateral, contenido central).
1
2
3
4
5
BorderPane border = new BorderPane();
border.setTop(new MenuBar());
border.setLeft(new ListView<>());
border.setCenter(new TextArea());
border.setBottom(new Label("Listo"));
AnchorPane
Permite fijar nodos a los bordes del contenedor mediante anclas (distancias respecto a cada lado).
1
2
3
4
5
AnchorPane anchor = new AnchorPane();
Button btn = new Button("Esquina");
AnchorPane.setTopAnchor(btn, 10.0); // Distancia desde arriba
AnchorPane.setRightAnchor(btn, 10.0); // Distancia desde la derecha
anchor.getChildren().add(btn);
StackPane
Apila los nodos uno sobre otro, centrados. Útil para superponer elementos (por ejemplo, un texto sobre una imagen).
1
2
StackPane stack = new StackPane();
stack.getChildren().addAll(new Rectangle(200, 200), new Label("Encima"));
4. Componentes (controles) principales
Los controles heredan de javafx.scene.control.Control.
| Control | Descripción |
|---|---|
Button |
Botón pulsable |
Label |
Texto no editable |
TextField |
Campo de texto de una línea |
TextArea |
Campo de texto multilínea |
CheckBox |
Casilla de verificación |
RadioButton |
Selección exclusiva (usar con ToggleGroup) |
ComboBox<T> |
Lista desplegable |
ListView<T> |
Lista con selección |
TableView<T> |
Tabla con columnas |
Slider |
Selector de valor numérico |
ProgressBar |
Barra de progreso |
MenuBar / Menu / MenuItem |
Barra de menús |
Ejemplo: RadioButton con ToggleGroup
1
2
3
4
5
ToggleGroup grupo = new ToggleGroup();
RadioButton r1 = new RadioButton("Opción A");
RadioButton r2 = new RadioButton("Opción B");
r1.setToggleGroup(grupo);
r2.setToggleGroup(grupo);
Ejemplo: TableView
1
2
3
4
5
TableView<Persona> tabla = new TableView<>();
TableColumn<Persona, String> colNombre = new TableColumn<>("Nombre");
colNombre.setCellValueFactory(new PropertyValueFactory<>("nombre"));
tabla.getColumns().add(colNombre);
tabla.setItems(FXCollections.observableArrayList(lista));
5. Manejadores de eventos
JavaFX usa un sistema de eventos basado en propagación con dos fases:
- Capture (bajada): el evento viaja desde la raíz hasta el nodo objetivo (target).
- Bubble (subida): el evento sube desde el target de vuelta hacia la raíz.

Llamar a event.consume() en cualquier punto detiene la propagación.
Formas de registrar manejadores
setOnXxx— la más sencilla (fase bubble)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Como voy a asignarle un evento, necesito tener la variable (y no valen variables anónimas)
Button button = new Button("Click Me");
button.setOnAction(event -> {
System.out.println("Button clicked");
});
textField.setOnKeyPressed(event -> {
if (event.getCode() == KeyCode.ENTER) {
procesar();
}
});
// Otros ejemplos
nodo.setOnMouseEntered(e -> nodo.setStyle("-fx-opacity: 0.8;"));
nodo.setOnMouseExited(e -> nodo.setStyle("-fx-opacity: 1.0;"));
addEventHandler— fase bubble, permite múltiples handlers
1
2
3
4
5
6
7
8
button.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {
System.out.println("Click en: " + event.getX() + ", " + event.getY());
});
// Se puede añadir otro handler al mismo nodo para el mismo evento
button.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {
System.out.println("Segundo handler también se ejecuta");
});
addEventFilter— fase capture, intercepta antes de llegar al target
1
2
3
4
5
scene.addEventFilter(MouseEvent.MOUSE_CLICKED, event -> {
System.out.println("Interceptado en capture: " + event.getTarget());
// Descomentar para bloquear la propagación:
// event.consume();
});
Tipos de eventos más comunes
| Evento | Clase | Descripción |
|---|---|---|
| Acción | ActionEvent |
Botones, menús, Enter en TextField |
| Clic / movimiento | MouseEvent |
Clics, movimiento, arrastre del ratón |
| Teclado | KeyEvent |
Teclas presionadas, soltadas, escritas |
| Scroll | ScrollEvent |
Rueda del ratón o gesto de scroll |
| Arrastrar y soltar | DragEvent |
Drag & drop entre nodos |
| Ventana | WindowEvent |
Abrir, cerrar, minimizar ventana |
| Cambio de propiedad | ChangeListener |
Cambio de valor en una Property |
Properties y Bindings — el sistema reactivo de JavaFX
Las Property (IntegerProperty, StringProperty, BooleanProperty, etc.) son la clave del patrón MVC en JavaFX: permiten conectar el modelo con la vista sin acoplarlos directamente.
Con ChangeListener
1
2
3
slider.valueProperty().addListener((observable, oldVal, newVal) -> {
label.setText(String.format("Valor: %.0f", newVal.doubleValue()));
});
Con binding directo (sin listener explícito)
1
2
3
4
// El label se actualiza automáticamente cuando cambia el slider
label.textProperty().bind(
slider.valueProperty().asString("Valor: %.0f")
);
Binding bidireccional
1
2
// Ambas propiedades se sincronizan en ambas direcciones
textField1.textProperty().bindBidirectional(textField2.textProperty());
Bindings compuestos
1
2
3
4
// El botón se deshabilita si el campo está vacío
boton.disableProperty().bind(
textField.textProperty().isEmpty()
);