Articoli Manifesto Tools Links Canali Libri Contatti ?
Linguaggi / Java

Creare un Layout Manager in Java

Abstract
L'utilità di un meccanismo di packaging degli elementi grafici è indubbia ma, spesso, quanto offerto di default porta ad una complessità del codice non gradita. In questo articolo seguiremo un approccio pratico, senza perdere di vista la teoria, per fornire gli strumenti necessari alla creazione di layout manager personalizzati in Java.
Data di stesura: 14/05/2003
Data di pubblicazione: 05/11/2003
Ultima modifica: 04/04/2006
di Stefano Fago Discuti sul forum   Stampa

Layout, Layout Manager e Container: design ad oggetti e flessibilità

I concetti fondamentali presenti nella gestione del posizionamento dei widget nei toolkit di default di Java sono sostanzialmente tre: quello di Layout come logica di packaging, quello di Layout Manager come realizzazione di un dato algoritmo di layout e quello di Container che esprime l'elemento-contenitore che stimola l'attività del Layout Manager. Ricordiamo, a tal proposito, che i toolkit Java (AWT e Swing) prevedono un'organizzazione delle gerarchie di widget sulla base del pattern Composite (G.o.F. 95): una componente grafica può essere un elemento a sua volta composto di altre componenti ed in questo senso diviene contenitore di widget.

[Figura 1]

Figura 1

INTENTO: Comporre gli oggetti in strutture ad albero per rappresentare gerarchia parte-intero. Questo design ha il vantaggio di permettere il trattamento di oggetti e loro composizione in modo uniforme.

Un layout manager deve interagire con un dato Container e, quando questo lo richiede, applicare l'algoritmo di packaging opportuno. In conseguenza di questo ruolo, il design scelto dai developer della Sun è stato quello che ha portato all'applicazione del pattern Strategy (G.o.F. 95): l'idea sottesa a tale pattern prevede un tipo che descriva il contratto per l'applicazione di una logica; si avranno, poi, differenti realizzazioni che vengono scritte nel rispetto di tale contratto. Parlando di codice quanto esposto vuol dire fornire un'interfaccia di riferimento e un insieme di classi concrete che implementino la data interfaccia; questo è proprio quanto riscontrabile nei toolkit Java.

[Figura 2]

Figura 2

INTENTO: Definisce una famiglia di algoritmi, incapsulando ognuno di essi, e rendendoli intercambiabili. Questo design permette il cambiamento dell'algoritmo in modo trasparente rispetto al cliente.

L'interfaccia di riferimento è espressa nell'interface java.awt.LayoutManager: questo elemento definisce i principali metodi che bisognerà implementare per ottenere un layout, cioè per incapsulare in un proprio oggetto una logica di packaging custom. Presentiamo di seguito la tabella di riepilogo dei metodi dell'astrazione menzionata.

void addLayoutComponent(String name, Component comp)
void layoutContainer(Container parent)
Dimension minimumLayoutSize(Containerparent)
Dimension preferredLayoutSize(Container parent)
void removeLayoutComponent(Component comp)

Il primo passo da compiere per giungere al nostro manager sarà allora creare una classe che realizzi l'interfaccia suddetta secondo la sintassi:

public class MyLayout implements java.awt.LayoutManager
{
  ...
}

Un dato Container usufruirà dell'azione di layout prendendo un riferimento al nuovo LayoutManager secondo la sintassi:

MyLayout layout = new MyLayout();
...
Container.setLayout(layout);

La principale callback che bisognerà considerare, dato che ospiterà la logica di riposizionamento e ridimensionamento dei widget, sarà il metodo con la seguente signature:

public void layoutContainer(Container container)

Il parametro in ingresso è fornito dallo stesso Container che necessita dell'azione di layout degli elementi ospitati: di fronte ad eventi di resize o di repaint, ad esempio, un dato container può richiedere l'aggiornamento del layout ad opera dei metodi invalidate() e validate() e dell'azione doLayout(); in particolare l'implementazione dell'ultimo metodo prevede che un riferimento a this venga fornito in ingresso al Layout Manager correntemente impostato per il Container:

  1. public void doLayout() 
  2.   LayoutManager layoutMgr = this.layoutMgr; 
  3.  
  4.   if (layoutMgr != null) 
  5.     layoutMgr.layoutContainer(this); 

Come può un Layout Manager operare sui widget di un container?

La soluzione più evidente è quella di ottenere i reference dei widget tramite il contenitore stesso grazie al metodo getComponents(), della classe java.awt.Container, che presenta la seguente signature:

public Component[] getComponents()

L'interfaccia java.awt.LayoutManager prevede, comunque, un'altra possibilità: il manager stesso può attuare una propria gestione dei widget grazie ad apposite callback:

public void addLayoutComponent( String name, Component comp )
public void removeLayoutComponent( Component comp )

È evidente che trattandosi di un'interfaccia Java siamo obbligati a implementare questi metodi, il che significa che per manager che sfruttino la prima soluzione presentata è necessario fornire una realizzazione vuota.

L'insieme dei metodi dell'interfaccia in esame si completa con le callback necessarie al dimensionamento del container: l'azione di layout delle componenti, infatti, condiziona anche lo stato del contenitore che le ospita. Quest'ultima affermazione potrebbe sembrare strana ma è da riconsiderare alla luce del pattern Composite: un container è comunque una componente e come tale può essere ospitata in un altro container di livello superiore.

I metodi in questione presentano la seguente signature:

public Dimension preferredLayoutSize(Container parent)
public Dimension minimumLayoutSize(Container parent)

L'uso del primo metodo fornisce la dimensione di riferimento per il dato container mentre l'invocazione del secondo metodo ritorna la dimensione minima attribuibile al container: questi valori devono essere conseguenza della dimensione dei widget ospitati.

Costruiamo un Layout Manager: Vertical Flow Layout

Abbiamo, a questo punto, tutti gli strumenti per creare il nostro primo layout manager che ci permetterà di allineare i widget secondo una politica simile al Flow Layout, presente nel toolkit AWT, ma con la caratteristica di operare verticalmente in contrapposizione, quindi, all'elemento a cui si ispira che opera orizzontalmente.

[Figura 3]

Figura 3: Flow Layout e Vertical Flow Layout

La definizione della classe manager inizia realizzando l'interfaccia java.awt.LayoutManager e con la gestione degli elementi di stato necessari all'azione di layout; forniremo inoltre delle costanti di riferimento per i valori di default e un costruttore overloaded.

  1. public class VerticalLayoutManager implements java.awt.LayoutManager 
  2.   // costanti 
  3.   ////////////////////////////////////////////////////////////// 
  4.   public static final int TOP                    = 0; 
  5.   public static final int MIDDLE                 = 1; 
  6.   public static final int BOTTOM                 = 2; 
  7.   public static final int DEFAULT_VERTICAL_GAP   = 5; 
  8.   public static final int DEFAULT_HORIZONTAL_GAP = 5; 
  9.   public static final int DEFAULT_ALIGNMENT      = MIDDLE; 
  10.  
  11.   // stato manager 
  12.   ///////////////////////////////////////////////////////////// 
  13.   private int align; 
  14.   private int hgap; 
  15.   private int vgap; 
  16.  
  17.   /** 
  18.   * Costruttore di Default 
  19.   */ 
  20.   public VerticalLayoutManager() 
  21.     this(DEFAULT_ALIGNMENT,DEFAULT_HORIZONTAL_GAP,DEFAULT_VERTICAL_GAP); 
  22.  
  23.   /** 
  24.   * Costruttore  
  25.   * @param align 
  26.   */ 
  27.   public VerticalLayoutManager(int align) 
  28.     this(DEFAULT_ALIGNMENT,DEFAULT_HORIZONTAL_GAP,DEFAULT_VERTICAL_GAP); 
  29.  
  30.   /** 
  31.   * Effettivo Costruttore invocato per la costruzione del Manager 
  32.   * @param align 
  33.   * @param hgap 
  34.   * @param vgap 
  35.   */ 
  36.   public VerticalLayoutManager(int align, int hgap, int vgap) 
  37.     setAlignment(align); 
  38.     setHorizontalGap(hgap); 
  39.     setVerticalGap(vgap); 

La nostra componente gestirà i widget ottenendoli direttamente dal container e in conseguenza di tale scelta i metodi di add e remove avranno un'implementazione vuota.

  1. public void addLayoutComponent(String name, Component comp) 
  2.  
  3. public void removeLayoutComponent(Component comp) 

Le callback necessarie alla determinazione della preferred-dimension e della minimum-dimension devono considerare alcuni fattori fondamentali:

  • dimensione dei widget;
  • i valori (insets) che esprimono gli eventuali bordi di un container (ciò che non è porta visiva);
  • gli eventuali gap definiti per attuare spaziature tra widget.

Ulteriori elementi di valutazione possono derivare dalla necessità di aumentare le performance del manager come l'azione di controllo sull'effettiva visibilità di un widget.

Esaminiamo un esempio di implementazione.

  1. public Dimension preferredLayoutSize(Container target) 
  2.   Insets insets           = target.getInsets(); 
  3.   Dimension tarsiz        = new Dimension(0, 0); 
  4.   Component [] components = target.getComponents(); 
  5.   int len                 = components.length; 
  6.  
  7.   for (int i = 0 ; i < len; i++) { 
  8.     Component m = components[i]; 
  9.     if (m.isVisible()) { 
  10.       ... // calcoli sulla dimensione della componente  
  11.  
  12.   tarsiz.width  += insets.left + insets.right + hgap*2; 
  13.   tarsiz.height += insets.top + insets.bottom + vgap*2; 
  14.  
  15.   return tarsiz; 

In modo analogo possiamo pensare la realizzazione del metodo minimumLayoutSize(...).

La logica di layout verrà effettivamente realizzata nel metodo layoutContainer( . . .): sarà durante l'esecuzione di questa callback che i widget verranno posizionati ed eventualmente ridimensionati.

Una realizzazione di riferimento potrebbe essere la seguente.

  1. public void layoutContainer(Container target) 
  2.   ... 
  3.   Insets insets = target.getInsets(); 
  4.   int numcomp   = target.getComponentCount(); 
  5.   ... 
  6.    
  7.   for (int i = 0 ; i < numcomp ; i++) { 
  8.     Component m = target.getComponent(i); 
  9.  
  10.     if (m.isVisible()) { 
  11.       ... 
  12.       Dimension d = m.getPreferredSize(); 
  13.       m.setSize(...); 
  14.       ... 
  15.       m.setLocation(...); 
  16.       ... 

Nella sezione delle risorse troverete i sorgenti relativi all'effettiva realizzazione del flusso verticale del nostro custom LayoutManager.

Conclusioni

La realizzazione di un layout manager implica il rispetto di poche, semplici regole, permettono allo sviluppatore di concentrarsi sull'algoritmica che definisce la politica di layout vera e propria. Implementare un sistema di packaging potente può coinvolgere, però, un numero considerevole di fattori, superiore a quello considerato in questo articolo, o riferirsi ad un modello di posizionamento particolare come nel caso di un layout ispirato ad un orologio. Affrontare queste tematiche sembrerebbe ostico ma le API Java offrono, in realtà, un ulteriore supporto all'idea di layout manager: l'interfaccia java.awt.LayoutManager2 ci permetterà la realizzazione di manager che tengano conto di specifici vincoli applicati ai widget, come quello di doversi disporre lungo una circonferenza!

In un prossimo articolo completeremo la gestione del packaging delle componenti parlando dell'interfaccia introdotta e fornendo ulteriori esempi legati a questa tematica.

Nel contempo provate la costruzione del vostro manager e ... buon divertimento!!!!

Informazioni sull'autore

Stefano Fago, classe 1973. Diplomato in ragioneria, ha conseguito il Diploma di Laurea in Informatica con un progetto legato alle interfacce grafiche soft-realtime in Java. Dopo esperienze in Alcatel ed Elea, ha svolto attività di consulenza come Software Developer e Trainer alla ObjectWay S.p.A. sede di Milano. Attualmente Software Designer presso la sezione Innovazione e Attività Progettuali di BPU Banca. Appassionato del linguaggio Java e di tutte le tecnolgie Object Oriented. Polistrumentista dilettante.

È possibile consultare l'elenco degli articoli scritti da Stefano Fago.

Altri articoli sul tema Linguaggi / Java.

Risorse

  1. Sorgenti dell'esempio di Layout Manager presentato in questo articolo.
    http://www.siforge.org/articles/2003/11/layout-managers-1/clayout.zip (3Kb)
  2. Raccolta di layout managers.
    http://www.softbear.com/people/larry/javalm.htm
  3. The Java Tutorial: Laying Out Components Within a Container.
    http://java.sun.com/docs/books/tutorial/uiswing/layout/
  4. IBM developerWorks: "Building a custom layout manager", esempio di layout manager di IBM.
    http://www-106.ibm.com/developerworks/usability/library/us-custom/
Discuti sul forum   Stampa

Cosa ne pensi di questo articolo?

Discussioni

Questo articolo o l'argomento ti ha interessato? Parliamone.