Il toolkit Swing permette di decidere la modalità con cui i componenti grafici (widget) di una applicazione Java saranno visualizzati (il loro look) e il modo in cui essi interagiranno a seguito dell’interazione con l’utente (feel).
Prima dell’avvento di Swing, con il framework “delle origini” AWT, ogni applicazione dotata di una GUI aveva un L&F uguale a quello del sistema operativo dove era eseguita; ciò comportava di sicuro un vantaggio per l’utente perché quelle applicazioni apparivano e rispondevano allo stesso modo delle applicazioni scritte in modo nativo. Per lo sviluppatore, però, ciò provocava seri problemi di portabilità, dato che ogni GUI per un particolare sistema poteva renderizzare i widget in posizioni diverse o addirittura non prevedere certi comportamenti di interazione. Nella sostanza il famoso motto di Java, WORA (Write Once, Run Everywhere), non era di semplice applicazione quando si dovevano scrivere applicazioni dotate di interfacce grafiche.
Grafica per tutti
Swing ha consentito di risolvere i problemi anzidetti perché ha introdotto dei L&F cross-platform che appaiono e si comportano in modo uniforme su tutti i sistemi previsti: il vetusto Metal e il relativamente nuovo Nimbus, quest’ultimo introdotto in Java SE 6 Update 10. In ogni caso è ancora possibile utilizzare L&F che appaiono e si comportano in modo nativo rispetto al sistema dove la relativa applicazione sta girando. Esistono: per GNU/Linux, Solaris o Unix in generale, GTK+ o Motif se GTK+ almeno dalla versione 2.2 non è installato; per Windows, quello classico oppure quello della versione corrente di utilizzo; per macOS, quello fornito da Apple stessa (allo stato attuale il L&F è quello proprio di macOS).
I tipi propri dei L&F sono disposti in determinati package del JDK (com.sun.java.swing.plaf.gtk, com.sun.java.swing.plaf.motif, com.sun.java.swing.plaf.nimbus, com.sun.java.swing.plaf.windows, javax.swing.plaf.basic, javax.swing.plaf.metal, javax.swing.plaf.multi, javax.swing.plaf.nimbus e javax.swing.plaf.basic.synth) che a partire da Java 9 fanno parte del modulo java.desktop.
Che cosa mi metto
Vediamo lo stralcio di un listato che dà la possibilità di scegliere dei L&F e che mostra alcuni widget e come essi siano renderizzati (il sorgente completo, unitamente alle istruzioni su come compilarlo correttamente, è reperibile su GitHub.
package com.pellegrinoprincipe; … public class SwingLookAndFeel extends javax.swing.JFrame { public SwingLookAndFeel() { initComponents(); } … private void initLookAndFeelMenu() { final UIManager.LookAndFeelInfo plaf[] = UIManager.getInstalledLookAndFeels(); for (int i = 0, n = plaf.length; i < n; i++) { JMenuItem menuItem = new JMenuItem(); menuItem.setText(plaf[i].getName()); menuItem.addActionListener((ActionEvent evt) -> // lambda expr. { JMenuItem itemSeleced = (JMenuItem) evt.getSource(); int ix = itemSeleced.getParent().getComponentZOrder(itemSeleced); try { UIManager.setLookAndFeel(plaf[ix].getClassName()); SwingUtilities.updateComponentTreeUI (SwingLookAndFeel.this); } catch (Exception ex) { JOptionPane.showMessageDialog(this, ex); } }); myMenu.add(menuItem); } } public static void main(String args[]) { java.awt.EventQueue.invokeLater(new Runnable() { public void run() { SwingLookAndFeel slf = new SwingLookAndFeel(); slf.setVisible(true); slf.initLookAndFeelMenu(); } }); } … }
Il listato in esame crea una finestra con una serie di widget e un menu applicativo dove ogni item rappresenta un determinato L&F da scegliere. La classe UIManager, grazie al metodo statico getInstalledLookAndFeels (segnatura completa: public static UIManager.LookAndFeelInfo[] getInstalledLookAndFeels() ) restituisce un array di oggetti di tipo LookAndFeelInfo dove ciascun elemento contiene informazioni circa il nome del L&F e la relativa classe di implementazione. Nel nostro caso l’array plaf, di tipo LookAndFeelInfo, viene usato in un apposito ciclo for per aggiungere dinamicamente gli item del menu Look And Feel il cui nome rispecchierà il nome dei L&F disponibili, ottenuti mediante l’invocazione del metodo getName (segnatura completa: public String getName() ) della classe LookAndFeelInfo.
Come cambia il menu
Successivamente, a ogni scelta effettuata per il tramite del menu, nel listener di gestione del relativo evento (ActionEvent) cambieremo da programma il L&F corrente mediante il metodo statico setLookAndFeel (segnatura completa: public static void setLookAndFeel(String className) ) della classe UIManager, al quale passeremo il nome della classe del L&F ottenuta con il metodo getClassName (segnatura completa: public String getClassName() ) della classe LookAndFeelInfo. Infine, per rendere subito operativo il cambiamento del L&F scelto, invochiamo il metodo statico updateComponentTreeUI (segnatura completa: public static void updateComponentTreeUI(Component c) ) della classe SwingUtilities. L’esempio illustrato consente di cambiare il L&F direttamente dal programma applicativo; possiamo tuttavia compiere questa operazione anche da linea di comando, avviando l’applicazione con il comando java e l’opzione -Dswing.defaultlaf:
java -Dswing.defaultlaf=com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel com.pellegrinoprincipe.SwingLookAndFeel
In alternativa si può anche creare (se non è già presente), nella directory lib del runtime di Java (per esempio, in Windows, C:Program FilesJavajdk1.8.0_152jrelib), il file swing.properties con la chiave swing.defaultlaf e l’indicazione della classe del L&F (con Java 9 il percorso del predetto file è cambiato e infatti dovrà essere inserito in un’apposita directory conf: per esempio, sempre in ambiente Windows, il path sarà qualcosa come C:Program FilesJavajre-9conf). Ritornando dunque al nostro precedente esempio, il file potrà contenere la seguente stringa:
swing.defaultlaf=com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel