JavaFx Primeros Pasos

Índice

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.

image-20260316105919371

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 en start().

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

image-20260316110027287

  • Stage es la ventana nativa del sistema operativo.
  • Scene vive 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.

image-20260316110049950

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:

  1. Capture (bajada): el evento viaja desde la raíz hasta el nodo objetivo (target).
  2. Bubble (subida): el evento sube desde el target de vuelta hacia la raíz.

image-20260316110204597

Llamar a event.consume() en cualquier punto detiene la propagación.


Formas de registrar manejadores

  1. 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;"));
  1. 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");
});
  1. 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()
);