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 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,SceneiNode. - 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 |
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’unStageen 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.
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.
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.
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 interiorMunta 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);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.
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); }
}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ó.
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- Annex B (
B-projecte-java-maven.qmd): estructura d’un projecte Maven ipom.xmlcomplet. - 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.
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énApplicationi implementastart(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
Les activitats d’aquest capítol són avaluables i es lliuren com a pràctica del tema (practiques/tema-16/).
- 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.
- 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.
- Llista de tasques (to-do). Un camp de text, un botó “Afegir” i una
ListViewque mostri les tasques. Guarda-les en unaList<String>(Capítol 15) al model. - 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.
- Ampliació de BiblioTech. Posa una interfície gràfica senzilla al projecte model: una finestra per donar d’alta llibres i una
ListView/TableViewper llistar-los, sobre el model de col·leccions que ja tens del Capítol 15.