Aqua
è l'interfaccia utente dei computer Macintosh
e il codice che andremo a sviluppare nel corso dell'articolo
è orientato ad ottenere con Swing una barra degli
strumenti il più possibile simile a quella di
Apple. Ma, si potrebbe obiettare, Swing non possiede
il pluggable look&feel, e di conseguenza, non è
già progettato per visualizzare tutti gli elementi
di una interfaccia utente con widget in tutto e per
tutto uguali a quelli nativi? In realtà, come
molti lettori sapranno, le varie implementazioni dei
look&feel sono lontane dall'essere perfette. In
ambito Mac OS X, Apple ha fatto un ottimo lavoro, anche
se anche in questo caso non è perfetto. In alcuni
piccoli particolari, come l'evidenziazione delle check
box, penso che la cosa sia voluta. Come se l'utente
dovesse capire che il programma è Java, e non
nativo Cocoa. Un modo per limitare il potenziale successo
della piattaforma di SUN, che è comunque alternativa
rispetto a quella nativa di Apple.
Si vuole dunque tentare di realizzare qualcosa di meglio
di quello che ha potuto fare l'azienda di Cupertino,
raggiungendo la perfezione grafica? No, in realtà
in questa serie di articoli si vogliono approfondire
le API Swing e mostrare tecniche di programmazione Java.
Infatti, si vedrà come manipolare l'aspetto dei
widget con funzionalità più avanzate rispetto
al normale uso che solitamente se ne fa.
Figura 1 - Un esempio di finestra di proprietà
Toolbar
Aqua
In Figura 1 si può vedere un esempio dell'interfaccia
utente standard di Aqua in merito alle finestre di proprietà.
Queste permettono di impostare le opzioni per una determinata
applicazione, ed hanno la caratteristica di possedere
una barra di icone in alto. Se si vuole ottenere una
barra degli strumenti, in Swing è normale utilizzare
la classe JToolBar, che però non produce affatto
un aspetto uguale a quello in Figura 1.
Figura 2 - Una toolbar Swing con il look&feel
Aqua
Come
si vede in Figura 2, infatti, ci sono molti elementi
discordanti. Ne elenchiamo i principali:
- presenza
di una zona di trascinamento per muovere la barra
in altre posizioni della finestra oppure in una finestra
a sé stante. Aqua non ha questo tipo di nozione,
anzi la posizione della barra è fissa.
- Lo
sfondo della barra ha il medesimo colore della finestra-
in realtà in Aqua lo sfondo non è un
colore fisso ma una serie di linee alternate.
- Ciascun
pulsante ha un riquadro che lo delimita e lo sfondo
ha un riempimento particolare. L'accostamento di più
pulsanti è decisamente sgradevole alla vista.
In Aqua le icone hanno tra di loro solo uno spazio.
- La
posizione dell'etichetta di testo e dell'icona è
differente: in Aqua l'icona sormonta la descrizione.
Nella toolbar Swing l'icona è a sinistra del
testo;
- Facendo
clic sul pulsante nella toolbar Swing viene cambiato
il colore di sfondo del pulsante. In Aqua invece si
ottiene una versione più scura dell'icona ed
il testo viene visualizzato in grassetto.
- Il
pulsante attualmente selezionato non viene evidenziato
in alcun modo, mentre su Aqua ha uno sfondo di colore
diverso.
- In
Aqua c'è una linea che delimita la barra degli
strumenti, mentre in Swing non c'è.
Sono differenze minime, ma che ci danno il modo per
indagare più nel dettaglio il funzionamento
di Swing.
Agire
sulla toolbar
Per creare una toolbar più simile a quella di
Aqua è stata creata la classe IconToolbar, che
è una sottoclasse di JToolbar. Il costruttore
di questa classe è il seguente:
public
IconToolbar() {
setFloatable(false);
setBackground( Color.WHITE );
setBorder(
BorderFactory.createMatteBorder(0,0,1,0,
buttonLineColor));
addSeparator(new Dimension(7,10));
}
In
questo codice vengono svolte numerose azioni:
- con
setFloatable(false) viene disabilitata la possibilità
di spostare la toolbar in altre posizioni della finestra
o in una finestra indipendente. Questo ha anche l'effetto
secondario, positivo, di rimuovere la zona di trascinamento
all'inizio della barra degli strumenti;
- con
setBackground() viene impostato lo sfondo bianco per
tutta la toolbar. In realtà in Aqua lo sfondo
è una variante più chiara del pattern
a linee alternate che contraddistingue l'area della
finestra. Il bianco è comunque una buona approssimazione,
che permette di evidenziare il confine tra il corpo
della finestra e l'area della toolbar;
- con
setBorder() viene impostato un bordo personalizzato
alla toolbar. In Swing ciascun componente può
possedere un bordo, che può essere ampiamente
personalizzato. Le dimensioni (0,0,1,0) identificano
la presenza di una sola linea in basso, del colore
individuato dalla variabile buttonLineColor.
Questa variabile è così definita:
protected
Color buttonLineColor = new Color(185,185,185);
Il
colore creato è esattamente quello utilizzato
in Aqua.
Una nota sulla classe BorderFactory. Questa classe Swing
contiene metodi statici per creare diversi tipi di bordi.
Alcuni di questi verranno utilizzati nel resto di questa
serie. Nel caso specifico MatteBorder è un bordo
che può essere personalizzato in termini di colori,
bordi presenti o assenti, loro dimensione e forma. Il
metodo createMatteBorder() si aspetta, nei primi quattro
parametri, la dimensione dei bordi superiore, sinistro,
inferiore e destro. Nota per i programmatori CSS: attenzione,
l'ordine dei bordi in Swing è diverso! In Swing
è: top, sx, bottom, dx, mentre in CSS è
top, dx, bottom, sx.
L'ultima linea di codice crea un separatore dalle dimensioni
personalizzate. L'oggetto Dimension si aspetta nel costruttore
prima la larghezza (7), e poi l'altezza (10). Quest'ultimo
elemento in realtà non è molto importante,
in quanto l'altezza della toolbar è determinata
da Swing in funzione dei pulsanti in essa contenuti.
Questo separatore iniziale permette di distanziare il
primo pulsante del numero corretto di pixel. Diversamente,
questo rimarrebbe appiccicato al lato sinistro.
Creare
i pulsanti
Il metodo createButton() della classe IconToolbar viene
utilizzato per creare pulsanti opportunamente configurati
per poter rientrare nella toolbar che stiamo realizzando.
Il metodo si aspetta due parametri: il primo è
la stringa descrittiva da visualizzare mentre il secondo
è il nome dell'icona da associare il pulsante.
La prima operazione è quella di creare un oggetto
JButton, che sono i classici elementi che vengono inseriti
nelle JToolbar. Nella costruzione di questo oggetto
viene utilizzato un piccolo trucco: per evitare che
i pulsanti siano troppo attaccati l'uno all'altro vengono
concatenati degli spazi prima e dopo la descrizione.
La seconda operazione è quella di ottenere l'icona
desiderata, cosa che avviene utilizzando il metodo getIcon(),
che non fa altro che ritornare il risultato di quesa
linea di codice:
new
ImageIcon( getClass().getResource( name ) );
Perché
l'icona venga trovata è necessario che nello
stesso JAR/CLASSPATH della classe sia presente, nello
stesso package (directory), il nome del file richiesto.
L'icona così caricata viene impostata sul pulsante
con il metodo setIcon():
public
JButton createButton( String text, String iconName )
{
JButton
button = new JButton(" " + text + " ");
ImageIcon
icon = getIcon(iconName);
button.setIcon(
icon );
Il
metodo prosegue poi impostando una serie di opzioni:
button.setBorderPainted(false);
button.setContentAreaFilled(false);
button.setVerticalTextPosition(
AbstractButton.BOTTOM);
button.setHorizontalTextPosition(
AbstractButton.CENTER);
button.setBackground(buttonBackgroundColor);
button.setIconTextGap(0);
Queste
chiamate effettuano, rispettivamente, le seguenti operazioni:
- viene
disabilitata la visualizzazione del bordo: le icone
delle toolbar Aqua non hanno bordi.
- Viene
disabilitato il riempimento dello sfondo: questo produce
l'effetto di eliminare il riquadro dal pulsante.
- Viene
impostata la posizione del testo rispetto all'icona:
questo deve essere in basso.
- Viene
impostato l'allineamento del testo, che in Aqua è
centrato rispetto allo spazio disponibile.
- Il
colore dello sfondo viene impostato al colore new
Color(219,219,219), determinato esaminando le finestre
create da Mac OS X.
- L'ultima
operazione è l'eliminazione dello spazio di
separazione (gap) presente tra icona e testo.
Bordi, bordi, bordi...
A questo punto è necessario sbizzarrirsi un poco
con i bordi, in modo da ricreare esattamente l'aspetto
di quelli presenti nelle icone Aqua. L'implementazione
di un proprio bordo avviene creando una classe che implementa
l'interfaccia Border. Ma Swing permette di personalizzare
notevolmente i bordi già presenti nella sua vasta
libreria, anche combinandoli tra di loro. Questi sono
detti bordi compound. In questo modo è possibile
costruire bordi molto complessi. Questa funzionalità
verrà ora sfruttata per costruire il bordo del
pulsante, combinando tre bordi diversi.
Lo schema è illustrato in Figura 3. Le due barre
verticali sono border1, che utilizza il colore buttonLineColor.
Le due barre orizzontali sono invece border2, che utilizza
il colore di sfondo. Combinando due bordi in questo
modo, si ottiene che i due lati si estendono per tutta
l'alteza del pulsante, mentre quelli orizzontali non
si vedono, essendo del colore di sfondo. Se non fosse
stato inserito anche il bordo orizzontale, quello verticale
sarebbe stato più corto di un pixel. A questo
punto viene concatenato un ulteriore bordo, che permette
di spaziare il contenuto del pulsante (icona e testo),
rispetto al lato superiore ed inferiore della toolbar.
Questo si ottiene con un EmptyBorder con lato alto di
5 pixel e lato basso di 2, che si chiama border3.
A questo punto è possibile combinare i vari bordi,
facendo attenzione a unirli nel corretto ordine.
Prima infatti è necessario unire border1 con
border2; il risultante viene combinato con border3.
Il risultato viene impostato come bordo del pulsante.
Figura 3 - struttura dei bordi di un pulsante
Il
codice è il seguente:
Border
border1 = BorderFactory.createMatteBorder(0,1,0,1,buttonLineColor);
Border border2 = BorderFactory.createMatteBorder(1,0,1,0,buttonBackgroundColor);
Border border3 = BorderFactory.createEmptyBorder(5,0,2,0);
Border compoundBorder1 = BorderFactory.createCompoundBorder(
border1, border2 );
Border border = BorderFactory.createCompoundBorder(
compoundBorder1, border3 );
button.setBorder(border);
Giocare
con le icone
L'ultima manipolazione in merito ai pulsanti è
la configurazione dell'icona da presentare quando viene
fatto clic sul pulsante. Come detto, è l'icona
originale del pulsante ma leggermente più scura.
Per impostare l'icona corrispondente allo stato di "premuto"
del pulsante è presente questo codice:
Image
img = icon.getImage();
ImageIcon pressedIcon = new ImageIcon(
createSelectedIcon( img ) );
button.setPressedIcon(pressedIcon);
Tutte
le operazioni viengono svolte nel metodo createSelectedIcon(),
la cui implementazione è la seguente:
private
Image createSelectedIcon( Image image ) {
ImageObserver observer = new ImageObserver()
{
public boolean imageUpdate(Image image,
int infoflags, int x, int y, int width, int height){
return (infoflags & ImageObserver.ALLBITS)
== 0;
}
};
int width = image.getWidth(observer);
int height = image.getHeight(observer);
BufferedImage img1 = new BufferedImage(image.getWidth(null),
image.getHeight(null),
BufferedImage.TYPE_INT_RGB);
Graphics g = img1.getGraphics();
g.drawImage(image, 0, 0, null);
BufferedImage img2 = new BufferedImage(width,
height, BufferedImage.TYPE_INT_RGB);
RescaleOp rop = new RescaleOp(0.8f, -1.0f,
null);
rop.filter(img1, img2);
return img2;
}
Il metodo non fa altro che utilizzare Java2D per eseguire
una trasformazione sui colori, scurendo l'immagine.
Procedendo con ordine, questo metodo svolge le seguenti
funzionalità:
- crea
un ImageObserver che da il via libera alle operazioni
sulle immagini solo quando rileva che tutti i dati
sono stati caricati. Questo oggetto viene utilizzato
da Java per monitorare il caricamento di una immagine.
- Viene
ottenuta la dimensione dell'immagine;
- Viene
creata una BufferedImage, un tipo di immagine che
può essere scritta;
- Su
di questa viene disegnata l'immagine, attraverso il
relativo oggetto Graphics;
- Viene
creata un'altra BufferedImage, della stessa dimensione
della prima;
- Viene
creata un'operazione RescaleOp, che non fa altro che
filtrare i colori dell'immagine. Con i parametri indicati
si ottiene una versione più scura.
- Con
il metodo filter() l'immagine di partenza viene copiata
sulla seconda, passando però dal filtro di
manipolazione del colore.
Alla
fine del metodo viene ritornata l'immagine manipolata.
Si noti che con Java2D, oppure con le più potenti
JAI, è possibile eseguire diversi tipi di manipolazione
sulle immagini. È anche da sottolineare che,
se lo si desidera, è possibile creare la versione
"scurita" dell'icona con software appositi,
come Fireworks o Photoshop, e far caricare al programma
le due icone: quella normale e quella scura.
Conclusioni
L'approccio per le icone qui illustrato è più
comodo in quando non richiede di mantenere una coppia
di icone per ciascuna funzionalità da aggiungere
alla toolbar, ma ha lo svantaggio di utilizzare più
risorse di CPU, quelle necessarie ad operare la trasformazione
di colore.
Figura 4 - toolbar Swing manipolata per assomigliare
di più a quella di Mac OS X
La
toolbar definitiva è presente in Figura 4. È
lontana dall'essere perfetta, ma nel suo sviluppo abbiamo
imparato tante cose!
|