MokaByte 48 - Gennaio 2001
Foto dell'autore
di
Andrea Gini
Corso di Swing 
V parte: i controlli 
In questo numero ci occuperemo di due elementi molto importanti nella costruzione delle moderne interfacce grafiche: le Tool Bar e le Menu Bar. Essi sono designati come luoghi d'elezione in cui disporre i controlli grafici.
Impareremo anche come comportarci in quelle situazioni in cui vogliamo assegnare una funzione a piu' di un controllo grafico: anche in questo caso i progettisti di Swing hanno realizzato una soluzione pratica ed intelligente

JToolBar
Nelle moderne interfacce grafiche l'insieme dei controlli viene suddiviso principalmente tra due luoghi: la MenuBar, che vedremo nel prossimo paragrafo, e la Tool Bar, di cui ci occuperemo ora.
JToolBar e' un contenitore che permette di raggruppare un insieme di controlli grafici in una riga che, nella maggioranza dei casi, viene posizionata al di sotto della barra dei menu. Sebbene sia utilizzata principalmente come contenitore di pulsanti provvisti di icona, e' possibile inserire al suo interno qualunque tipo di componente, come campi di testo o liste di selezione a discesa
Ricorrendo al Drag & Drop e' possibile "staccare" una Tool Bar dalla sua posizione originale e renderla fluttuante: in questo caso essa verra' visualizzata in una piccola finestra separata dal Frame principale. Allo stesso modo e' possibile "afferrare" una Tool Bar con il mouse e trascinarla in una nuova posizione: questa possibilita' di personalizzazione e' presente nella maggior parte dei programmi grafici.
Usare JToolBar all'interno dei propri programmi non presenta particolari difficolta': e' sufficiente crearla, aggiungervi i componenti nell'ordine da sinistra a destra, e posizionarla all'interno del contenitore principale

  JToolBar toolBar = new JToolBar();
  ImageIcon icon = new ImageIcon("img.gif");
  JButton b = new JButton(icon);
  toolBar.add(b);
  ....
  JFrame f = new JFrame();
  f.getContentPane().setLayout(new BorderLayout());
  f.getContentPane().add(BorderLayout.NORTH,toolBar);

Qui di seguito presentiamo un riassunto dell'API  JToolBar.

  • public JToolBar(): Crea una Tool Bar. 
  • public Component add(Component c): Aggiunge un componente alla Tool Bar. L'ordine di immissione dei componenti e' da sinistra a destra.
  • public JButton add(Action a): Crea un pulsante corrispondente alla Action del parametro e lo aggiunge alla Tool Bar 
  • public void addSeparator(): aggiunge un separatore alla Tool Bar
  • public void setFloatable(boolean b): se si passa true come parametro, la Tool Bar viene impostata come removibile, quindi puo' essere "staccata" dalla sua posizione originale e trascinata in una nuova posizione.


JMenu
I menu sono controlli che permettono di accedere ad un grande numero di opzioni in uno spazio ridotto, organizzato gerarchicamente.Ogni programma grafico dispone di una MenuBar organizzata per gruppi di funzioni: accesso al disco, operazioni sulla clipboard, opzioni e cosi' via; ogni menu puo' essere composto da elementi attivi (MenuItem) o da ulteriori menu nidificati.
 
 

Figura 1 - Gerarchia di JMenu 

Su Swing anche i menu si assemblano in maniera gerarchica, costruendo un oggetto per ogni elemento e aggiungendolo al proprio contenitore. La gerarchia delle classi in Figura 1 mostra che ogni sottoclasse di JComponent e' predisposta a contenere Menu, capacita' che viene garantita dall'interfaccia MenuContainer; le classi JMenu e JPopupMenu sono contenitori realizzati apposta per questo scopo. La classe JMenuItem  implementa il pulsante a menu di tipo piu' semplice: essendo sottoclasse di AbstractButton, ne eredita l'interfaccia di programmazione e il comportamento. JRadioButtonMenuItem e JCheckBoxMenuItem sono MenuItems analoghi ai pulsanti JRadioButton e JCheckBox; oltre alla parentela diretta con JMenuItem, essi hanno in comune con JMenu e JPopupMenu l'interfaccia MenuElement, che accomuna tutti i componenti che possono comparire all'interno di un menu.

Ecco costruttori e metodi per le classi JMenuBar e JMenu

  • JMenuBar(): crea una JMenuBar
  • JMenu(String text): crea un JMenu con l'etichetta specificata dal parametro


Alcuni metodi comuni ad entrambe le classi 

  • void add(JMenu m): aggiunge un JMenu
  • void remove(JMenu m): rimuove un JMenu
  • void removeAll(): rimuove tutti i JMenu


I seguenti costruttori permettono di creare JMenuItem, JRadioButtonMenuItem e JCheckBoxMenuItem in maniera simile a come si puo' fare con i JButton. I parametri permettono di specificare l'etichetta, l'icona e stato.
 

  • JMenuItem(String text)
  • JMenuItem(String text, Icon icon)
  • JCheckBoxMenuItem(String text, Icon icon, boolean b)
  • JCheckBoxMenuItem(String text, boolean b)
  • JRadioButtonMenuItem(String text, boolean selected)
  • JRadioButtonMenuItem(String text, Icon icon, boolean selected)


Sebbene sia possibile posizionare una JMenuBar ovunque all'interno di un'interfaccia grafica, i Top Level Container JFrame, JApplet e JDialog riservano a questo scopo un posto esclusivo, situato appena sotto alla barra del titolo. Possiamo aggiungere un JMenu ad un JFrame, ad un JApplet o ad un JDialog usando il metodo setJMenuBar(JMenuBar), come si vede nelle righe di esempio:

JMenuBar menubar = new JMenuBar();
....
JFrame f = new JFrame("A Frame");
f.setJMenuBar(menuBar)

Il seguente esempio illustra la costruzione di un menu ricorrendo ad elementi di ogni tipo; la figura 2 mostra il risultato, mentre la Figura 3 ne illustra la gerarchia di contenimento. 

import javax.swing.*;

public class JMenuExample extends JFrame {
  public JMenuExample() {
    // Imposta le proprieta' del Top Level Container
    super("JMenuExample");
    setBounds(10,35,250,250);
    // Crea menu, sottomenu e menuitems
    JMenuBar menubar = new JMenuBar();
    JMenu menu = new JMenu("Menu");
      JMenuItem simpleItem = new JMenuItem("SimpleItem");
      JMenu checkSubMenu = new JMenu("CheckBoxes");
        JCheckBoxMenuItem check1 = new JCheckBoxMenuItem("Check 1");
        JCheckBoxMenuItem check2 = new JCheckBoxMenuItem("Check 1");
      JMenu radioSubMenu = new JMenu("Radio");
        JRadioButtonMenuItem radio1 = new JRadioButtonMenuItem("Radio 1");
        JRadioButtonMenuItem radio2 = new JRadioButtonMenuItem("Radio 2");
        ButtonGroup group = new ButtonGroup();
        group.add(radio1);
        group.add(radio2);
    // Compone i menu
        checkSubMenu.add(check1);
        checkSubMenu.add(check2);
        radioSubMenu.add(radio1);
        radioSubMenu.add(radio2); 
      menu.add(simpleItem);
      menu.addSeparator();//(new JSeparator());
      menu.add(checkSubMenu);
      menu.addSeparator();//.add(new JSeparator());
      menu.add(radioSubMenu);
    menubar.add(menu);
    // Aggiunge la menubar al JFrame
    setJMenuBar(menubar);
    setVisible(true); 
  }
 public static void main(String argv[]) {
    JMenuExample m = new JMenuExample();
 }

 
 
 

Figura 2 - Esempio di Menu

 
 
 
Figura 3 - Gerarchia di contenimento relativa al programma 
di esempio 

 
 

JPopupMenu
I JPopupMenu implementano i menu contestuali presenti in quasi tutti i moderni sistemi a finestre. La costruzione di JPopupMenu e' del tutto simile a quella di JMenu; mentre diversa e' la modalita' di visualizzazione. Il metodo

public void show(Component invoker, int x, int y) 

visualizza il menu al di sopra del componente specificato dal parametro invoker, alle coordinate x e y (relative ad invoker). Per associare un JPopupMenu alla pressione del tasto destro del mouse su un oggetto grafico, e' necessario registrare il componente interessato presso un MouseListener incaricato di chiamare il metodo show() al momento opportuno. Dal momento che alcuni sistemi a finestre  mostrano il menu contestuale alla pressione del tasto destro (evento mousePressed) , mentre altri lo mostrano al momento del rilascio (evento mouseReleased), e' bene ascoltare entrambi gli eventi, controllando la condizione isPopupTrigger() sull'evento MouseEvent: esso restituisce true solamente se l'evento corrente e' quello che provoca il richiamo del menu contestuale nella piattaforma ospite.

class PopupListener extends MouseAdapter {
   // tasto destro premuto (stile Motif)
   public void mousePressed(MouseEvent e) {
     if (e.isPopupTrigger()) {
        popup.show(e.getComponent(),e.getX(), e.getY());
     }
   }
   // tasto destro premuto e rilasciato (stile windows)
   public void mouseReleased(MouseEvent e) {
     if (e.isPopupTrigger()) {
       popup.show(e.getComponent(),e.getX(), e.getY());
     }
   }
}

Questo accorgimento permette di creare programmi che rispecchiano il comportamento della piattaforma ospite, senza ambiguita' che potrebbero disorientare l'utente.
Il seguente esempio crea un JTextField al quale aggiunge un MouseListener che si occupa di visualizzare un JPopupMenu alla pressione del tasto destro. 

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class JPopupMenuExample extends JFrame {
  private JPopupMenu popup;
 
  public JPopupMenuExample() {
    super("JPopupMenuExample");
    setBounds(10,35,350,120);
 
    JTextField textField = new JTextField("Premi il tasto sinistro per vedere un JPopupMenu");
    textField.setEditable(false);
    getContentPane().setLayout(new FlowLayout());
    getContentPane().add(textField);

    popup = new JPopupMenu();
      JMenuItem popupItem1 = new JMenuItem("PopupItem 1");
      JMenuItem popupItem2 = new JMenuItem("PopupItem 2");
      JMenuItem popupItem3 = new JMenuItem("PopupItem 3");
      popup.add(popupItem1);
      popup.add(popupItem2);
      popup.add(popupItem3);
 
    // Aggiunge un MouseListener al componente che deve mostrare il menu
    MouseListener popupListener = new PopupListener();
    textField.addMouseListener(popupListener);
    setVisible(true);
   }
   class PopupListener extends MouseAdapter {
     public void mousePressed(MouseEvent e) {
       if (e.isPopupTrigger()) {
          popup.show(e.getComponent(),e.getX(), e.getY());
       }
     }
     public void mouseReleased(MouseEvent e) {
       if (e.isPopupTrigger()) {
         popup.show(e.getComponent(),e.getX(), e.getY());
       }
     }
    }
    public static void main(String[] args) {
        JPopupMenuExample window = new JPopupMenuExample();
    }
}
 
 

Figura 4 - Esempio di JPopupMenu 

Gestione degli Eventi
La gestione degli eventi nei Menu e' del tutto simile a quella dei pulsanti: ogni volta che si seleziona un JMenuItem, esso lancia un ActionEvent ai suoi ascoltatori. Normalmente si usa ActionListener per i JMenuItem, ItemListener per i JCheckboxMenuItem, mentre per JRadioButtonMenuItem possiamo usare sia l'uno che l'altro.
 
 
 

Un modo alternativo per gestire gli eventi: le Action
La maggior parte dei programmi grafici permette di accedere ad una funzionalita' in diverse maniere. I Word Processor, ad esempio, permettono di effettuare un "Cut" su clipboard in almeno tre modi distinti: dal menu "Edit", tramite il pulsante identificato dall'icona della forbice o tramite una voce del menu contestuale. Questa ridondanza e' gradita all'utente, che ha la possibilita' di utilizzare il programma secondo le proprie abitudini e il proprio grado di esperienza, ma puo' rivelarsi complicato da implementare per il programmatore.
Su Swing e' possibile risolvere questo genere di problemi ricorrendo alle Action, oggetti che permettono di associare un particolare evento ad un gruppo di controlli grafici, fornendo nel contempo la possibilita' di gestire in modo centralizzato gli attributi e lo stato.
 
 
 

Descrizione dell'API
 
 

Figura 5 - Gerarchia di Action

 

L'interfaccia Action, sottoclasse di ActionListener, eredita il metodo actionPerformed(ActionEvent e) con il quale si implementa la normale gestione degli eventi. 
Il metodo setEnabled(boolean b) permette di abilitare una Action; la chiamata a questo metodo provoca automaticamente l'aggiornamento dello stato di tutti i controlli grafici ad essa associati.
La coppia di metodi 
  Object getValue(String key)
  void putValue(String key, Object value)
serve a leggere o ad impostare coppie chiave-valore in cui la chiave e' una stringa che descrive un attributo, e il valore e' l'attributo stesso. Tra le possibili chiavi possiamo elencare

  • Action.NAME: la chiave che specifica il nome dell'azione, che verra' riportato sul pulsante. Il valore corrispondente deve essere di tipo String.
  • Action.SHORT_DESCRIPTION: specifica la descrizione dell'azione, usata nei tooltip. Anche in questo caso si richiede un valore di tipo String.
  • Action.SMALL_ICON: la chiave che corrisponde all'icona di default associata a questa Action. Il valore deve essere un oggetto di tipo Icon.


Per impostare l'icona relativa ad una Action dobbiamo utilizzare l'istruzione

action.putValue(Action.SMALL_ICON ,new ImageIcon("img.gif"))

La classe AbstractAction, implementazione dell'interfaccia Action, fornisce alcuni costruttori che permettono di impostare le proprieta' in modo piu' elegante

  • AbstractAction(String name): definisce una Action con il nome specificato dal parametro.
  • AbstractAction(String name, Icon icon): definisce una Action con l'icona e il nome specificati dai parametri.

 
 

Esempio pratico
Possiamo creare un oggetto Action estendendo la classe AbstractAction e fornendo il codice del metodo actionPerformed(ActionEvent e), in modo simile a quanto faremmo per un oggetto di tipo ActionListener.

  class MyAction extends AbstractAction {
    private Icon myIcon = new ImageIcon("img.gif");
    public MyAction() {
      super("My Action",myIcon);
    }
    public void actionPerformed(ActionEvent e) {
      // qui va il codice dell'ascoltatore
    }
  } 

Anche in questo caso e' possibile definire una Action come classe anonima

  Action myAction = new AbstractAction("My Action",
            new ImageIcon("img.gif")) {
    public void actionPerformed(ActionEvent e) {
      // qui va il codice dell'ascoltatore
    }
  }); 

Per abbinare una Action ai corrispondenti controlli grafici e' sufficiente utilizzare il metodo add(Action a) presente su JMenu, JToolBar e JPopupMenu, come si vede nelle seguenti righe: 

  Action myAction = new MyAction();
  JToolBar toolBar = new JToolBar();
  JMenuBar menuBar = new JMenuBar();
  JPopupMenu popup = new JPopupMenu();
  // aggiunge un pulsante alla Tool Bar
  toolBar.add(myAction);
  // aggiunge un MenuItem alla Menu Bar
  menuBar.add(myAction);
  // aggiunge un MenuItem al Popup Menu
  popup.add(myAction);
 
Dal momento che il metodo add(Action) restituisce il componente che viene creato, e' possibile cambiarne l'aspetto anche dopo che e' stato creato. Se vogliamo aggiungere un MenuItem al menu, ma vogliamo che esso sia rappresentato soltanto da una stringa di testo, senza icona, possiamo fare cosi':

  JMenuItem mi = menuBar.add(myAction);
  mi.setIcon(null);
 
Se durante l'esecuzione del programma vogliamo disabilitare i controlli abbinati a MyAction, possiamo farlo ricorrendo all'unica istruzione

  myAction.setEnabled(false);

che provvedera' a disabilitare tutti i controlli legati a MyAction. 
 
 
 

Approfondimento: come funzionano le Action
La semplicita' d'uso delle Action comporta un grande lavoro dietro le quinte, che per fortuna viene svolto in tutto e per tutto dalle API di sistema. La semplice aggiunta di una Action ad un JMenuBar o ad una JMenuBar comporta le seguenti operazioni:

  • Viene creato un componente adeguato al contenitore (un JButton caso di JToolBar e un JMenuItem nel caso di JMenu)
  • Sul componente appena creato vengono impostate le  proprieta' della Action (icona, stringa di testo e stato iniziale)
  • La Action viene registrata come ascoltatore del componente
  • Viene crearto un oggetto di tipo PropertyChangeListener abbinato al componente. Il compito di questo oggetto e' quello di osservare l'Action e di notificarne i cambiamenti al componente associato.
  • Il PropertyChangeListener viene registrato come ascoltatore della Action. 


Quando il controllo viene azionato, la Action verra' invocata come un normale ActionListener. Se durante l'esecuzione del programma viene invece alterata una delle proprieta' della Action (come il nome, l'icona o lo stato), questo cambiamento viene notificato a tutti i PropertyChangeListener registrati, ognuno dei quali andra' a modificare l'aspetto dell'oggetto grafico ad esso abbinato, per adeguarlo alla nuova situazione (chiamando i metodi setText(), setIcon() o setEnabled()).

La relazione circolare che si instaura tra un controllo grafico e una Action puo' essere modellata come un'architettura Observer-Observable a due livelli, visibile in Figura 6:
 
 

Figura 6 - Architettura Observer-Observable a due livelli 

Ovviamente il ricorso ad una struttura cosi' articolata comporta un lieve degrado di performance rispetto alla gestione diretta degli eventi; il vantaggio che ne deriva e' comunque di gran lunga superiore agli inconvenienti prestazionali, e pertanto si consiglia di ricorrere senza indugio alle Action in tutti i casi nei quali possono tornare utili.
 

Conclusioni
Arrivati a questo punto siamo ormai in grado di maneggiare i piu' importanti elementi delle interfacce grafiche. Nel prossimo articolo ci occuperemo di una serie di controlli indispensabili per interazioni piu' complesse, come l'inserimento di campi di testo o la scelta di elementi da una lista.

Vai alla Home Page di MokaByte
Vai alla prima pagina di questo mese


MokaByte®  è un marchio registrato da MokaByte s.r.l.
Java® è un marchio registrato da Sun Microsystems; tutti i diritti riservati
E' vietata la riproduzione anche parziale
Per comunicazioni inviare una mail a
mokainfo@mokabyte.it