Sei sulla pagina 1di 42

Fondamenti di JAVAFX

14.1 Introduzione
JavaFX è un eccellente strumento pedagogico per l'apprendimento della programmazione orientata agli oggetti.

JavaFX è un nuovo framework per lo sviluppo di programmi GUI Java. L'API JavaFX è un eccellente esempio di
come vengono applicati i principi della programmazione orientata agli oggetti. Questo capitolo ha due scopi.
Innanzitutto, presenta le basi della programmazione JavaFX. In secondo luogo, utilizza JavaFX per illustrare la
progettazione e la programmazione orientata agli oggetti. Nello specifico, questo capitolo introduce il framework
di JavaFX e illustra i componenti della GUI di JavaFX e le loro relazioni. Impareremo come sviluppare semplici
programmi GUI utilizzando riquadri di layout, pulsanti, etichette, campi di testo, colori, caratteri, immagini,
visualizzazioni di immagini e forme.

14.2 JavaFX vs Swing e AWT


Swing e AWT sono stati sostituiti dalla piattaforma JavaFX per lo sviluppo di rich Internet application RIA
(applicazioni web che possiedono le caratteristiche e le funzionalità delle applicazioni desktop).

Quando è stato introdotto Java, le classi GUI erano raggruppate in una libreria nota come Abstract Windows
Toolkit (AWT). AWT va bene per lo sviluppo di semplici interfacce utente grafiche, ma non per lo sviluppo di
progetti GUI completi. Inoltre, AWT è soggetto a bug specifici della piattaforma. I componenti dell'interfaccia
utente AWT sono stati sostituiti da una libreria più robusta, versatile e flessibile nota come componenti Swing. I
componenti Swing vengono disegnati direttamente su elementi canvas (tele) utilizzando il codice Java. I
componenti Swing dipendono meno dalla piattaforma di destinazione e utilizzano meno risorse GUI native. Swing
è progettato per lo sviluppo di applicazioni GUI desktop. Ora è sostituito da una piattaforma GUI completamente
nuova nota come JavaFX. JavaFX incorpora moderne tecnologie GUI per consentire lo sviluppo di rich Internet
application. Una rich Internet application (RIA) è un'applicazione Web progettata per fornire le stesse
caratteristiche e funzioni normalmente associate alle applicazioni desktop. Un'applicazione JavaFX può essere
eseguita senza problemi sia su un desktop che da un browser Web. Inoltre, JavaFX fornisce un supporto multi-
touch per dispositivi abilitati al tocco come tablet e smartphone. JavaFX dispone di un supporto integrato per
l´animazione 2D e 3D, per la riproduzione video e audio e funziona sia come applicazione stand-alone che da un
browser. Nel seguito impareremo come progettare le GUI in Java utilizzando JavaFX per due motivi. Innanzitutto,
JavaFX è molto più semplice da imparare e da utilizzare per i nuovi programmatori Java. In secondo luogo, Swing
è essenzialmente morto, perché non riceverà ulteriori miglioramenti. JavaFX è il nuovo strumento GUI per lo
sviluppo di rich Internet application multipiattaforma per computer desktop, dispositivi portatili e per il Web.

14.3 La struttura e configurazione di JavaFX


La classe astratta javafx.application.Application definisce il framework essenziale per la scrittura di programmi
JavaFX.

Iniziamo scrivendo un semplice programma JavaFX che illustra la struttura di base di un programma JavaFX. Ogni
programma JavaFX è definito in una classe che estende javafx.application.Application, come mostrato nel
Listato 14.1:
1
Si può testare ed eseguire il programma da una finestra di comando o da un IDE come NetBeans o Eclipse. Un
esempio di esecuzione del programma è mostrato nella Figura 14.1. Prima di eseguire il programma è necessario
configurare l´ambiente di sviluppo per poter eseguire applicazioni JavaFX (riferirsi agli Appunti Java n.ro 14 -
Supplemento A - Configurare JavaFX su Eclipse).

Il metodo launch (riga 22) è un metodo statico definito nella classe Application per avviare un'applicazione
JavaFX autonoma. Il metodo main (righe 21-23) non è necessario se si esegue il programma da riga di comando.
Potrebbe essere necessario avviare un programma JavaFX da un IDE con un supporto JavaFX limitato. Quando si
esegue un'applicazione JavaFX senza un metodo main, la JVM richiama automaticamente il metodo launch per
eseguire l'applicazione.
La classe main sovrascrive il metodo launch definito in javafx.application.Application (riga 8). Dopo l'avvio di
un'applicazione JavaFX, la JVM costruisce un'istanza della classe usando il suo costruttore no-arg e invoca il suo
metodo start. Il metodo start normalmente posiziona i controlli dell'interfaccia utente in un scena e mostra la
scena in uno stage (palco o finestra), come mostrato nella Figura 14.2a.
La riga 10 crea un oggetto Button e lo posiziona in un oggetto Scene (riga 11). Un oggetto Scene può essere
creato utilizzando il costruttore Scene (nodo, width, height). Questo costruttore specifica la larghezza e l'altezza
della scena e posiziona il nodo (in questo caso il Button) nella scena.
Un oggetto Stage è una finestra. All'avvio dell'applicazione viene automaticamente creato dalla JVM un oggetto
Stage detto palco principale o finestra principale. La riga 13 colloca la scena nella finestra principale e la riga 14

2
mostra il palco principale. JavaFX denomina le classi Stage e Scene rifacendosi ai termini che si usano in teatro.
Si può pensare al palco come la piattaforma sulla quale vengono posizionate le scene e ai nodi come agli attori
che recitano nelle scene.
È possibile creare più finestre, se necessario. Il programma JavaFX nel Listato 14.2, visualizza due finestre come
mostrato nella Figura 14.2b.

Si noti che il metodo main è stato omesso nel listato poiché è identico per ogni applicazione JavaFX. D'ora in
poi, per brevità, non elencheremo il metodo main nel nostro codice sorgente JavaFX.

Per impostazione predefinita, l'utente può ridimensionare le finestre. Per impedire all'utente di
ridimensionare la finestra, si può invocare il metodo stage.setResizable (false).

3
14.4 Panes (Riquadri), controlli UI (User Interface) e Shapes (Forme)
Panelli, controlli dell'interfaccia utente e forme sono sottotipi di Node.

Quando eseguiamo MyJavaFX nel Listato 14.1, la finestra viene visualizzata come mostrato in Figura 14.1. Il
pulsante è sempre centrato nella scena e occupa l'intera finestra indipendentemente da come viene
ridimensionata. È possibile risolvere il problema impostando le proprietà di posizione e dimensione di un
pulsante. Tuttavia, un approccio migliore consiste nell'utilizzare classi contenitore, chiamate Panes (riquadri),
per disporre automaticamente i nodi nella posizione e dimensione desiderate. Si posizionano i nodi all'interno di
un riquadro e quindi si posiziona il riquadro in una scena. Un nodo è un componente visivo come può essere una
forma (Shapes), un visualizzatore di immagini (ImageView), un controllo dell'interfaccia utente o un riquadro
(pane). Una forma si riferisce a un testo, una linea, un cerchio, un'ellisse, un rettangolo, un arco, un poligono,
una polilinea, ecc. Un controllo UI (dell'interfaccia utente) fa riferimento a un'etichetta, un pulsante, una casella
di controllo, un pulsante di opzione, un campo di testo, un'area di testo, ecc. essere visualizzato in un palco
(stage), come mostrato nella Figura 14.3a. La relazione tra Stage, Scene, Node, Control e Pane è illustrata nel
diagramma UML, come mostrato nella Figura 14.3b.

Si noti che una scena (Scene) può contenere un controllo o un riquadro (Pane), ma non una forma (Shape) o
un'immagine (ImageView). Un riquadro (Pane) può contenere qualsiasi sottotipo di nodo (Node). È possibile
creare una scena utilizzando il costruttore Scene(Parent, width, height) o Scene(Parent). La dimensione della
scena viene decisa automaticamente in quest'ultimo costruttore. Ogni sottoclasse di Node ha un costruttore
senza argomenti per la creazione di un nodo predefinito. Il Listato 14.3 fornisce un programma che inserisce un
pulsante in un riquadro, come mostrato nella Figura 14.4

4
Il programma crea uno StackPane (riga 11) e aggiunge un pulsante come figlio del riquadro (Pane) (riga 12). Il
metodo getChildren () restituisce un'istanza di javafx.collections.ObservableList.ObservableList e si comporta
in modo molto simile a un ArrayList per memorizzare una raccolta di elementi. Invocare il metodo add(e)
aggiunge un elemento all'elenco. Nello StackPane i nodi vengono posizionati al centro del riquadro uno sopra
l'altro. In questo caso c'è un solo nodo nel riquadro. Lo StackPane rispetta la dimensione preferita di un nodo.
Quindi il pulsante viene visualizzato nella sua dimensione preferita. Il Listato 14.4 fornisce un esempio che mostra
un cerchio al centro del riquadro, come mostrato nella Figura 14.5a.

5
Il programma crea un Circle (riga 12) e imposta il suo centro a (100, 100) (righe 13-14), che è anche il centro della
scena, poiché la scena è creata con larghezza e l'altezza di 200 (riga 24). Il raggio del cerchio è impostato su 50
(riga 15). Notiamo che le unità di misura per la grafica in Java sono tutte in pixel. Il colore del tratto (cioè il
colore per disegnare il cerchio) è impostato sul nero (riga 16). Il colore di riempimento (ovvero il colore per
riempire il cerchio) è impostato su bianco (riga 17). È possibile impostare il colore su null per specificare che non
è impostato alcun colore. Il programma crea un riquadro (pane) (riga 20) e posiziona il cerchio nel riquadro (riga
21). Notare che le coordinate dell'angolo superiore sinistro del riquadro sono (0,0) nel sistema di coordinate Java,
come mostrato nella Figura 14.6a, in contrasto con il sistema di coordinate convenzionale dove (0, 0) è al centro
del finestra, come mostrato nella Figura 14.6b. La coordinata x aumenta da sinistra a destra e la coordinata y
aumenta verso il basso nel sistema di coordinate Java.

Il riquadro (pane) viene posizionato nella scena (scene) (riga 24) e la scena (scene) viene impostata nella finestra
(primaryStage) (riga 26). Il cerchio è visualizzato al centro della finestra (primaryStage), come mostrato nella
Figura 14.5a. Tuttavia, se ridimensionate la finestra, il cerchio non è centrato, come mostrato nella Figura 14.5b.
Per visualizzare il cerchio centrato durante il ridimensionamento della finestra, le coordinate x e y del centro del
cerchio devono essere reimpostate al centro del riquadro (pane). Questo può essere fatto usando l'associazione
di proprietà, introdotta nella sezione successiva.
6
14.5 Proprietà di binding (o di associazione)
È possibile associare un oggetto di destinazione a un oggetto di origine. Una modifica nell'oggetto di origine si
rifletterà automaticamente nell'oggetto di destinazione.

JavaFX introduce un nuovo concetto chiamato associazione di proprietà (property binding) che consente a un
oggetto destinazione di essere associato a un oggetto origine. Se il valore nell'oggetto origine cambia, anche
l'oggetto destinazione viene modificato automaticamente. L'oggetto destinazione è chiamato oggetto di
collegamento (binding object) o proprietà di collegamento (binding property) e l'oggetto origine è chiamato
oggetto associabile (bindable object) o oggetto osservabile (observable object). Come osservato nel listato
precedente, la circonferenza non è centrata durante il ridimensionamento della finestra. Per visualizzare la
circonferenza centrata nel riquadro durante il ridimensionamento della finestra, le coordinate x e y del centro
del cerchio devono essere reimpostate al centro del riquadro. Questo può essere fatto associando la proprietà
centerX con la proprietà width/2 del riquadro e la proprietà centerY con la proprietà height/2 del riquadro,
come illustrato nel Listato 14.5.

La classe Circle ha la proprietà centerX per rappresentare la coordinata x del centro del cerchio. Questa
proprietà, come molte proprietà nelle classi JavaFX, può essere utilizzata sia come destinazione che come origine
in una associazione di proprietà (property binding). Un oggetto destinazione è associato alle modifiche
nell'oggetto origine e si aggiorna automaticamente una volta apportata una modifica all'oggetto origine. Un
oggetto destinazione si associa a un'oggetto origine utilizzando il metodo bind come segue:

7
Il metodo bind è definito nell'interfaccia javafx.beans.property.Property. Una proprietà di associazione è
un'istanza di javafx.beans.property.Property. Un oggetto origine è un'istanza dell'interfaccia
javafx.beans.value.ObservableValue. Un ObservableValue è un'entità che racchiude un valore e consente di
osservare il valore per le modifiche.

JavaFX definisce le proprietà di associazione per i tipi e le stringhe primitive. Per un valore di tipo double / float
/ long / int / boolean, la proprietà di associazione è DoubleProperty / FloatProperty / LongProperty /
IntegerProperty / BooleanProperty. Per una stringa, la proprietà di associazione è StringProperty.

Queste proprietà sono anche sottotipi di ObservableValue. Quindi possono anche essere usati come oggetti
origine per le proprietà di associazione. Per convenzione, ogni proprietà di associazione (ad esempio, centerX)
in una classe JavaFX (ad esempio, Circle) ha un metodo getter (ad esempio, getCenterX()) e setter (ad esempio,
setCenterX(double)) per restituire e impostare il valore della proprietà. Ha anche un metodo getter per restituire
la proprietà stessa. La convenzione di denominazione per questo metodo è: il nome della proprietà seguito dalla
parola Property. Ad esempio, il metodo di acquisizione delle proprietà per centerX è centerXProperty().

Si invoca il metodo getCenterX() come metodo getter per i valori, il metodo setCenterX(double) come metodo
setter dei valori e centerXProperty() come metodo getter delle proprietà. Notiamo che getCenterX() restituisce
un valore double mentre centerXProperty() restituisce un oggetto di tipo DoubleProperty. La Figura 14.7a
mostra la convenzione per la definizione di una proprietà di associazione (binding) in una classe generica e la
Figura 14.7b mostra un esempio concreto in cui centerX è una proprietà di binding del tipo DoubleProperty.

Il programma nel Listato 14.5 è lo stesso del Listato 14.4 tranne per il fatto che associa le proprietà centerX e
centerY di Circle alla metà della larghezza e dell'altezza del riquadro (righe 16-17). Notare che
circle.centerXProperty() restituisce centerX mentre pane.widthProperty() restituisce width. Sia centerX che
width sono proprietà di associazione del tipo DoubleProperty. Le classi di proprietà di associazione numerica
come DoubleProperty e IntegerProperty contengono i metodi di addizione, sottrazione, moltiplicazione e
divisione per l'aggiunta, la sottrazione, la moltiplicazione e la divisione di un valore in una proprietà di
associazione e la restituzione di una nuova proprietà osservabile. Quindi, pane.widthProperty().divide(2)
restituisce una nuova proprietà osservabile che rappresenta la metà della larghezza del riquadro. La dichiarazione

equivale a

8
Poiché centerX è associato a width.divide(2), quando la larghezza del riquadro viene modificata, centerX si
aggiorna automaticamente per corrispondere alla larghezza/2 del riquadro pane. Il listato 14.6 fornisce un altro
esempio che mostra l'associazione.

ll programma crea un'istanza di DoubleProperty utilizzando SimpleDoubleProperty(1) (riga 6). Si tenga presente
che DoubleProperty, FloatProperty, LongProperty, IntegerProperty e BooleanProperty sono classi astratte. Le
loro sottoclassi concrete SimpleDoubleProperty, SimpleFloatProperty, SimpleLongProperty,
SimpleIntegerProperty e SimpleBooleanProperty vengono utilizzate per creare istanze di queste proprietà.
Queste classi sono molto simili alle classi wrapper (contenitore) Double, Float, Long, Integer e Boolean con
funzionalità aggiuntive per l'associazione a un oggetto sorgente. Il programma associa d1 con d2 (riga 8). Ora i
valori in d1 e d2 sono gli stessi. Dopo aver impostato d2 a 70,2 (riga 11), anche d1 diventa 70,2 (riga 13).
L'associazione illustrata in questo esempio è nota come associazione unidirezionale. Occasionalmente, è utile
sincronizzare due proprietà in modo che una modifica in una proprietà si rifletta in un altro oggetto e viceversa.
Questa operazione è chiamata associazione bidirezionale. Se la destinazione e l'origine sono entrambe proprietà
di associazione e proprietà osservabili, possono essere associate in modo bidirezionale utilizzando il metodo
bindBidirectional.

14.6 Proprietà e metodi comuni per i nodi


La classe astratta Node definisce molte proprietà e metodi comuni a tutti i nodi.

I nodi condividono molte proprietà comuni. In questa sezione introduciamo due di queste proprietà style e
rotate. Le proprietà di stile in JavaFX sono simili ai fogli di stile (CSS) utilizzati per specificare gli stili per gli
elementi HTML in una pagina Web. Quindi, le proprietà di stile in JavaFX sono chiamate JavaFX CSS. In JavaFX,
una proprietà di stile è definita con un prefisso –fx-. Ogni nodo ha le proprie proprietà di stile. Possiamo trovare
un elenco di tutte queste proprietà al seguente indirizzo: https://docs.oracle.com/javase/8/javafx/api

9
/javafx/scene/doc-files/cssref.html

Per informazioni su HTML e CSS, vedere Appunti JAVA n.ro14-Supplemento B-HTML e Appunti JAVA n.ro14-
Supplemento C-CSS disponibili su moodle. Se non si ha familiarità con HTML e CSS, si può comunque utilizzare
JavaFX CSS.

La sintassi per impostare uno stile è styleName: value. È possibile impostare più proprietà di stile per un nodo
separate da punto e virgola (;). Ad esempio, la seguente dichiarazione

imposta due proprietà CSS JavaFX per una circonferenza. Questa affermazione è equivalente alle seguenti due
affermazioni.

Se si utilizza un CSS JavaFX errato, il programma verrà comunque compilato ed eseguito, ma lo stile verrà
ignorato. La proprietà rotate consente di specificare un angolo in gradi per ruotare il nodo intorno al suo centro.
Se il grado è positivo, la rotazione viene eseguita in senso orario; in caso contrario, viene eseguita in senso
antiorario. Ad esempio, il codice seguente ruota un pulsante (button) di 80 gradi.

Il Listato 14.7 fornisce un esempio in cui si crea un pulsante, se ne imposta lo stile e lo si aggiunge a un riquadro
(pane). Quindi si ruota il riquadro di 45 gradi e se ne imposta lo stile con il colore del bordo rosso e il colore dello
sfondo grigio chiaro, come mostrato nella Figura 14.8

10
Come si vede nella Figura 14.8, la rotazione di un riquadro (pane) fa ruotare anche tutti i nodi che in esso
contenuti. La classe Node contiene molti metodi utili che possono essere applicati a tutti i nodi. Ad esempio, è
possibile utilizzare il metodo contains(double x, double y) per verificare se un punto (x, y) si trova all'interno del
perimetro di un nodo.

14.7 La classe Color


La classe Color può essere utilizzata per creare colori.
JavaFX definisce la classe astratta Paint per disegnare un nodo. La classe javafx.scene.paint.Color è una
sottoclasse concreta di Paint, che viene usata per incapsulare i colori, come mostrato nella Figura 14.9.

Un'istanza della classe Color può essere costruita utilizzando il seguente costruttore:

11
in cui r, g e b specificano un colore dai suoi componenti rosso, verde e blu con valori in va da 0,0 (tonalità più
scura) a 1,0 (tonalità più chiara). Il valore di opacità definisce la trasparenza di un colore compreso tra 0,0
(completamente trasparente) e 1,0 (completamente opaco). Questo è noto come modello RGBA, dove RGBA
sta per rosso, verde, blu e alfa. Il valore alfa indica l'opacità. Per esempio,

La classe Color è immutabile. Una volta creato un oggetto Color, le sue proprietà non possono essere modificate.
Il metodo brighter() restituisce un nuovo oggetto Color con valori più grandi per rosso, verde e blu e il metodo
darker() restituisce un nuovo oggetto Color con valori più piccoli per rosso, verde e blu. Il valore di opacità è lo
stesso dell'oggetto Color originale. Si può anche creare un oggetto Color usando i metodi statici color(r, g, b),
color(r, g, b, opacità), rgb(r, g, b) e rgb(r, g, b, opacità).
In alternativa si può utilizzare uno dei tanti colori standard come BEIGE, BLACK, BLUE, BROWN, CYAN,
DARKGRAY, GOLD, GRAY, GREEN, LIGHTGRAY, MAGENTA, NAVY, ORANGE, PINK, RED, SILVER e WHITE definiti
come costanti nella classe Color. Il seguente codice, ad esempio, imposta il colore di riempimento di un cerchio
su rosso:

14.8 La classe Font


Un Font descrive il nome, il peso e la dimensione del carattere.
È possibile impostare i caratteri per il rendering del testo. La classe javafx.scene.text.Font viene utilizzata per
creare i fonts, come mostrato nella Figura 14.10.

Un'istanza della classe Font può essere costruita usando i suoi costruttori o usando i suoi metodi statici. Un Font
è definito dal nome, dal peso (o spessore del carattere, ad ex. bold, extra bold, thin, medium, light, semi bold,
etc.), dalla posture (sono corsivo, normale) e dalle dimensioni. Times, Courier e Arial sono i esempi di nomi dei
font. È possibile ottenere un elenco dei nomi delle famiglie di font disponibili invocando il metodo statico
getFamilies(). List è un'interfaccia che definisce metodi comuni per una lista. ArrayList è un'implementazione
concreta della classe List. Le posture dei font sono due costanti: FontPosture.ITALIC e FontPosture.REGULAR.
Ad esempio, le seguenti dichiarazioni creano due caratteri.

12
Il Listato 14.8 fornisce un programma che visualizza un'etichetta (Label) utilizzando il carattere (Times New
Roman, grassetto, corsivo di dimensione 20), come mostrato nella Figura 14.11.

13
Il programma crea uno StackPane (riga 14) e vi aggiunge una circonferenza e una Label (righe 21, 27). Queste
due istruzioni possono essere combinate in una singola istruzione:

Uno StackPane posiziona i nodi al centro e i nodi vengono posizionati uno sopra l'altro. Un colore personalizzato
viene creato e impostato come colore di riempimento per il cerchio (riga 20). Il programma crea una Label e ne
imposta il font (riga 25) in modo che il testo nell'etichetta venga visualizzato in Times New Roman, in grassetto,
corsivo e 20 pixel.
Durante il ridimensionamento della finestra, il cerchio e la Label vengono visualizzati al centro della finestra,
perché il cerchio e l'etichetta si trovano nello stack pane. Lo StackPane posiziona automaticamente i nodi al
centro del riquadro. L'oggetto Font è immutabile. Una volta creato un oggetto Font, le sue proprietà non possono
essere modificate.

14.9 Le classi Image e ImageView


La classe Image rappresenta un'immagine grafica, la classe ImageView può essere utilizzata per visualizzare
un'immagine.
La classe javafx.scene.image.Image rappresenta un'immagine grafica e viene utilizzata per il caricamento di
un'immagine da un file specificato o da un URL. Ad esempio, new Image ("image / us.gif") crea un oggetto Image
per l´immagine us.gif posizionata nella directory image sotto la directory in cui si trova il file .class di JAVA mentre
new Image ("http://www.moodle.edu/image/us.gif") crea un oggetto Image per l´immagine disponibile al web
URL specificato. Javafx.scene.image.ImageView è un nodo che consente di visualizzare un'immagine. Un
ImageView può essere creato da un oggetto Image. Ad esempio, il codice seguente crea un ImageView da un
immagine:

In alternativa, si può creare un ImageView direttamente da un file o da un URL come segue:

I diagrammi UML per le classi Image e ImageView sono illustrati nelle Figure 14.12 e 14.13.

ReadOnlyDoubleProperty
ReadOnlyDoubleProperty
ReadOnlyDoubleProperty

14
Il Listato 14.9 mostra un'immagine in tre ImageViewer, come mostrato nella Figura 14.14.

15
Il programma crea un HBox (riga 14). Un HBox è un riquadro che posiziona tutti i nodi orizzontalmente in una
sola riga. Il programma crea un'immagine e quindi un ImageView per visualizzare l´immagine e posiziona
l´ImageView nell'HBox (riga 17).
Il programma poi crea la seconda ImageView (riga 19), ne imposta le proprietà fitHeight e fitWidth (righe 20–
21) e posiziona l´ImageView in HBox (riga 22). Il programma, infine, crea un terzo ImageView (riga 24), lo ruota
di 90 gradi (riga 25) e lo inserisce nell'HBox (riga 26). Il metodo setRotate è definito nella classe Node e può
essere utilizzato per qualsiasi nodo.
Notare che un oggetto Image può essere condiviso da più nodi. In questo caso, è condiviso da tre ImageView.
Tuttavia, un nodo come ImageView non può essere condiviso. Non si può inserire un ImageView più volte in un
riquadro o in una scena.
Si noti che bisogna posizionare l´immagine nella stessa directory del file .class, come mostrato nella figura
seguente.

Se si utilizza l'URL per individuare l´immagine, deve essere indicato il protocollo URL http: //. Così il codice
seguente è sbagliato

e deve essere rimpiazzato da:

14.10 Layout Pane


JavaFX fornisce molti tipi di riquadri per disporre automaticamente i nodi nella posizione e dimensione preferita.
JavaFX fornisce molti tipi di riquadri per organizzare i nodi in un contenitore, come mostrato in Tabella 14.1.
Nelle precedenti sezioni abbiamo già utilizzato i riquadri di layout Pane, StackPane e HBox per contenere i nodi.
In questa sezione analizziamo i riquadri in modo più dettagliato.

16
Abbiamo utilizzato Pane nel Listato 14.4, ShowCircle.java. Un Pane viene solitamente utilizzato come una tela
per la visualizzazione di forme (shapes). Pane è la classe base per tutti i riquadri specializzati. Abbiamo usato un
riquadro specializzato StackPane nel Listato 14.3, ButtonInPane.java. I nodi vengono inseriti al centro di uno
StackPane. Ogni riquadro contiene un elenco per contenere i nodi nel riquadro. Questo elenco è un istanza di
ObservableList, che può essere ottenuta utilizzando il metodo getChildren () del riquadro.
È possibile utilizzare il metodo add (node) per aggiungere un elemento all'elenco, utilizzare addAll (node1,
node2, ...) per aggiungere un numero variabile di nodi al riquadro.

14.10.1 FlowPane
FlowPane dispone i nodi nel riquadro orizzontalmente da sinistra a destra oppure verticalmente dall'alto verso
il basso nell'ordine in cui sono stati aggiunti. Quando una riga o una colonna è piena, viene avviata una nuova
riga o colonna. È possibile specificare il modo in cui vengono posizionati i nodi orizzontalmente o verticalmente
utilizzando una delle due costanti: Orientation.HORIZONTAL o Orientation.VERTICAL. È inoltre possibile
specificare lo spazio tra i nodi in pixel. Il diagramma delle classi per FlowPane è mostrato nella Figura 14.15.
Gli attributi alignment, orientation, hgap e vgap sono proprietà di binding (associazione). Ogni proprietà di
binding in JavaFX ha un metodo getter (ad esempio, getHgap ()) che restituisce il suo valore, un metodo setter
(ad esempio, setHGap (double)) per impostare un valore ed un metodo getter che restituisce la proprietà stessa
(ad esempio, hgapProperty ()). Per un attributo di tipo ObjectProperty <T>, il metodo getter restituisce un valore
di tipo T e il metodo getter per la proprietà restituisce il valore della proprietà di tipo ObjectProperty <T>.

17
Il Listato 14.10 fornisce un programma che utilizza un FlowPane. Il programma aggiunge Label e TextField (campi
di testo) ad un FlowPane, come mostrato nella Figura 14.16.

Il programma crea un FlowPane (riga 13) e imposta la sua proprietà padding (margine interno) con un oggetto
Insets (riga 14). Un oggetto Insets specifica la dimensione del bordo di un riquadro. Il costruttore Insets (11, 12,
13, 14) crea un Insets con le dimensioni del bordo superiore (11), destra (12), inferiore (13) e sinistra (14) in pixel,
come mostrato nella Figura 14.17. Si può anche usare il costruttore Inset (valore) per creare un oggetto Inset
con lo stesso valore per tutti e quattro i lati. Le proprietà hGap e vGap alle righe 15-16 servono a specificare lo
spazio orizzontale e verticale tra due nodi nel riquadro, come mostrato nella Figura 14.17.

18
Ogni FlowPane contiene un oggetto ObservableList per contenere i nodi. Questo elenco può essere ottenuto
utilizzando il metodo getChildren () (riga 19). Per aggiungere un nodo ad un FlowPane è quindi aggiungerlo
all´elenco dei nodi si utilizza il metodo add (node) o addAll (node1, node2, ...).
È inoltre possibile rimuovere un nodo dall'elenco utilizzando il metodo remove (node) o utilizzare il metodo
removeAll () per rimuovere tutti i nodi dal riquadro. Il programma aggiunge le etichette e campi di testo al
riquadro (righe 19–24). L´invocazione di tfMi.setPrefColumnCount (1) imposta il numero di colonne preferito a
1 per il campo di testo MI (riga 22). Il programma dichiara un riferimento esplicito tfMi ad un oggetto TextField
per MI. Il riferimento esplicito è necessario, perché dobbiamo fare riferimento direttamente all'oggetto per
impostare la sua proprietà prefColumnCount.
Il programma aggiunge il riquadro alla scena (riga 27), imposta la scena nello stage (riga 29), e visualizza lo stage
(riga 30). Notiamo che se si ridimensiona la finestra, i nodi vengono automaticamente riorganizzati per adattarsi
al riquadro. Nella Figura 14.16a, la prima riga ha tre nodi, ma in Figura 14.16b, la prima riga ha quattro nodi,
perché la larghezza è stata aumentata. Supponiamo di voler aggiungere l'oggetto tfMi ad un riquadro dieci volte;
appariranno dieci campi di testo nel riquadro? No, un nodo come un campo di testo può essere aggiunto ad un
solo riquadro e una sola volta. Aggiungere un nodo a un riquadro più volte o a riquadri diversi causerà un errore
di runtime.

Nota
Un nodo può essere posizionato solo in un riquadro. Pertanto, la relazione tra un riquadro e un nodo è la
composizione indicata da un diamante pieno, come mostrato in Figura 14.3b.

19
14.10.2 GridPane
Un GridPane dispone i nodi in una formazione a griglia (matrice). I nodi vengono inseriti secondo indici di colonna
e di riga. Il diagramma delle classi per GridPane è mostrato nella Figura 14.18.

Il Listato 14.11 fornisce un programma che utilizza un GridPane. Il programma è simile a quello nel Listato 14.10,
tranne per il fatto che aggiunge tre etichette e tre campi di testo e un pulsante alla posizione specificata in una
griglia, come mostrato nella Figura 14.19.

20
Il programma crea un GridPane (riga 16) e ne imposta le proprietà (riga 17–20). L'allineamento è impostato al
centro (riga 17), e fa sì che i nodi vengano posizionati al centro della griglia. Se si ridimensiona la finestra,
vedremo i nodi rimanere al centro della griglia.
Il programma aggiunge un etichetta alla colonna 0 e alla riga 0 (riga 23). Gli indici di colonna e di riga iniziano da
0. Il metodo add inserisce un nodo alla colonna e alla riga specificate. Non tutte le celle della griglia devono
essere riempite. Un pulsante è posizionato alla colonna 1 e alla riga 3 (riga 30), ma non sono presenti nodi alla
colonna 0 e alla riga 3. Per rimuovere un nodo da un GridPane, si utilizza pane.getChildren ().remove (nodo).
Per rimuovere tutti i nodi, invece si utilizza il metodo pane.getChildren ().removeAll ().
Il programma richiama il metodo statico setHalignment per allineare il pulsante a destra nella cella (riga 31).
Notiamo che la dimensione della scena non è impostata (riga 34). In questo caso, la dimensione della scena viene
automaticamente calcolata in base alle dimensioni dei nodi posti all'interno della scena.

21
14.10.3 BorderPane
Un BorderPane permette di posizionare i nodi in cinque regioni: superiore, inferiore, sinistra, destra e centro,
utilizzando i metodi setTop (nodo), setBottom (nodo), setLeft (nodo), setRight (nodo) e setCenter (nodo). Il
diagramma delle classi per il BorderPane è mostrato nella Figura 14.20.

Il Listato 14.12 fornisce un programma che utilizza un BorderPane. Il programma pone cinque CustomPane nelle
cinque aree del riquadro, come mostrato nella Figura 14.21.

22
Il programma definisce un CustomPane che estende StackPane (riga 31). Il costruttore di CustomPane aggiunge
una Label con il titolo specificato (riga 33), imposta uno stile per il colore del bordo, e imposta il padding
utilizzando un oggetto Insets (riga 35).
Il programma crea un BorderPane (riga 13) e posiziona cinque istanze di CustomPane nelle cinque regioni del
riquadro (righe 16-20). Notiamo che un riquadro è un nodo. Quindi un riquadro può essere aggiunto in un altro
riquadro. Per rimuovere un nodo dalla regione superiore, si può richiamare setTop (null). Se una regione non è
occupata, nessuno spazio verrà assegnato a questa regione.

14.10.4 HBox e VBox


Un HBox permette di disporre i suoi figli in una singola riga orizzontale. Analogamente, un VBox permette di
disporre i suoi figli in una singola colonna verticale. Ricordiamo che un FlowPane permette di disporre i suoi figli
su più righe o più colonne, invece un HBox o un VBox permette di disporre i figli in una sola riga o una sola
colonna. I diagrammi delle classi per HBox e VBox sono mostrati nelle Figure 14.22 e 14.23.

23
Il Listato 14.12 fornisce un programma che utilizza HBox e VBox. Il programma pone due Button in un HBox e
cinque Label in un VBox, come mostrato nella Figura 14.24.

24
Il programma definisce il metodo getHBox (). Questo metodo restituisce un HBox che contiene due Button e un
ImageView (righe 30–39). Il colore di sfondo dell'HBox è impostato su oro utilizzando Java CSS (riga 33). Il
programma definisce il metodo getVBox (). Questo metodo restituisce un VBox che contiene cinque Label (righe
41-55). La prima Label viene aggiunta al VBox nella riga 44 e le altre quattro vengono aggiunte alla riga 51. Il
metodo setMargin viene utilizzato per impostare il margine di un nodo quando viene posizionato all'interno del
VBox (riga 50).

14.11 La classe astratta Shape


JavaFX fornisce molte classi derivate da Shape per disegnare testo, linee, circonferenze, rettangoli, ellissi, archi,
poligoni e polilinee.
La classe Shape è la classe astratta di base che definisce le proprietà comuni per tutte le forme. Tra queste ci
sono le proprietà fill, stroke e strokeWidth. La proprietà fill (riempimento) specifica un colore che riempie
l'interno di una forma. La proprietà stroke (tratto) specifica il colore utilizzato per disegnare il contorno di una
forma. La proprietà strokeWidth specifica la larghezza del contorno di una forma. Questa sezione introduce le
classi Text, Line, Rectangle, Circle, Ellipse, Arc, Polygon e Polyline che consentono di disegnare testi e forme
semplici. Tutte queste sono sottoclassi di Shape, come mostrato nella Figura 14.25.

25
14.11.1 Text
La classe Text definisce un nodo che visualizza una stringa in un punto iniziale (x, y), come mostrato in Figura
14.27a. Un oggetto Text viene solitamente posizionato in un riquadro. Il punto nell'angolo superiore sinistro del
riquadro è (0, 0) e il punto in basso a destra è (pane.getWidth (), pane.getHeight ()). Una stringa può essere
visualizzata su più righe separate da \ n. Il diagramma UML per la classe Text è mostrato nella Figura 14.26. Il
Listato 14.14 fornisce un esempio che utilizza Text, come mostrato in Figura 14.27b.

26
Il programma crea un oggetto Text (riga 18), ne imposta il font (riga 19) e lo posiziona nel riquadro (riga 21). Il
programma crea un altro oggetto Text con più righe (riga 23) e lo posiziona nel riquadro (riga 24). Il programma
crea il terzo oggetto Text (riga 26), ne imposta il colore (fill) (riga 27), la sottolineatura (underline) e una linea
barrata (strikethrough) (linee 28–29) e lo posiziona nel riquadro (linea 30).

14.11.2 Line
Una linea collega due punti mediante quattro parametri startX, startY, endX e endY, come mostrato nella Figura
14.29a. La classe Line definisce una linea. Il diagramma UML per la classe Line è mostrato nella Figura 14.28. Il
Listato 14.15 fornisce un esempio che mostra un testo, come mostrato in Figura 14.29b.

27
Il programma definisce una classe di riquadro personalizzata denominata LinePane (riga 19). Il riquadro
personalizzato crea due linee e associa i punti iniziale e finale della linea alla larghezza e altezza del riquadro
(righe 22-23, 29-30) in modo che i due punti delle linee vengano modificati quando il riquadro viene
ridimensionato.

28
14.11.3 Rectangle
Un Rectangle è definito dai parametri x, y, width, height, arcWidth e arcHeight, come mostrato nella Figura
14.31a. Il punto dell'angolo superiore sinistro del rettangolo è in (x, y) e il parametro aw (arcWidth) è il diametro
orizzontale degli archi all'angolo e ah (arcHeight) è il diametro verticale degli archi all'angolo. La classe Rectangle
definisce un rettangolo. Il diagramma UML per la classe Rectangle è mostrato nella Figura 14.30.

Il Listato 14.16 fornisce un esempio che utilizza i rettangoli, come mostrato nella Figura 14.31b.

29
Il programma crea più rettangoli. Per impostazione predefinita, il colore di riempimento è nero. Quindi un
rettangolo è pieno di colore nero. Il colore del tratto è bianco per impostazione predefinita. Alla riga 17 si imposta
il colore del tratto (stroke) del rettangolo r1 a nero. Il programma crea il rettangolo r3 (riga 26) e ne imposta la
larghezza e altezza dell'arco (righe 27–28). Quindi r3 viene visualizzato come un rettangolo arrotondato. Il
programma crea ripetutamente un rettangolo (riga 33), lo ruota (riga 34), imposta un colore del tratto (linee 35-
36), il suo colore di riempimento diventa bianco (linea 37) e aggiunge il rettangolo al riquadro (riga 38). Se la riga
37 è sostituita dalla riga seguente:

il rettangolo non è riempito con un colore. Quindi i rettangoli vengono visualizzati come mostrato nella Figura
14.31c.

14.11.4 Circle ed Ellipse


Abbiamo usato le circonferenze in diversi esempi all'inizio di questo capitolo. Una circonferenza è definita dai
suoi parametri centerX, centerY e radius. La classe Circle definisce una circonferenza. Il diagramma UML per la
classe Circle è mostrato nella Figura 14.32.

Un'ellisse è definita dai suoi parametri centerX, centerY, radiusX e radiusY, come mostrato nella Figura 14.34a.
La classe Ellipse definisce un'ellisse. Il diagramma UML per La classe Ellipse è mostrato nella Figura 14.33.

Il Listato 14.17 fornisce un esempio che utilizza ellissi, come mostrato nella Figura 14.34b.

30
31
Il programma crea ripetutamente un'ellisse (riga 16), imposta un colore del tratto casuale (righe 17-18), imposta
il colore di riempimento su bianco (riga 19), lo ruota (riga 20) e aggiunge il rettangolo riquadro (riga 21).

14.11.5 Arc
Un arco è concepito come parte di un'ellisse, definito dai parametri centerX, centerY, radiusX, radiusY,
startAngle, length e da un tipo di arco (ArcType.OPEN, ArcType.CHORD o ArcType.ROUND). Il parametro
startAngle è l'angolo iniziale; e lenght è l'angolo di copertura (cioè l'angolo coperto dall'arco). Gli angoli sono
misurati in gradi e rispettano le convenzioni matematiche (cioè 0 gradi è nella direzione est, e gli angoli positivi
indicano la rotazione in senso antiorario dalla direzione est), come mostrato in Figura 14.36a. La classe Arc
definisce un arco. Il diagramma UML per la classe Arc è mostrato nella Figura 14.35. Il Listato 14.18 fornisce un
esempio che visualizza gli archi, come mostrato nella Figura 14.36b.

32
33
Il programma crea un arco arc1 centrato su (150, 100) con raggioX 80 e raggioY 80. L'angolo di partenza è 30 con
lunghezza 35 (linea 15). Il tipo di arco di arc1 è impostato su ArcType.ROUND (riga 18). Poiché il colore di
riempimento di arc1 è rosso, arc1 viene visualizzato pieno di colore rosso. Il programma crea un arco arc3
centrato su (150, 100) con raggioX 80 e raggioY 80. L'angolo iniziale è 30 + 180 con lunghezza 35 (linea 29). Il
tipo di arco di arc3 è impostato su ArcType. CHORD (linea 31). Poiché il colore di riempimento di arc3 è bianco e
il colore del tratto è nero, arc3 viene visualizzato con contorno nero come accordo. Gli angoli possono essere
negativi. Un angolo di partenza negativo ruota in senso orario da est, come mostrato nella Figura 14.37. Un
angolo di copertura negativo spazia in senso orario a partire dall´angolo di partenza. Le due istruzioni seguenti
definiscono lo stesso arco:

La prima istruzione utilizza l'angolo iniziale negativo -30 e l'angolo di copertura negativo pari a -20, come
mostrato nella Figura 14.37a. La seconda affermazione utilizza un angolo iniziale negativo -50 e un angolo di
copertura positivo pari 20, come mostrato nella Figura 14.37b.

Notiamo che i metodi trigonometrici della classe Math utilizzano angoli in radianti, mentre gli angoli nella classe
Arc sono in gradi.

34
14.11.6 Polygon e Polyline
La classe Polygon definisce un poligono che collega una sequenza di punti, come mostrato in Figura 14.38a. La
classe Polyline è simile alla classe Polygon tranne per il fatto che la classe Polyline non viene chiusa
automaticamente, come mostrato nella Figura 14.38b.

Il diagramma UML per la classe Polygon è mostrato nella Figura 14.39. Il Listato 14.19 fornisce un esempio che
crea un esagono, come mostrato nella Figura 14.40.

Polygon

35
Il programma crea un poligono (riga 14) e lo aggiunge a un riquadro (riga 15). Il metodo polygon.getPoints ()
restituisce un ObservableList <Double> (riga 18), che contiene il metodo add per aggiungere un elemento alla
lista (righe 26–27). Notiamo che il valore passato ad add (value) deve essere un valore double. Se viene passato
un valore int, il valore int verrebbe automaticamente incapsulato in un Integer. Ciò causerebbe un errore perché
il ObservableList <Double> è costituito da elementi Double. Il ciclo aggiunge sei punti al poligono (righe 25–28).
Ogni punto è rappresentato dal suo coordinate x ed y. Per ogni punto, la sua coordinata x viene aggiunta
all'elenco del poligono (riga 26) e quindi la sua coordinata y viene aggiunta all'elenco (riga 27). La formula per
calcolare Le coordinate x ed y per ciascun punto nell'esagono sono illustrate nella Figura 14.40a. Se si sostituisce
Polygon con Polyline, il programma visualizza una polilinea come mostrato in Figura 14.40b. La classe Polyline
viene utilizzata allo stesso modo di Polygon tranne per il fatto che il punto iniziale e finale non vengono collegati
in Polyline.

36
14.12 Case Study: La classe ClockPane
Questo caso di studio sviluppa una classe che visualizza un orologio in un riquadro. Il diagramma UML della classe
ClockPane è mostrato nella Figura 14.41.

Supponiamo che ClockPane sia disponibile; scriviamo un programma di test (Listato 14.20) che visualizza un
orologio analogico e utilizza una Label per mostrare l'ora, i minuti e i secondi, come mostrato in Figura 14.42.

37
A questo punto non ci resta che studiare come implementare la classe ClockPane. Si osservi che essendo, per
ipotesi, la classe già disponibile e sapendo che possiamo usare la classe senza sapere come è implementata, se
vogliamo possiamo evitare di studiare come viene implementata.

Per disegnare un orologio, bisogna disegnare un cerchio e tre lancette per i secondi, i minuti e le ore
rispettivamente. Per disegnare una lancetta, è necessario specificare le due estremità della linea. Come mostrato
nella Figura 14.42b, una estremità è posizionata nel centro dell'orologio (centerX, centerY); l'altra estremità, si
trova nel punto (endX, endY), che è determinato dalla seguente formula:

Poiché ci sono 60 secondi in un minuto, l'angolo per la lancetta dei secondi è dato da:

La posizione della lancetta dei minuti è determinata dai minuti e dai secondi. Il valore esatto del minuto più i
secondi è pari a: minute + second / 60. Ad esempio, se il tempo è pari a 3 minuti e 30 secondi, i minuti totali
sono 3,5. Poiché ci sono 60 minuti in un'ora, l'angolo a cui posizionare la lancetta dei minuti è dunque:

Poiché una circonferenza viene divisa in 12 ore, l'angolo della lancetta delle ore è pari a:

Per semplicità nel calcolare gli angoli delle lancette dei minuti e delle ore, è possibile omettere il secondi, perché
sono talmente piccoli che possiamo trascurarli. Pertanto, gli endpoint per la lancetta dei secondi, lancetta dei
minuti e la lancetta delle ore possono essere calcolati come segue:
38
La classe ClockPane è implementata nel Listato 14.21.

39
40
Il programma visualizza un orologio per l'ora corrente utilizzando il costruttore no-arg (lines 18–20) e visualizza
un orologio per l'ora, i minuti e i secondi specificati utilizzando l'altro costruttore (righe 23–28). L'ora, i minuti e
i secondi correnti si ottengono utilizzando la classe GregorianCalendar (righe 86-96). La classe
GregorianCalendar delle API Java consente di creare un'istanza di Calendar per l'ora corrente utilizzando il suo
costruttore no-arg. È quindi possibile utilizzare i suoi metodi get (Calendar.HOUR), get (Calendar.MINUTE) e
get(Calendar.SECOND) per restituire l'ora, i minuti e i secondi da un oggetto Calendar. La classe definisce le
proprietà hour, minute e second per memorizzare l'ora rappresentata nell'orologio (righe 10-12) e utilizza le
proprietà w ed h per rappresentare la larghezza e l'altezza del riquadro dell'orologio (riga 15). I valori iniziali di
w e h sono impostati su 250. I valori w e h si possono reimpostare utilizzando i metodi setW e setH (righe 69,
80). Questi valori vengono utilizzati per disegnare un orologio nel riquadro mediante il metodo paintClock (). Il
metodo paintClock () disegna l'orologio (righe 99–143). Il raggio dell'orologio è proporzionale alla larghezza e
all'altezza del riquadro (riga 101). Al centro del riquadro viene creata una circonferenza che rappresenta
l'orologio (riga 106). Il testo per visualizzare le ore 12, 3, 6, 9 è creato nelle righe 109–112.
Le lancette dei secondi, dei minuti e delle ore sono le linee create alle righe 114–139. Il metodo paintClock ()
inserisce tutte queste forme nel riquadro utilizzando il metodo addAll (riga 142).

41
Poiché il metodo paintClock () viene richiamato ogni volta che viene impostata una nuova proprietà (hour,
minute, second, w e h) (righe 27, 38, 49, 60, 71, 82, 95), prima di aggiungere nuovi contenuti al riquadro, i vecchi
contenuti vengono cancellati (riga 141).

42

Potrebbero piacerti anche