Java Tutorial - Parte 2 0.1
Un tutorial per esempi
Caricamento in corso...
Ricerca in corso...
Nessun risultato
La Graphic User Interface

Introduzione alla GUI

I programmi che abbiamo scritto nella prima parte di questo tutorial sono programmi cosidetti CLI (Command Line Interface = interfaccia a riga di comando) la cui caratteristica principale è quella di essere procedurali: il programma inizia con la prima istruzione del metodo main e termina quando il metodo main finisce.
Schematicamente, questo è un programma CLI, ricordate?

public class Main
{
public static void main( String[] args )
{
parseCmdLine( args ); // interpreta la command-line
... elaborazione ...
}
}

Una applicazione GUI, invece, non è procedurale; il suo metodo main crea la interfaccia grafica e poi ... non fa null'altro. Le operazioni propedeutiche della applicazione vengono eseguite solo se intervengono degli eventi che, solitamente, vengono innescati dal utente quando preme un bottone, o sceglie un elemento da una lista e seleziona un comando da un menù.
Il paradigma di una GUI è il cosidetto event driven programming (programmazione guidata dagli eventi); gli venti che non si limitano alla interazione con l'utente: un evento può essere innescato, per esempio, quando si ricevono dati su un canale di comunicazione.

Application Program Interface

Per creare una applicazione GUI non possiamo più usare i metodi che abbiamo usato in passato: il metodo System.out.println che stampava un messaggio sul terminale, in una GUI non esiste perchè ... non esiste alcun terminale! Anche l'oggetto System.in che rappresenta lo standard input (la tastiera) e che abbiamo usato in passato per ottenere l'input dall'utente, non esiste in una GUI.

Certo è che non possiamo gestire l'output sullo schermo pilotando direttamente l'hardware soprattutto perchè:

  • dovremmo scrivere montagne di codice per gestire i vari tipi di schermo
  • solo il sistema operativo può gestire l'hardware del computer in quanto questa operazione deve essere eseguita con i privilegi del kernel

Quindi anche in una GUI vengono messe a disposizione del programmatore le funzioni per eseguire l'I/O da e verso le periferiche: queste funzioni sono spesso scritte in linguaggio "C" e sono note col nome di Application Program Interface (in breve, API).

Ogni sistema operativo grafico possiede le proprie specifiche API ed esse non sono compatibili tra i sistemi: Windows™ ha le sue specifiche API mentre su altre piattaforme, come per esempio Linux, ogni ambiente grafico ha le sue proprie API: l'ambiente grafico KDE, per esempio, possiede API diverse da GNOME (un altro ambiente grafico Linux) ed entrambe sono diverse dal sistema grafico unix che è conosciuto col nome di "X11".

Aprite il file hellow.c che trovate nella sottocartella /javatutor2/projects/hellow/. Si tratta di un sorgente in linguaggio "C" che visualizza una finestra di dialogo modale. Questo è il contenuto del file:

#include <windows.h>
int WINAPI
WinMain (HINSTANCE hInstance, HINSTANCE hPrevInst, LPTSTR lpCmdLine, int nShowCmd)
{
MessageBoxW (NULL, L"Hello World!", L"hello", MB_OK | MB_ICONINFORMATION);
return 0;
}

Aprite il terminale e spostatevi nella cartella citata più sopra e compilate il sorgente usando il compilatore MinGW (se non lo avete installato rileggete il capitolo Gli strumenti di sviluppo). Compilate il sorgente col seguente comando; fate attenzione a specificare la opzione -mwindows e poi mandatelo in esecuzione:

\javatutor2\projects\hellow>gcc -o hellow.exe hellow.c -mwindows
\javatutor2\projects\hellow>hellow.exe
\javatutor2\projects\hellow>

Otterrete una finestra con il messaggio "Hello World". Per chiuderla, clikkate sul bottone di comando "OK":

Quella che avete eseguito è una applicazione GUI minimale che visualizza una message box di tipo modale. Voglio attirare la vostra attenzione sulla prima riga del listato sorgente:

#include <windows.h>

Si tratta di una direttiva del preprocessore del linguaggio "C" che ingloba nel sorgente un intero file: il file windows.h che rappresenta il file header. Questo file contiene le dichiarazioni dei tipi di dato e delle funzioni specifiche per il sistema operativo Windows: quello che voglio farvi capire è che il listato sorgente scritto poc'anzi è specifico per Windows e non potrà mai compilare su una piattaforma GUI diversa da Windows.

Ambienti cross-platform

Per superare questa limitazione sono nati diversi ambienti cosidetti cross platform (=multi piattaforma) per mezzo dei quali è possibile scrivere una applicazione GUI che compila su diverse piattaforme come per esempio Windows, Linux e persino Apple.
Questi ambienti sono normalmente basati su linguaggi object-oriented come il "C++" o il "C#" dal momento che questi linguaggi riescono ad astrarre le funzioni a basso livello specifiche della API. Esistono molti ambienti di questo tipo ma i più famosi (e più usati) sono, al momento attuale e per quanto ne so, tre:

  • GTK: è un progetto libero ed open source gestito da GNOME e da una comunità attiva di collaboratori. GTK è rilasciato secondo i termini della GNU Lesser General Public License, una licenza particolarmente permissiva.
  • Qt: è un framework di sviluppo di applicazioni multipiattaforma come Linux, Windows, macOS, Android o sistemi embedded. Questa libreria crea applicazioni native quindi molto veloci. E' disponibile sia sotto licenza commerciale che open-source.
  • wxWidgets: è una libreria C++ che consente agli sviluppatori di creare applicazioni per Windows, macOS, Linux e altre piattaforme con una singola base di codice. Ha binding di linguaggio popolari per Python, Ruby, Lua, Perl e molti altri linguaggi.

La libreria GUI di Java

Il linguaggio Java non ha bisogno di ambienti di sviluppo multi-piattaforma poichè è il linguaggio stesso ad essere multi-piattaforma. Java non compila in modo nativo: esso crea un codice intermedio detto bytecode che viene interpretato da una Java Virtual Machine (JVM).
Pertanto è sufficiente implementare una JVM per la specifica piattaforma hardware/software ed il codice creato dal compilatore Java funzionerà su qualsiasi piattaforma indipendentemente dal linguaggio nativo usato su detta piattaforma: e questo si applica sia alle applicazioni CLI che alle applicazioni GUI.

Java fornisce nella propria libreria standard un framework (=struttura) potente e flessibile chiamato Swing costituito da decine e decine di classi Java che incapsulano tutti gli aspetti di programmazione di una GUI.
Swing fà parte del modulo java.desktop e consiste di diversi packages che gestiscono la GUI mediante classi dedicate; Swing è il successore di AWT (Abstract Window Toolkit) un framework Java per lo sviluppo di applicazioni GUI desktop sul quale, peraltro, lo stesso Swing si basa.
Entrambi i toolkit (AWT e Swing) fanno parte del modulo java.desktop. Per maggiori informazioni vedi il modulo java.desktop.

Un esempio di app Java

Aprite il file HelloWorldSwing.java che trovate nella cartella javatutor2/projects/hellow.

/*
* HelloWorldSwing.java
*/
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JLabel;
public class HelloWorldSwing {
private static void createAndShowGUI() {
// crea il frame principale
JFrame frame = new JFrame("HelloWorldSwing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// aggiunge al 'content-panel' la label "hello world"
JPanel contentPane = new JPanel();
JLabel label = new JLabel("Hello World");
contentPane.add( label );
// imposta il content-pane nel frame principale
// e visualizza la finestra
frame.add( contentPane );
//frame.pack();
frame.setSize( 400, 200 );
frame.setVisible(true);
}
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}

Compilate il sorgente e mandatelo in esecuzione:

javatutor2\projects\hellow>javac HelloWorldSwing.java 
javatutor2\projects\hellow>java -ea HelloWorldSwing

otterrete la vostra prima applicazione GUI scritta in Java:

Per chiudera la applicazione clikkate sul bottone di chiusura in alto a destra.
Analizziamo il codice che abbiamo scritto:

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JLabel;

Queste righe sono abbastanza facili: abbiamo importato le classi che rappresentano:

  • JFrame: il frame principale della finestra; questo componente grafico possiede alcune caratteristiche speciali rispetto agli altri componenti grafici; esso può avere un bordo, una barra del titolo e alcuni bottoni speciali come il bottone di chiusura della finestra. Il frame può anche possedere una barra del menù.
  • JPanel: questa classe rappresenta un elemento GUI generico che non ha bordo nè decorazioni speciali (che invece il frame possiede); caratteristica particolare di questo componente è che può contenere altri componenti e che esso viene impostato come contenuto di un frame
  • Jlabel: questa classe rappresenta un componente speciale; una label (=etichetta). Le labels non hanno alcuna funzionalità speciale (come per invece hanno i buttons) se non quella di visualizzare una stringa di testo o una icona. Nel nostro caso la stringa "Hello World"

Passiamo ora ad analizzare il main:

public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}

Nel metodo principale vediamo un costrutto piuttosto particolare: abbiamo richiamato il metodo statico invokeLater della classe SwingUtilities. Questo metodo accetta come parametro un oggetto di classe Runnable o meglio, un oggetto di una classe che implementi Runnable (ebbene si, questa non è una classe ma una interfaccia).

Poichè Runnable è una interfaccia qualsiasi classe che la implementi deve definire tutti i metodi dichiarati nella interfaccia stessa (che vengono anche chiamati metodi astratti). L'unico metodo dichiarato in Runnable è il metodo run che non ha parametri e ritorna void.
Pertanto dovremo scrivere una classe che implementa Runnable e che dovrebbe essere passata come argomento al metodo invokeLater:

public class MyRunnable()
{
public void run()
{
createAndShowGUI();
}
}
...
javax.swing.SwingUtilities.invokeLater(new MyRunnable());

Allo scopo di rendere il codice più conciso, Java permette al programmatore di definire le cosidette classi anonime. Si tratta in sostanza di sostituire al costrutto new MyRunnable scritto proprio qui sopra, la definizione stessa della classe MyRunnable omettendone il nome dal momento che esso non è nemmeno più necessario.
La classe così definita non ha un nome proprio ed è per questo che viene chiamata classe anonima.

Proseguendo nella analisi del codice arriviamo al metodo createAndShowGUI:

private static void createAndShowGUI() {
// crea il frame principale
JFrame frame = new JFrame("HelloWorldSwing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

La prima riga del codice costruisce un frame passando come argomento aò costruttore la stringa "HelloWorldSwing" che rappresenta il titolo del frame; esso viene visualizzato nella barra del titolo.

La seconda riga di codice imposta la azione che deve essere compiuta quando l'utente clikka il bottone di chiusura della finestra (la "X" che appare solitamente nell'angolo in alto a destra nella barra del titolo). La costante EXIT_ON_CLOSE specifica che la azione da eseguire è la chiusura della applicazione; questo è il comportamento normale per tutte le applicazioni.

// aggiunge al 'content-panel' la label "hello world"
JPanel contentPane = new JPanel();
JLabel label = new JLabel("Hello World");
contentPane.add( label );

Proseguendo nella analisi del metodo createAndShowGUI troviamo le righe che creano il pannello dei contenuti che rappresenta l'area interna del frame.
Dovete sapere che il frame non ha alcun contenuto: un frame è il bordo della finestra e le due barre: del titolo e del eventuale menù ma tutto ciò che vi è all'interno è assolutamente trasparente; senza un pannello dei contenuti la vostra applicazione visualizzerebbe solo il bordo e le barre e all'interno voi vedreste solo tutto ciò che già si trovava sullo schermo.

Ecco perchè dobbiamo creare un content pane (= annello dei contenuti) nel quale possiamo aggiungere i componenti da visualizzare: le etichette, i bottoni di comando, i bottoni radio e le check box, le liste, etc.
La classe che rappresenta un pannello dei contenuti è la JPanel che può essere costruita senza alcun argomento: per aggiungere i componenti si usa il metodo add. Ed è proprio con questo metodo che aggiungiamo la JLabel che visualizza la stringa.

// imposta il content-pane nel frame principale
// e visualizza la finestra
frame.add( contentPane );

Il content pane deve essere aggiunto al JFrame; è possibile usare il metodo add a questo scopo oppure anche il metodo setContentPane.

//frame.pack();
frame.setSize( 400, 200 );
frame.setVisible(true);

Il metodo createAndShowGUI termina con le righe che impostano la dimensione del frame a 400 x 200 pixels e lo rende visibile sullo schermo.

E' necessario impostare una dimensione del frame in una applicazione? La risposta è: NO, anzi, è meglio lasciare calcolare la dimensione del content-pane alla libreria Java e dimensionare il frame di conseguenza.
Questa operazione si può ottenere con il metodo pack della clssse JFrame (che nel listato è stata commentata). Per vederne l'effetto non divete fare altro che commentare la riga che imposta le dimensione del frame e togliere il commento alla riga che contiene l'istruzione frame.pack().
Poichè la label che contiene la stringa "HelloWorld" è di dimensioni molto ridotte otterrete una finestra minuscola sullo schermo; ma questo è proprio il risultato del metodo pack: dimensionare il frame in modo che esso contenga solo i componenti da visualizzare.

Che significa invokeLater?

Vi chiederete per quale oscuro motivo abbiamo usato questo metodo per richiamare il metodo createAndShowGUI. Non potevamo semplicemente scrivere?

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

Ebbene, no! Il motivo per cui abbiamo dovuto usare invokeLater vi è al momento di difficile comprensione ma sappiate che una applicazione GUI si compone di due threads distinti:

  • il primo thread è quello in cui la applicazione esegue la inizializzaione ed è costituito dal solo metodo main
  • un secondo thread viene fatto partire dalla libreria Swing nel quale vengono elaborati tutti gli eventi della GUI. Questo thread è chiamato Event Dispacthing Thread (EDT)

Voglio attirare la attenzione del lettore sul fatto che il disegno di un componente della GUI, compreso lo stesso frame, rappresenta un evento e che, di conseguenza, DEVE essere eseguito nel EDT.

Torneremo su questo argomento in Versione 0.5: il giocatore A.I. nel quale vi renderete conto che qualsiasi evento che ha impatto sulla GUI deve essere inoltrato al EDT; in caso contrario il nostro codice non avrà alcun effetto visivo.

Argomento precedente Argomento successivo - Indice Generale