Ir al contenido principal

Desarrollo de interfaces gráficas con JavaFX

Introducción

JavaFX es la tecnología moderna de Java para la creación de interfaces gráficas de usuario (GUI), sustituyendo a las antiguas bibliotecas como Swing y AWT. Esta plataforma permite a los desarrolladores crear aplicaciones de escritorio visualmente atractivas, con características avanzadas de diseño, animaciones y efectos visuales. JavaFX está integrado en el JDK desde Java 8, aunque a partir de Java 11 se convirtió en un módulo independiente, permitiendo mayor flexibilidad en su desarrollo y actualización.

En este artículo, aprenderás los fundamentos de JavaFX para desarrollar interfaces gráficas efectivas. Conocerás cómo estructurar una aplicación JavaFX, crear diversos controles de interfaz, aplicar estilos con CSS y manejar eventos para hacer que tu aplicación responda a las acciones del usuario. Este conocimiento te permitirá evolucionar desde las aplicaciones de consola hacia programas con interfaces visuales profesionales.

Estructura básica de una aplicación JavaFX

Toda aplicación JavaFX sigue una estructura fundamental que es importante comprender antes de comenzar a desarrollar. Los componentes clave son:

La clase Application

Para crear una aplicación JavaFX, debemos extender la clase javafx.application.Application e implementar el método start():

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class AplicacionBasica extends Application {

    @Override
    public void start(Stage primaryStage) {
        // Creamos un componente Label
        Label etiqueta = new Label("¡Hola JavaFX!");
        
        // Creamos un contenedor para nuestros componentes
        StackPane raiz = new StackPane();
        raiz.getChildren().add(etiqueta);
        
        // Creamos una escena con nuestro contenedor
        Scene escena = new Scene(raiz, 300, 200);
        
        // Configuramos y mostramos el escenario
        primaryStage.setTitle("Mi Primera Aplicación JavaFX");
        primaryStage.setScene(escena);
        primaryStage.show();
    }

    public static void main(String[] args) {
        // Lanzamos la aplicación
        launch(args);
    }
}

Jerarquía de componentes en JavaFX

JavaFX utiliza una estructura jerárquica para organizar los elementos de la interfaz:

  1. Stage (Escenario): Representa la ventana principal de la aplicación.
  2. Scene (Escena): El contenedor de primer nivel dentro del Stage.
  3. Nodos: Los componentes visuales (controles, contenedores, formas, etc.) que se organizan en la escena.

Contenedores de diseño

Los contenedores son nodos especiales que organizan la disposición de otros nodos dentro de ellos. JavaFX ofrece varios tipos de contenedores para diferentes necesidades de diseño:

BorderPane

Divide el espacio en cinco regiones: arriba, abajo, izquierda, derecha y centro.

import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;

BorderPane borderPane = new BorderPane();
borderPane.setTop(new Button("Arriba"));
borderPane.setBottom(new Button("Abajo"));
borderPane.setLeft(new Button("Izquierda"));
borderPane.setRight(new Button("Derecha"));
borderPane.setCenter(new Button("Centro"));

GridPane

Organiza los elementos en una cuadrícula flexible de filas y columnas:

import javafx.geometry.Insets;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;

GridPane grid = new GridPane();
grid.setPadding(new Insets(10));
grid.setHgap(10); // Espacio horizontal entre columnas
grid.setVgap(8);  // Espacio vertical entre filas

// Añadimos componentes específicos a posiciones de la cuadrícula
grid.add(new Label("Nombre:"), 0, 0); // columna 0, fila 0
grid.add(new TextField(), 1, 0);      // columna 1, fila 0
grid.add(new Label("Apellido:"), 0, 1);
grid.add(new TextField(), 1, 1);
grid.add(new Label("Email:"), 0, 2);
grid.add(new TextField(), 1, 2);

HBox y VBox

HBox organiza componentes en una fila horizontal, mientras que VBox los organiza en una columna vertical:

import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;

// Crear un HBox con espaciado de 10px entre elementos
HBox hbox = new HBox(10);
hbox.setPadding(new Insets(15));
hbox.setAlignment(Pos.CENTER);
hbox.getChildren().addAll(
    new Button("Botón 1"),
    new Button("Botón 2"),
    new Button("Botón 3"));

// Crear un VBox con espaciado de 8px entre elementos
VBox vbox = new VBox(8);
vbox.setPadding(new Insets(15));
vbox.setAlignment(Pos.CENTER_LEFT);
vbox.getChildren().addAll(
    new Button("Opción A"),
    new Button("Opción B"),
    new Button("Opción C"));

Controles de interfaz de usuario

JavaFX proporciona una amplia gama de controles para la interacción con el usuario:

Botones y etiquetas

import javafx.scene.control.Button;
import javafx.scene.control.Label;

Label etiqueta = new Label("Introduce tu nombre:");
Button boton = new Button("Enviar");

Campos de texto y áreas de texto

import javafx.scene.control.TextField;
import javafx.scene.control.TextArea;

// Campo para texto de una sola línea
TextField campoNombre = new TextField();
campoNombre.setPromptText("Escribe tu nombre aquí");

// Área para texto multilínea
TextArea areaComentarios = new TextArea();
areaComentarios.setPromptText("Escribe tus comentarios");
areaComentarios.setPrefRowCount(5); // Número preferido de filas visibles

Casillas de verificación y botones de radio

import javafx.scene.control.CheckBox;
import javafx.scene.control.RadioButton;
import javafx.scene.control.ToggleGroup;

// Casillas de verificación (permiten selección múltiple)
CheckBox chkJava = new CheckBox("Java");
CheckBox chkPython = new CheckBox("Python");
CheckBox chkCSharp = new CheckBox("C#");

// Botones de radio (permiten una sola selección dentro del grupo)
ToggleGroup grupoNivel = new ToggleGroup();

RadioButton rbPrincipiante = new RadioButton("Principiante");
RadioButton rbIntermedio = new RadioButton("Intermedio");
RadioButton rbAvanzado = new RadioButton("Avanzado");

rbPrincipiante.setToggleGroup(grupoNivel);
rbIntermedio.setToggleGroup(grupoNivel);
rbAvanzado.setToggleGroup(grupoNivel);

// Establecer selección predeterminada
rbPrincipiante.setSelected(true);

Menús

import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;

MenuBar barraMenu = new MenuBar();

// Menú Archivo
Menu menuArchivo = new Menu("Archivo");
MenuItem itemNuevo = new MenuItem("Nuevo");
MenuItem itemAbrir = new MenuItem("Abrir");
MenuItem itemGuardar = new MenuItem("Guardar");
MenuItem itemSalir = new MenuItem("Salir");
menuArchivo.getItems().addAll(itemNuevo, itemAbrir, itemGuardar, itemSalir);

// Menú Editar
Menu menuEditar = new Menu("Editar");
MenuItem itemCopiar = new MenuItem("Copiar");
MenuItem itemPegar = new MenuItem("Pegar");
menuEditar.getItems().addAll(itemCopiar, itemPegar);

barraMenu.getMenus().addAll(menuArchivo, menuEditar);

Manejo de eventos

El manejo de eventos en JavaFX permite que tu aplicación responda a las acciones del usuario, como clics de botón o entrada de texto:

Manejo básico de eventos

import javafx.event.ActionEvent;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.Button;

Button botonSaludar = new Button("Saludar");

// Usando expresiones lambda (Java 8+)
botonSaludar.setOnAction((ActionEvent evento) -> {
    Alert alerta = new Alert(AlertType.INFORMATION);
    alerta.setTitle("Saludo");
    alerta.setHeaderText(null);
    alerta.setContentText("¡Hola, bienvenido a JavaFX!");
    alerta.showAndWait();
});

Controladores de eventos personalizados

Para aplicaciones más complejas, puedes crear clases controladoras dedicadas:

import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;

public class FormularioController {
    
    @FXML
    private TextField campoNombre;
    
    @FXML
    private Button botonEnviar;
    
    @FXML
    public void initialize() {
        // Configuramos el evento al inicializarse el controlador
        botonEnviar.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                System.out.println("Nombre enviado: " + campoNombre.getText());
            }
        });
    }
    
    // Método alternativo usando anotaciones FXML
    @FXML
    public void onEnviarClick(ActionEvent event) {
        System.out.println("Nombre enviado: " + campoNombre.getText());
    }
}

Estilo con CSS

JavaFX permite personalizar la apariencia de tu aplicación utilizando CSS, similar al usado en desarrollo web:

// Aplicar estilos directamente a un nodo
Button botonPersonalizado = new Button("Botón Estilizado");
botonPersonalizado.setStyle("-fx-background-color: #4286f4; -fx-text-fill: white;");

// Aplicar un archivo CSS a toda la escena
Scene escena = new Scene(raiz, 800, 600);
escena.getStylesheets().add(getClass().getResource("estilos.css").toExternalForm());

Ejemplo de archivo CSS (estilos.css):

/* Estilo para todos los botones */
.button {
    -fx-background-color: #4286f4;
    -fx-text-fill: white;
    -fx-font-weight: bold;
    -fx-padding: 5 10 5 10;
}

/* Estilo para botones al pasar el cursor */
.button:hover {
    -fx-background-color: #2a5db0;
}

/* Estilo para etiquetas */
.label {
    -fx-font-size: 14px;
    -fx-text-fill: #333333;
}

Uso de FXML para separar diseño y código

FXML es un lenguaje basado en XML que permite separar la interfaz de usuario del código de la aplicación:

<!-- vista.fxml -->
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.geometry.Insets?>

<VBox spacing="10" alignment="CENTER" prefWidth="300" prefHeight="200" 
      xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" 
      fx:controller="com.ejemplo.FormularioController">
   <padding>
      <Insets top="20" right="20" bottom="20" left="20"/>
   </padding>
   
   <Label text="Nombre:"/>
   <TextField fx:id="campoNombre"/>
   <Button fx:id="botonEnviar" text="Enviar" onAction="#onEnviarClick"/>
</VBox>

Para cargar un archivo FXML:

import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

@Override
public void start(Stage primaryStage) throws Exception {
    Parent raiz = FXMLLoader.load(getClass().getResource("vista.fxml"));
    Scene escena = new Scene(raiz);
    
    primaryStage.setTitle("Aplicación FXML");
    primaryStage.setScene(escena);
    primaryStage.show();
}

Ejemplo práctico: Formulario de registro

Vamos a desarrollar un ejemplo completo de un formulario de registro utilizando JavaFX:

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;

public class FormularioRegistro extends Application {

    @Override
    public void start(Stage primaryStage) {
        // Creamos el panel principal
        BorderPane panelPrincipal = new BorderPane();
        
        // Título
        Label titulo = new Label("Formulario de Registro");
        titulo.setStyle("-fx-font-size: 18px; -fx-font-weight: bold;");
        HBox panelTitulo = new HBox(titulo);
        panelTitulo.setAlignment(Pos.CENTER);
        panelTitulo.setPadding(new Insets(15));
        
        // Formulario
        GridPane formulario = new GridPane();
        formulario.setHgap(10);
        formulario.setVgap(8);
        formulario.setPadding(new Insets(20));
        formulario.setAlignment(Pos.CENTER);
        
        Label lblNombre = new Label("Nombre:");
        TextField txtNombre = new TextField();
        
        Label lblApellido = new Label("Apellido:");
        TextField txtApellido = new TextField();
        
        Label lblEmail = new Label("Email:");
        TextField txtEmail = new TextField();
        
        Label lblPassword = new Label("Contraseña:");
        PasswordField txtPassword = new PasswordField();
        
        Label lblGenero = new Label("Género:");
        ToggleGroup grupoGenero = new ToggleGroup();
        RadioButton rbMasculino = new RadioButton("Masculino");
        RadioButton rbFemenino = new RadioButton("Femenino");
        RadioButton rbOtro = new RadioButton("Otro");
        rbMasculino.setToggleGroup(grupoGenero);
        rbFemenino.setToggleGroup(grupoGenero);
        rbOtro.setToggleGroup(grupoGenero);
        HBox panelGenero = new HBox(10, rbMasculino, rbFemenino, rbOtro);
        
        CheckBox chkTerminos = new CheckBox("Acepto los términos y condiciones");
        
        Button btnRegistrar = new Button("Registrarse");
        Button btnCancelar = new Button("Cancelar");
        HBox panelBotones = new HBox(10, btnRegistrar, btnCancelar);
        panelBotones.setAlignment(Pos.CENTER_RIGHT);
        
        // Añadimos los componentes al formulario
        formulario.add(lblNombre, 0, 0);
        formulario.add(txtNombre, 1, 0);
        formulario.add(lblApellido, 0, 1);
        formulario.add(txtApellido, 1, 1);
        formulario.add(lblEmail, 0, 2);
        formulario.add(txtEmail, 1, 2);
        formulario.add(lblPassword, 0, 3);
        formulario.add(txtPassword, 1, 3);
        formulario.add(lblGenero, 0, 4);
        formulario.add(panelGenero, 1, 4);
        formulario.add(chkTerminos, 0, 5, 2, 1);
        formulario.add(panelBotones, 1, 6);
        
        // Ajustamos el ancho de los campos
        txtNombre.setPrefWidth(200);
        txtApellido.setPrefWidth(200);
        txtEmail.setPrefWidth(200);
        txtPassword.setPrefWidth(200);
        
        // Manejamos el evento del botón Registrar
        btnRegistrar.setOnAction(e -> {
            if (!chkTerminos.isSelected()) {
                mostrarAlerta("Debe aceptar los términos y condiciones para registrarse.");
                return;
            }
            
            if (txtNombre.getText().isEmpty() || txtApellido.getText().isEmpty() ||
                txtEmail.getText().isEmpty() || txtPassword.getText().isEmpty()) {
                mostrarAlerta("Por favor, complete todos los campos obligatorios.");
                return;
            }
            
            mostrarConfirmacion("Usuario registrado correctamente: " + txtNombre.getText() + 
                               " " + txtApellido.getText());
            limpiarFormulario(txtNombre, txtApellido, txtEmail, txtPassword, chkTerminos, grupoGenero);
        });
        
        // Manejamos el evento del botón Cancelar
        btnCancelar.setOnAction(e -> {
            limpiarFormulario(txtNombre, txtApellido, txtEmail, txtPassword, chkTerminos, grupoGenero);
        });
        
        // Organizamos el panel principal
        panelPrincipal.setTop(panelTitulo);
        panelPrincipal.setCenter(formulario);
        
        // Creamos la escena
        Scene escena = new Scene(panelPrincipal, 450, 350);
        
        // Configuramos y mostramos el escenario
        primaryStage.setTitle("Registro de Usuario");
        primaryStage.setScene(escena);
        primaryStage.show();
    }
    
    private void mostrarAlerta(String mensaje) {
        Alert alerta = new Alert(Alert.AlertType.WARNING);
        alerta.setTitle("Advertencia");
        alerta.setHeaderText(null);
        alerta.setContentText(mensaje);
        alerta.showAndWait();
    }
    
    private void mostrarConfirmacion(String mensaje) {
        Alert confirmacion = new Alert(Alert.AlertType.INFORMATION);
        confirmacion.setTitle("Registro Exitoso");
        confirmacion.setHeaderText(null);
        confirmacion.setContentText(mensaje);
        confirmacion.showAndWait();
    }
    
    private void limpiarFormulario(TextField txtNombre, TextField txtApellido, 
                                   TextField txtEmail, PasswordField txtPassword, 
                                   CheckBox chkTerminos, ToggleGroup grupoGenero) {
        txtNombre.clear();
        txtApellido.clear();
        txtEmail.clear();
        txtPassword.clear();
        chkTerminos.setSelected(false);
        grupoGenero.selectToggle(null);
    }

    public static void main(String[] args) {
        launch(args);
    }
}

Al ejecutar este código, obtendrás un formulario de registro completo con validación básica de campos.

Resumen

En este artículo has aprendido los fundamentos del desarrollo de interfaces gráficas con JavaFX, desde la estructura básica de una aplicación hasta la creación de formularios interactivos. Hemos explorado los distintos contenedores para organizar elementos (BorderPane, GridPane, HBox, VBox), los controles de interfaz más comunes (botones, campos de texto, casillas, menús), el manejo de eventos para responder a acciones del usuario, y cómo aplicar estilos con CSS para mejorar la apariencia.

JavaFX representa una herramienta poderosa para crear aplicaciones Java con interfaces modernas y atractivas. A partir de aquí, puedes explorar características más avanzadas como animaciones, efectos visuales, gráficos y charts, o la integración con bases de datos que veremos en el siguiente artículo. La combinación de JavaFX con los conocimientos previos de Java te permitirá desarrollar aplicaciones completas y profesionales con una experiencia de usuario mejorada.