17  Interfícies gràfiques amb JavaFX

Objectius

  • Entendre què és una interfície gràfica d’usuari (GUI) i com es diferencia de la consola.
  • Conèixer l’arquitectura de JavaFX: Stage, Scene i Node.
  • Muntar una finestra amb controls (botons, camps de text, etiquetes) i layouts.
  • Respondre a esdeveniments (clics, tecles) amb controladors (handlers).
  • Organitzar una aplicació amb el patró MVC (Model-Vista-Controlador).

Fins ara els teus programes parlaven amb l’usuari per la consola: Scanner per llegir, System.out per escriure (Capítol 10). Funciona, però cap aplicació real d’escriptori funciona així. Una interfície gràfica (GUI) presenta finestres, botons i formularis amb els quals l’usuari interactua amb el ratolí i el teclat. En aquest capítol faràs el salt a construir-ne una amb JavaFX, la biblioteca gràfica moderna de l’ecosistema Java.

17.1 De la consola a la finestra

17.1.1 Explicació

Un programa de consola és seqüencial: demana una dada, espera, calcula, mostra i continua. Tu controles el fil. Una aplicació gràfica funciona a l’inrevés: espera que passi alguna cosa (que l’usuari cliqui, escrigui, tanqui…) i reacciona. Això s’anomena programació dirigida per esdeveniments.

17.1.2 Analogia

La consola és una conversa per carta: preguntes, esperes resposta, tornes a preguntar, tot en ordre. Una GUI és una recepció d’hotel: el recepcionista està assegut esperant i reacciona segons qui arriba i què demana. No sap en quin ordre vindran les peticions; simplement, respon a cada esdeveniment.

Aspecte Consola Interfície gràfica (GUI)
Flux Seqüencial, el controla el programa Dirigit per esdeveniments
Entrada Scanner (text) Ratolí, teclat, controls
Sortida System.out (text) Finestres, botons, taules
Espera Bloqueja fins que escrius Escolta events en segon pla
Experiència Tècnica Amigable per a qualsevol usuari
NotaPer què JavaFX i no Swing?

Java té dues biblioteques gràfiques: Swing (antiga, encara vigent) i JavaFX (moderna, recomanada avui). JavaFX separa millor el disseny visual de la lògica, permet dissenyar interfícies amb fitxers FXML i té un estil més actual. Des de Java 11 JavaFX no ve dins del JDK: s’afegeix com a dependència (vegeu la secció de configuració).

17.2 L’arquitectura de JavaFX: Stage, Scene, Node

17.2.1 Explicació

Tota aplicació JavaFX s’organitza en tres nivells encaixats:

  • Stage — la finestra del sistema operatiu (amb la seva barra de títol, botons de minimitzar/tancar). És l’escenari.
  • Scene — el contingut que es mostra dins d’un Stage en un moment donat. Pots intercanviar escenes dins la mateixa finestra (per exemple, pantalla d’inici → pantalla de joc).
  • Node — cada element visual dins de l’escena: un botó, una etiqueta, un camp de text, un contenidor… Els nodes s’organitzen en un arbre.

17.2.2 Analogia

Pensa en el teatre: l’escenari (Stage) és fix; l’escena (Scene) és el decorat que hi muntes per a cada acte i pots canviar; els actors i objectes (Node) són els elements que poblen aquesta escena. Un mateix escenari, moltes escenes, cada escena amb els seus nodes.

flowchart TD
    App["Application (arrenca l'app)"] --> Stage["Stage · la finestra"]
    Stage --> Scene["Scene · el contingut"]
    Scene --> Root["Node arrel · un layout (VBox, BorderPane...)"]
    Root --> N1["Label"]
    Root --> N2["TextField"]
    Root --> N3["Button"]

17.2.3 Exemple mínim: la primera finestra

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

public class HolaFX extends Application {

    @Override
    public void start(Stage escenari) {
        Label missatge = new Label("Hola, JavaFX!");   // un Node
        StackPane arrel = new StackPane(missatge);      // layout que el centra
        Scene escena = new Scene(arrel, 300, 200);      // contingut 300x200

        escenari.setTitle("La meva primera finestra");
        escenari.setScene(escena);
        escenari.show();                                 // mostra la finestra
    }

    public static void main(String[] args) {
        launch(args);   // arrenca JavaFX i acaba cridant start()
    }
}

Tota aplicació JavaFX estén Application i implementa start(Stage). El mètode main només crida launch, que inicialitza el sistema gràfic i acaba cridant start.

AlertaError típic: oblidar show()

Si construeixes l’Stage i l’Scene però no crides escenari.show(), no apareix cap finestra i el programa sembla que no fa res. La finestra existeix en memòria, però no s’ensenya.

ConsellMini-repte

Modifica HolaFX perquè la finestra mesuri 500×350 i mostri el teu nom. Canvia també el títol de la barra superior.

17.3 Layouts: col·locar els controls

17.3.1 Explicació

No col·loques els controls amb coordenades a mà (seria un malson quan es redimensiona la finestra). Els poses dins de layouts: contenidors que organitzen els seus fills segons una regla.

Layout Com col·loca els fills
VBox En columna (un sota l’altre)
HBox En fila (un al costat de l’altre)
BorderPane En 5 zones: dalt, baix, esquerra, dreta, centre
GridPane En graella de files i columnes (ideal per a formularis)
StackPane Apilats i centrats un sobre l’altre

17.3.2 Analogia

Un layout és el moble on col·loques les coses. Una VBox és una prestatgeria (baldes de dalt a baix); una HBox, un penja-robes (tot en fila); un GridPane, un caseller de columnes i files. Tries el moble segons com vols que quedi la roba.

17.3.3 Exemple mínim

Label etiqueta = new Label("Nom:");
TextField camp = new TextField();
Button boto = new Button("Desa");

VBox arrel = new VBox(10, etiqueta, camp, boto);  // 10 px de separació vertical
arrel.setStyle("-fx-padding: 20;");                // marge interior
ConsellMini-repte

Munta un GridPane amb un mini-formulari: dues etiquetes (“Nom” i “Correu”) a la columna 0 i dos TextField a la columna 1, més un botó “Enviar” a sota.

17.4 Esdeveniments i controladors

17.4.1 Explicació

Un esdeveniment és una cosa que passa a la interfície: un clic, una tecla premuda, un canvi de text. Un controlador (event handler) és el codi que respon a aquell esdeveniment. Dius al control “quan et cliquin, executa això”, i això sol ser una lambda (Capítol 16).

17.4.2 Analogia

És el timbre de casa: el timbre (control) genera un esdeveniment (algú prem el botó) i tu tens preparada una reacció (controlador): anar a obrir. Mentre ningú no prem, no fas res; quan sona, reacciones.

17.4.3 Exemple mínim

Button boto = new Button("Saluda");
Label sortida = new Label();

// Quan es clica el botó, executa la lambda (el controlador)
boto.setOnAction(event -> {
    sortida.setText("Hola! Has clicat el botó.");
});

flowchart LR
    U["Usuari clica"] --> E["Esdeveniment (ActionEvent)"]
    E --> H["Controlador setOnAction(...)"]
    H --> A["Acció: actualitza la GUI"]

Un exemple una mica més complet, que llegeix un camp de text i respon:

TextField campNom = new TextField();
Button boto = new Button("Saluda");
Label sortida = new Label();

boto.setOnAction(e -> {
    String nom = campNom.getText().trim();
    if (nom.isEmpty()) {
        sortida.setText("Escriu el teu nom primer.");
    } else {
        sortida.setText("Hola, " + nom + "!");
    }
});

VBox arrel = new VBox(10, campNom, boto, sortida);
AlertaError típic: bloquejar el fil de la interfície

Tota la GUI de JavaFX viu en un únic fil, el JavaFX Application Thread. Si dins d’un controlador fas una tasca llarga (llegir un fitxer enorme, una consulta a Internet, un Thread.sleep), la finestra es congela i deixa de respondre. Les tasques llargues s’han de fer en un fil a part (amb Task o Service). De moment, mantén els controladors ràpids.

ConsellMini-repte

Afegeix a l’exemple un segon botó “Esborra” que buidi el camp de text i l’etiqueta de sortida.

17.5 El patró MVC: separar dades, aparença i lògica

17.5.1 Explicació

A mesura que una aplicació creix, barrejar-ho tot en una classe es torna ingovernable. El patró MVC (Model-Vista-Controlador) reparteix les responsabilitats en tres parts:

  • Model — les dades i les regles de negoci. No sap res de la interfície. Són les classes del Capítol 12: Soci, Llibre, Biblioteca
  • Vista — la interfície: què veu l’usuari (els controls JavaFX). No conté lògica de negoci.
  • Controlador — el pont: reacciona als esdeveniments de la vista, consulta o modifica el model i actualitza la vista.

17.5.2 Analogia

Pensa en un restaurant: la cuina i el rebost són el model (on són els ingredients i les receptes); la sala amb les taules és la vista (el que veu el client); el cambrer és el controlador (pren la comanda de la sala, la porta a la cuina i torna amb el plat). Cadascú fa la seva feina i no es trepitgen.

flowchart LR
    V["Vista<br/>(controls JavaFX)"] -->|esdeveniment| C["Controlador"]
    C -->|consulta/modifica| M["Model<br/>(Soci, Llibre...)"]
    M -->|dades| C
    C -->|actualitza| V

17.5.3 Per què separar-ho

  • Pots canviar la vista (redissenyar la finestra) sense tocar la lògica.
  • Pots provar el model sense obrir cap finestra (tests automàtics).
  • Diverses persones treballen en paral·lel: qui fa disseny i qui fa lògica.
  • El codi és més fàcil d’entendre i mantenir.

17.5.4 Exemple mínim (esquema)

// MODEL: dades i regles, sense res de JavaFX
public class Comptador {
    private int valor = 0;
    public void incrementa() { valor++; }
    public int getValor() { return valor; }
}
// VISTA + CONTROLADOR (junts, per simplicitat)
public class AppComptador extends Application {
    private final Comptador model = new Comptador();   // el MODEL

    @Override
    public void start(Stage escenari) {
        Label pantalla = new Label("0");               // VISTA
        Button boto = new Button("+1");

        // CONTROLADOR: reacciona, toca el model, actualitza la vista
        boto.setOnAction(e -> {
            model.incrementa();
            pantalla.setText(String.valueOf(model.getValor()));
        });

        VBox arrel = new VBox(10, pantalla, boto);
        escenari.setScene(new Scene(arrel, 200, 150));
        escenari.setTitle("Comptador MVC");
        escenari.show();
    }

    public static void main(String[] args) { launch(args); }
}
NotaFXML: separar encara més la vista

En projectes grans, la vista es descriu en un fitxer FXML (una mena d’HTML per a JavaFX) i el controlador es connecta amb l’anotació @FXML. Així el disseny visual queda en un fitxer a part i es pot editar amb l’eina visual Scene Builder. A la pràctica del tema treballaràs aquesta separació.

ConsellMini-repte

Amplia el Comptador amb un mètode decrementa() al model i un botó “−1” a la vista. Fixa’t que només toques el model i afegeixes un controlador: la resta no canvia. Això és la força de l’MVC.

17.6 Configurar JavaFX amb Maven

17.6.1 Explicació

Com que JavaFX ja no ve dins del JDK, cal declarar-lo com a dependència al projecte. Amb Maven (vegeu l’@sec-… de l’Annex B) s’afegeix al pom.xml i s’executa amb el connector de JavaFX.

17.6.2 Exemple mínim de pom.xml

<dependencies>
    <dependency>
        <groupId>org.openjfx</groupId>
        <artifactId>javafx-controls</artifactId>
        <version>21</version>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-maven-plugin</artifactId>
            <version>0.0.8</version>
            <configuration>
                <mainClass>cat.dam.HolaFX</mainClass>
            </configuration>
        </plugin>
    </plugins>
</build>

Amb això, executes l’aplicació amb:

mvn clean javafx:run
NotaOn aprofundir
  • Annex B (B-projecte-java-maven.qmd): estructura d’un projecte Maven i pom.xml complet.
  • Annex E (E-maven-gradle.qmd): Maven vs Gradle i quan usar cadascun.

Aquesta pràctica t’obligarà a tocar el pom.xml: no et saltis els annexos.

AlertaError típic: “JavaFX runtime components are missing”

Aquest error surt quan intentes executar la classe directament (com un main normal) sense el mòdul de JavaFX al classpath. La solució és executar via mvn javafx:run (o configurar el mòdul a l’IDE), no amb “Run” a pèl sobre la classe.

17.7 Resum

  • Una GUI és dirigida per esdeveniments: el programa espera i reacciona, al contrari que la consola, seqüencial.
  • L’arquitectura de JavaFX encaixa tres nivells: Stage (finestra) → Scene (contingut) → Node (cada element). Tota app estén Application i implementa start(Stage).
  • Els controls es col·loquen dins de layouts (VBox, HBox, BorderPane, GridPane, StackPane), no amb coordenades manuals.
  • Un esdeveniment (clic, tecla) es respon amb un controlador, sovint una lambda (setOnAction). No bloquegis el fil de la interfície amb tasques llargues.
  • El patró MVC separa Model (dades), Vista (interfície) i Controlador (pont), cosa que fa el codi mantenible, provable i escalable.
  • JavaFX s’afegeix com a dependència Maven i s’executa amb mvn javafx:run (annexos B i E).
  • Aquest tema s’avalua per pràctica, no per examen escrit: l’important és construir una app que funcioni.

17.8 Per practicar

Important

Les activitats d’aquest capítol són avaluables i es lliuren com a pràctica del tema (practiques/tema-16/).

  1. Conversor d’unitats. Fes una finestra amb un camp per introduir graus Celsius i un botó que mostri els Fahrenheit en una etiqueta. Valida que l’entrada sigui un número.
  2. Calculadora d’IMC. Demana pes i alçada en dos camps, calcula l’índex de massa corporal en clicar i mostra el resultat amb un missatge segons el rang. Separa el càlcul (model) de la vista.
  3. Llista de tasques (to-do). Un camp de text, un botó “Afegir” i una ListView que mostri les tasques. Guarda-les en una List<String> (Capítol 15) al model.
  4. Refactor a MVC. Agafa la calculadora d’IMC de l’exercici 2 i reorganitza-la en tres classes ben separades: model, vista i controlador. Justifica breument què has posat a cada part.
  5. Ampliació de BiblioTech. Posa una interfície gràfica senzilla al projecte model: una finestra per donar d’alta llibres i una ListView/TableView per llistar-los, sobre el model de col·leccions que ja tens del Capítol 15.