MokaByte Numero  45  - Ottobre 2000
Corso di Swing
II parte
di 
Andrea Gini
Gerarchia di Contenimento

Prosegue il corso introduttivo a Swing: questo mese parliamo di contenitori e delle gerarchie di contenimento

Un'interfaccia grafica,  e' composta da un Top Level Container, ad esempio un JFrame, al cui interno e' possibile disporre qualunque tipo di componente. Swing, grazie alla sua struttura altamente modulare, permette di inserire qualunque componente all'interno di qualunque altro: per esempio e' possibile, anche se non particolarmente utile, creare un JButton contenente una JComboBox. 
 
 
Figura 1 - Il design modulare di Swing permette anche di innestare un controllo dentro ad un altro

 

Come abbiamo visto, esistono alcuni componenti che hanno lo scopo di fungere da contenitori per altri componenti. Il piu' usato di questi e' senza dubbio JPanel, un pannello di uso estremamente generale. Come vedremo nell'esempio successivo, possiamo creare un JPanel, disporre alcuni controlli grafici al suo interno usando il metodo add(Component c), e quindi inserirlo in un altro JPanel o nel ContentPane di un Top Level Container.
 

Il seguente esempio permettera' di illustrare meglio i concetti appena illustrati, e di introdurre i successivi.

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

public class FirstExample
{
 public static void main(String argv[])
 {
    // imposta il Look&Feel di sistema
    try
    {
      UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
    }
    catch(Exception e) {}

    // Primo Componente 
    JTextField textField = new JTextField("Premi OK");
    textField.setEditable(false);

    // Secondo Componente
    JLabel labelIcon = new JLabel(new ImageIcon("img.gif"));
    labelIcon.setBorder(BorderFactory.createLineBorder(Color.black));

    // Terzo Componente
    JButton okButton = new JButton("OK");
    okButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        try
        {
          System.exit(0);
        }
        catch (Exception ex)
        { }
      }
    });
    // Quarto Componente
    JButton cancelButton = new JButton("Cancel");

    // Pannello NORTH
    JPanel northPanel = new JPanel();
    northPanel.setLayout(new GridLayout(1,0));
    northPanel.setBorder(BorderFactory.createEmptyBorder(10,4,10,4));
    northPanel.add(textField);

    // Pannello CENTER
    JPanel centralPanel = new JPanel();
    centralPanel.setLayout(new BorderLayout());
    CentralPanel.setBorder(
        BorderFactory.createEmptyBorder(3,4,3,4));
    centralPanel.add(BorderLayout.CENTER,labelIcon);

    // Pannello SOUTH
    JPanel southPanel = new JPanel();
    southPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));

    southPanel.add(cancelButton);
    southPanel.add(okButton);

    // Top Level Container 
    JFrame f = new JFrame("Swing");
    f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
    f.getContentPane().setLayout(new BorderLayout());
    f.getContentPane().add(BorderLayout.NORTH,northPanel);
    f.getContentPane().add(BorderLayout.CENTER,centralPanel);
    f.getContentPane().add(BorderLayout.SOUTH,southPanel);
    f.pack();
    f.setVisible(true);
 }

 
 
 

Figura 2 - Un semplice programma di esempio

Come si puo' vedere leggendo il sorgente,  le interfacce grafiche vengono assemblate dall'interno all'esterno: dapprima vengono creati i quattro componenti piu' interni, quindi i tre pannelli destinati a contenerli e infine un JFrame su cui questi tre pannelli vengono montati. Ogni pannello stabilisce alcune regole valide al proprio interno, come un bordo o un LayoutManager (vedremo i LayoutManager nel prossimo paragrafo). Vediamo una rappresentazione ad albero della gerarchia dei componenti.
 
 

Figura 3 - Gerarchia di Contenimento del programma di esempio

Per vedere la gerarchia di ogni JFrame o JDialog, basta clickare sul suo bordo e quindi premere Control-Shift-F1. La console visualizzera' una lista di tutta la gerarchia di contenimento.
 
 
 

Layout Management
Nel disporre i componenti all'interno di un contenitore, possiamo ricevere un grosso aiuto dai LayoutManager. I LayoutManager, situati nel package java.awt, sono oggetti che si occupano dell'impaginazione automatica dei componenti all'interno di un contenitore. Possiamo assegnare ad ogni container un proprio LM, usando il metodo setLayoutManager(LayoutManager).

Sebbene sia possibile posizionare i controlli dentro ad un contenitore indicandone le coordinate assolute, l'uso dei LayoutManager risulta piu' rapido ed elegante, e rende le interfacce grafiche meno soggette a problemi di visualizzazione al cambio di dimensione o di Look & Feel: provando a deformare la finestra dell'esempio, possiamo notare come i componenti restino posizionati elegantemente al suo interno, mantenendo inalterate le posizioni relative.
 
 

Figura 4 -  Deformando il frame si scoprono i vantaggi del Layout Management

Senza la pretesa di esaurire l'argomento, presentiamo i tre LayoutManager di uso piu' generale, che verranno utilizzati negli esempi presenti in questo corso.
 
 
 

FlowLayout
Questo semplice LM dispone i componenti da sinistra a destra e dall'alto al basso. Ogni componente viene creato con la dimensione minima. Vediamo un esempio

JButton b1 = new JButton("Primo");
JButton b2 = new JButton("Secondo");
JButton b3 = new JButton("Terzo");
JButton b4 = new JButton("Quarto");
JButton b5 = new JButton("Quinto");
JFrame f = new JFrame();

f.getContentPane().setLayout(new FlowLayout());
f.getContentPane().add(b1);
f.getContentPane().add(b2);
f.getContentPane().add(b3);
f.getContentPane().add(b4);
f.getContentPane().add(b5);

f.pack();
f.setVisible(true);
 
 

Figura 5 - Flowlayout

Al termine di una riga, i componenti vengono inseriti nella successiva; su ogni riga i componenti vengono centrati. Si noti come ad ogni oggetto venga assegnata una dimensione diversa, corrispondente alla sua dimensione minima.
 
 

Figura 6 - FlowLayout

E' possibile creare FlowLayout che allineano i componenti a destra o a sinistra, invece che al centro. Basta usare il costruttore

public FlowLayout(int alignment)

in cui il parametro alignment puo' assumere i valori FlowLayout.LEFT, FlowLayout.CENTER, o FlowLayout.RIGHT. Gli argomenti horizontalGap e verticalGap specificano quanti pixel mettere tra i vari componenti.
 
 
 

GridLayout
GridLayout suddivide il contenitore in una griglia di celle di uguale dimensione. La dimensione della griglia vengono definite attraverso uno dei seguenti costruttori

public GridLayout(int rows, int columns)
public GridLayout(int rows, int columns,int horizontalGap, int verticalGap)
public GridLayout() // corrisponde a GridLayout(1,1,0,0)

in cui rows e columns specificano rispettivamente le righe e le colonne, mentre horizontalGap e verticalGap indicano quanti pixel disporre tra un componente e l'altro.

  JButton buttons[] = new JButton[14];
  for(int i=0;i<14;i++)
    buttons[i] = new JButton(String.valueOf(i));

  JFrame f = new JFrame("GridLayout");
  f.getContentPane().setLayout(new GridLayout(4,4));
  for(int i=0;i<14;i++)
    f.getContentPane().add(buttons[i]);

  f.pack();
  f.setVisible(true);
 
 
 

Figura 7 - GridLayout

Se gli  argomenti row e column vengono posti uno a 0 e l'altro ad 1, otterremo una riga o una colonna di componenti equidimensionati, diversamente da FlowLayout che assegna una dimensione diversa ad ogni componente
 
 

Figura 8 - Layout Riga e Colonna usando GridLayout

BorderLayout
Il BorderLayout suddivide il contenitore esattamente in cinque aree, disposte a croce. Il costruttore permette, come al solito, di definire la spaziatura in pixel tra i vari componenti

BorderLayout(int horizontalGap, int verticalGap) 
BorderLayout()      //corrisponde a BorderLayout(0,0)

Il programmatore puo' decidere in quale posizione aggiungere un controllo usando il metodo

add(component,String ) 

dove il secondo parametro deve assumere uno dei valori costanti BorderLayout.NORTH, BorderLayout.SOUTH, BorderLayout.CENTER, BorderLayout.EAST o BorderLayout.WEST
Vediamo l'esempio

JFrame f = new JFrame("BorderLayout");
f.getContentPane().setLayout(new BorderLayout());
f.getContentPane().add(new Button("North"), BorderLayout.NORTH);
f.getContentPane().add(new Button("South"), BorderLayout.SOUTH);
f.getContentPane().add(new Button("East"), BorderLayout.EAST);
f.getContentPane().add(new Button("West"), BorderLayout.WEST);
f.getContentPane().add(new Button("Center"), BorderLayout.CENTER);
f.pack();
f.setVisible(true);
 
 

Figura 9 - BorderLayout

Ovviamente non e' obbligatorio riempire tutti e cinque gli spazi.
 
 

Figura 10 - BorderLayout

BorderLayout e' particolarmente indicato nel gestire l'impaginazione complessiva di una finestra, poiche' la sua conformazione ripropone il layout di una tipica applicazione a finestre, con la barra degli strumenti in alto, una barra di stato in basso, un browser a sinistra o a destra e il pannello principale al centro.
 
 
 

Progettazione Top Down di Interfacce Grafiche
Durante la progettazione di Interfacce Grafiche, torna utile ricorrere ad un approccio Top Down, descrivendo il nostro insieme di componenti partendo da una visone generale per poi scendere via via verso il dettaglio. Per sviluppare una GUI come quella dell'esempio, si puo' procedere in questo modo:

  1. Definiamo il tipo di Top Level Container su cui vogliamo lavorare, in questo caso un JFrame
  2. Assegnamo un LayoutManager al JFrame, in modo da suddividerne la superficie in aree piu' piccole. Nell'esempio siamo ricorsi al BorderLayout.
  3. Per ogni area messa a disposizione dal Layout Manager possiamo definire un nuovo JPanel. 
  4. Ogni sottopannello puo' ricorrere ad un LayoutManager differente. Nell'area superiore abbiamo usato un GridLayout per far si che il JTextField occupasse tutta l'area disponibile in larghezza; in quella centrale il BorderLayout fa in modo che il disegno sia sempre al centro dell'area disponibile; in basso un FlowLayout garantisce che i pulsanti vengano sempre allineati a sinistra. Si noti anche che ogni pannello definisce un proprio bordo.
  5. Ogni pannello identificato nel punto 3 potra' essere sviluppato ulteriormente, creando al suo interno ulteriori pannelli, o disponendo dei controlli. Nell'esempio abbiamo aggiunto al primo pannello un JTextField, nel secondo una JLabel contenente un'icona e nel terzo due JButton.


Terminata la fase progettuale, possiamo passare a scrivere il codice per i nostri controlli. In questa seconda fase adotteremo l'approccio Bottom Up, cioe' scriveremo per primo il codice dei componenti atomici, quindi quello dei contenitori e infine quello del JFrame.
 
 
 

Uso della specializzazione nel Design di Interfacce Grafiche
La semantica dei linguaggi ad oggetti si sposa molto bene con la programmazione di interfacce grafiche. Negli esempi presentati sopra abbiamo composto le interfacce grafiche creando i singoli componenti e incastrandoli uno dentro l'altro. 

Il ricorso alla specializzazione permette invece di trattare ogni sottopannello come se si trattasse di un oggetto grafico personalizzato, specializzato in una particolare funzione. Il codice dell'esempio puo' essere riscritto in questa maniera, definendo la classe principale come sottoclasse di JFrame, e realizzando per ognuno dei pannelli NorthPanel, CenterPanel e SouthPanel una classe interna derivata da JPanel.

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

public class SecondExample extends JFrame
{
  class NorthPanel extends JPanel
  {
    private JTextField textField;

    public NorthPanel()
    {
      textField = new JTextField("Premi OK");

      textField.setEditable(false);
      setLayout(new GridLayout(1,0));
      setBorder(BorderFactory.createEmptyBorder(10,4,10,4));
      add(textField);
    }
  }
  class CenterPanel extends JPanel
  {
    private JLabel labelIcon;

    public CenterPanel()
    {
      labelIcon = new JLabel(new ImageIcon("img.gif"));
      labelIcon.setBorder(BorderFactory.createLineBorder(Color.black));

      setLayout(new BorderLayout());
      setBorder(BorderFactory.createEmptyBorder(3,4,3,4));
      add(BorderLayout.CENTER,labelIcon);
    }
  }
  class SouthPanel extends JPanel
  {
    private JButton okButton;
    private JButton cancelButton;

    public SouthPanel()
    {
      okButton = new JButton("OK");
      okButton.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          try
          {
            System.exit(0);
          }
          catch (Exception ex)
          { }
        }
      });
      cancelButton = new JButton("Cancel");

      setLayout(new FlowLayout(FlowLayout.RIGHT));
      add(cancelButton);
      add(okButton);
    }
  }

  private JPanel northPanel;
  private JPanel centerPanel;
  private JPanel southPanel;

  public SecondExample()
  {
    super("Swing");

    JPanel northPanel = new NorthPanel();
    JPanel centerPanel = new CenterPanel();
    JPanel southPanel = new SouthPanel();

    setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
    getContentPane().setLayout(new BorderLayout());
    getContentPane().add(BorderLayout.NORTH,northPanel);
    getContentPane().add(BorderLayout.CENTER,centerPanel);
    getContentPane().add(BorderLayout.SOUTH,southPanel);

    pack();
    setVisible(true); 
  }

 public static void main(String argv[])
 {
    // imposta il Look&Feel di sistema
    try
    {
      UIManager.setLookAndFeel(
        UIManager.getSystemLookAndFeelClassName());
    }
    catch(Exception e) {}

    SecondExample se = new SecondExample();
 }

 
 

Conclusioni
In questo articolo abbiamo introdotto due concetti fondamentali della programmazione grafica in java: la gerarchia di contenimento e la gestione del Layout. Nel prossimo incontro affronteremo lo studio dei primi controlli grafici.

Chi volesse mettersi in contatto con la redazione può farlo scrivendo a mokainfo@mokabyte.it