MokaByte 100 - 8bre 2005
 
MokaByte 100 - 8bre 2005 Prima pagina Cerca Home Page

 

 

 

Applicazioni Desktop: toolbar in stile Aqua

Iniziamo una nuova serie di articoli incentrata sullo sviluppo delle applicazioni desktop: un modo per entrare più in dettaglio nelle API Swing, in modo da poterne sfruttare tutte le potenzialità.

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!