Panel Matic – Semplici i layout complessi con Java
Java® mette a disposizione degli sviluppatori un numero piuttosto elevato di layout manager, LM di qui in avanti, di base:
- FlowLayout
- BoxLayout
- GridLayout
- BorderLayout
- CardLayout
- Ecc.
Sebbene questi, consentano di definire strutture con un elevato grado di complessità, nessuno garantisce un controllo “completo” circa la disposizione ed il dimensionamento dei singoli componenti, ad esempio nel caso del GridLayout tutti i componenti inseriti all’interno del container a cui tale layot è assegnato assumeranno tutti la medesima dimensione, nel caso del BoxLayout se si opta per un posizionamento orizzontale dei vari elementi questi assumeranno la medesima altezza, mentre se si opta per un posizionamento in verticaole ogni elemento avrà la medesima larghezza. E’ ovvio che un comportamento di questo tipo può dar luogo ad incongruenze o generare risultati che si discostano di molto da quelli che sono gli obiettivi di usabilità che un progettista si pone, per queste e per altre motivazioni si deve ricorrere spesso all’utilizzo, di quello che non a torto può considerarsi il LM più flessibile, ma nel contempo il più flessibile di quelli messi a disposizione degli sviluppatori, ossia il GridBagLayout, GBL di qui in avanti.
Esso infatti, attraverso i GridBagConstraints, consente di definire per ogni singolo elemento da inserire all’interno del container cui risulta associato un insieme di ben 11 caratteristiche distinte che riportiamo qui di seguito:
- gridx, gridy: Consentono di specificare la riga e la colonna in alto a sinistra del componente. La colonna più a sinistra ha indirizzo gridx = 0 e la riga superiore ha gridy = 0, è possibile utilizzare il vincolo GridBagConstraints.RELATIVE per specificare che l’elemento sarà inserito immediatamente a destra (per gridx) o immediatamente al di sotto (per gridy) del componente che è stato aggiunto al contenitore prima di quello che si stà per aggiungere. In genere però, si consiglia di specificare valori di gridx e gridy per ogni singolo componente, piuttosto che utilizzare solo GridBagConstraints.RELATIVE, in questo modo, si riescono ad ottenere layout con una struttura più prevedibile;
- gridwidth,gridheight: Consentono di specificare il numero di colonne (per gridwidth) o righe (per gridheight) nell’area di visualizzazione che dovranno essere utilizzati per rappresentare il componente. Questi vincoli specificano il numero di celle utilizzate per visualizzare il componente e non quindi il numero di pixel. Il valore predefinito è 1, è inoltre possibile utilizzare GridBagConstraints.REMAINDER per specificare che il componente sarà l’ultimo nella sua riga (per gridwidth) o colonna (ad gridheight). Utilizzando GridBagConstraints.RELATIVE è possibile specificare che il componente sia la penultimo nella sua fila (per gridwidth) o colonna (ad gridheight). In ogni caso è comunque sempre preferibile indicare in maniera esplicita i valori per gridwidth e gridheight da utilizzare per ogni componente, piuttosto che utilizzare GridBagConstraints.RELATIVE e GridBagConstraints.REMAINDER, ciò garantisce, come nel caso dei gridx e gridy, di ottenere layout con strutture più prevedebili.
Nota: GBL non permette ai componenti di estendersi su più righe a meno che questo non si trovi nella colonna più a sinistra o si siano specificati valori positivi sia per gridx sia per gridy
- Fill: Utilizzato quando l’area per la visualizzazione è più grande di quella richiesta dal componente, in questo modo è possibile determinare se, e come ridimensionare il componente E’ possibile specificare i seguenti valori (definiti come costanti nella classe GridBagConstraints):
- NONE (impostazione predefinita),
- HORIZONTAL (rende il componente sufficientemente ampio da riempire l’area di visualizzazione in orizzontale, ma non cambiandone l’altezza),
- VERTICAL (rende il componente abbastanza alto da riempire l’area di visualizzazione in verticale , non modifica la sua larghezza)
- BOTH (rende il componente abbastanza ampio da riempire del tutto l’area di visualizzazione).
- ipadx,ipady: Specifica il padding interno al componente in pratica consentono di indicare quanto spazio aggiungere alle dimensioni di base del componente. Il valore predefinito è zero. La larghezza del componente sarà quindi pari alla sua larghezza minima più ipadx * 2 pixel, poiché il padding orizzontale si applica ad entrambi i lati del componente. Analogamente, l’altezza del componente sarà uguale l’altezza minima più ipady * 2 pixel.
- insets: Specifica il padding esterno al componente, ossia indica la quantità minima di spazio tra il componente ed i bordi della sua area di visualizzazione. Il valore è specificato come un oggetto Insets. Non esiste alcuna impostazione di default per la classe insets di cui i singoli componenti possono usufruire.
- anchor: Utilizzato quando il componente ha dimensione minore della sua area di visualizzazione, determinare dove ,all’interno dell’area assegnatagli, posizionare il componente. I valori validi , al solito definiti come costanti all’interno della classe GridBagConstraints, sono:
- CENTER (impostazione predefinita),
- PAGE_START,
- PAGE_END,
- LINE_START,
- LINE_END,
- FIRST_LINE_START,
- FIRST_LINE_END,
- LAST_LINE_END
- LAST_LINE_START.
Visivamente i valori appena indicati sono così interpretati:
FIRST_LINE_START PAGE_START FIRST_LINE_END
LINE_START CENTER LINE_END
LAST_LINE_START PAGE_END LAST_LINE_END
Nota: Le costanti _PAGE * e * LINE_ * sono state introdotte a partire alla versione 1.4, per quelle precedenti queste stesse costanti utilizzano i nomi dei punti cardinali, ad esempio, a NORTHEAST
indica la parte in alto a destra dell’area di visualizzazione, in ogni caso è fortemente consigliato l’utilizzo delle le nuove costanti,poiché consentono una più facile localizzazione dei componenti.
- weightx,weighty: Specificare il “peso” di ogni singolo componente è un’arte che può avere un impatto significativo sull’aspetto dei componenti all’interno GBL. I pesi sono utilizzati per determinare come distribuire lo spazio tra le colonne (weightx) e tra le righe (weighty), in questo modo questo si determinano anche i comportamenti delle politiche per il ridimensionamento dei singoli componenti. Se non si specifica almeno un valore diverso da zero per weightx o weigthy, tutti i componenti si raggruppano al centro del loro contenitore. Questo perché quando il peso è 0,0 (il valore predefinito), il GBL inserisce uno spazio standard tra la griglia delle celle e i bordi del contenitore. Generalmente i pesi sono specificati con valori compresi tra 0.0 e 1.0, chiaramente valori più alti implicano la necessità per quel componente di utilizzare più spazio.
Per chiarire quanto possa essere complesso utilizzare il GridBagConstrints di seguito riportiamo un semplice esempio ripreso dal tutorial Oracle®:
/** Copyright (c) 1995, 2008, Oracle and/or its affiliates. All rights reserved.
* * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * – Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * – Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * – Neither the name of Oracle or the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS * IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
package layout;
/* * GridBagLayoutDemo.java requires no other files. */
import java.awt.*; import javax.swing.JButton; import javax.swing.JFrame;
public class GridBagLayoutDemo { final static boolean shouldFill = true; final static boolean shouldWeightX = true; final static boolean RIGHT_TO_LEFT = false;
public static void addComponentsToPane(Container pane) { if (RIGHT_TO_LEFT) { pane.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT); }
JButton button; pane.setLayout(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); if (shouldFill) { //natural height, maximum width c.fill = GridBagConstraints.HORIZONTAL; }
button = new JButton(“Button 1”); if (shouldWeightX) { c.weightx = 0.5; } c.fill = GridBagConstraints.HORIZONTAL; c.gridx = 0; c.gridy = 0; pane.add(button, c);
button = new JButton(“Button 2”); c.fill = GridBagConstraints.HORIZONTAL; c.weightx = 0.5; c.gridx = 1; c.gridy = 0; pane.add(button, c);
button = new JButton(“Button 3”); c.fill = GridBagConstraints.HORIZONTAL; c.weightx = 0.5; c.gridx = 2; c.gridy = 0; pane.add(button, c);
button = new JButton(“Long-Named Button 4”); c.fill = GridBagConstraints.HORIZONTAL; c.ipady = 40; //make this component tall c.weightx = 0.0; c.gridwidth = 3; c.gridx = 0; c.gridy = 1; pane.add(button, c);
button = new JButton(“5”); c.fill = GridBagConstraints.HORIZONTAL; c.ipady = 0; //reset to default c.weighty = 1.0; //request any extra vertical space c.anchor = GridBagConstraints.PAGE_END; //bottom of space c.insets = new Insets(10,0,0,0); //top padding c.gridx = 1; //aligned with button 2 c.gridwidth = 2; //2 columns wide c.gridy = 2; //third row pane.add(button, c); }
/** * Create the GUI and show it. For thread safety, * this method should be invoked from the * event-dispatching thread. */ private static void createAndShowGUI() { //Create and set up the window. JFrame frame = new JFrame(“GridBagLayoutDemo”); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Set up the content pane. addComponentsToPane(frame.getContentPane());
//Display the window. frame.pack(); frame.setVisible(true); }
public static void main(String[] args) { //Schedule a job for the event-dispatching thread: //creating and showing this application’s GUI. javax.swing.SwingUtilities.invokeLater(new Runnable() { public void run() { createAndShowGUI(); } }); } }
|
Il risultato prodotto da questo codice è il seguente:
Appare evidente come, se da un lato l’utilizzo del gribaglayout permette un controllo pressoché assoluto sulla definizione di un’interfaccia, dall’altro comporta uno sforzo programmatico no trascurabile, per questo motivo, nel corso degli anni sono stati sviluppati una serie di LM alternativi, i quali si pongono come obiettivo di raggiungere un grado di flessibilità analogo a quello del GBL, senza però richiedere lo stesso impegno in termini di definizione per i vincoli da applicare ai singoli componenti. A questo di seguito presentiamo un comodo LM sviluppato da Michael Bar-Sinai chiamato PanelMatic.
PanelMatic è una libreria che consente la realizzazione di GUI complesse in modo molto semplice, in particolare questa libreria è stata sviluppata tenendo quali riferimenti i seguenti obiettivi:
- Avere una diretta connessione tra quello che è il codice prodotto e l’effettivo risultato finale
- Consentire la modifica di GUI già esistenti in modo semplice senza dover ricorrere ad artifici particolari
- Garantire un elevato grado di personalizzazione
- Internazionalizzazione, incluso il supporto alle RTL
- Conentire la creazione di pannelli attraverso la specifica di espressioni,
- Ecc.
A tal proposito si consideri il seguente codice d’esempio:
createComponents();PanelMatic.begin()
.addHeader(HeaderLevel.H1, “User Data”) .add( “Name”, txfName ) .add( “Username”, txfUsername ) .add( “Password”, txfPassword ) .add( cbIsAdmin ) .add( btnOk, Modifiers.L_END, Modifiers.P_FEET, Modifiers.GROW ) .get();
|
Il risultato finale sarà la seguente interfaccia:
Vediamo ora di chiarire ulteriormente in che modo utilizzare PanelMatic mediante una ulteriore serie di esempi, il primo in particolare ci fa veder in che modo realizzare un semplice Form.
Esso sarà costituito da una serie di label, un’area di testo alcuni campi testo, nonché gli immancabili tasti cancel e ok, inoltre ogni qualvolta un textjield otterrà il focus sarà evidenziato in giallo. Il codice necessario per ottenerequesto tipo di risultato è il seguente:
Box buttonBox = Box.createHorizontalBox();buttonBox.add( btnCancel );
buttonBox.add( btnOk ); PanelMatic.begin( this, new ColorOnFocusCustomizer() ) .addHeader(HeaderLevel.H1, “User Data”) .add( “Name”, txfName ) .add( “Username”, txfUsername ) .add( “Password”, txfPassword ) .add( cbIsAdmin ) .add( new JScrollPane(txaMemo), GROW ) .add( tabs, GROW_LESS ) .add( buttonBox, L_END, P_FEET ) .get();
|
Il risultato di questo codice è il seguente:
Il prossimo esempio mostra in che modo PanelMatic consente la realizzazione di strutture annidate in modo semplice ed intuitivo, in particolare in questo caso all’interno del pannello principale sarà creato un seconfo pannello all’interno del quale saranno inserite una serie di checkbox.
createComponents();PanelMatic.begin( this )
.addHeader(new TestUtils.RandIcon(20), HeaderLevel.H1, “Qoute Generator”) .add( “Source”, PanelMatic.begin() // second builder .add(chkMonty) .add(chkEmo) .add(chkColbert) .add(chkSimpsons) .add(chkLorem) .get()) .add( new TestUtils.RoundIcon(Color.red, 16),.”Output Type”, cmbType ) .add( btnGo, L_CENTER ) .get();
|
Il Risultato del codice precedente è il seguente:
Da questi due semplici esempi appare evidente la simmetria tra le azioni codificate ed il risultato prodotto a video. PanelMatic inoltre consente anche di costruire GUI utilizzando il LM già definiti in Java® a tal riguardo mostriamo in che modo è possibile utilizzare un BorderLayout:
createComponents();setLayout( new BorderLayout() );
.add( new JLabel(drawing), BorderLayout.CENTER ); .add( PanelMatic.begin() .addHeader( HeaderLevel.H4, “Name the drawing” ) .add( icns[0], “Name”, txfName ) .add( icns[1], “Rate”, sldRate ) .add( btnSubmit, GROW, L_END, P_FEET ) .get(), .BorderLayout.LINE_END);
|
Il risultato finale sarà il seguente:
Nel caso in cui sorgesse la necessità di dover “raggruppare” più componenti è possibile ricorrere alla classe Groupings così come avviene nell’esempio seguente:
PanelMatic.begin().add(“Username”, new JTextField(20))
.add(“Password”, new JPasswordField(20)) .add( Groupings.lineGroup( new JButton(“login”), new JButton(“exit”)), GROW, L_END, P_FEET ) .get() );
|
In questo caso il risultato ottenuto sarebbe il seguente:
Nella nostra introduzione si è detto che tra le features di cui questa libreria può fregiarsi è il supporto all’internazionalizzazione a tal proposito si consideri il seguente esempio:
PanelMatic.setLocalizationBundle(he_loc);PanelMatic.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
// Crea pannello con caratteri ebraici PanelMatic.setLocalizationBundle(en_loc); PanelMatic.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT); // Crea un pannello con le label scritte in inglese
|
Il risultato che si otterrebbe sarebbe il seguente:
Da questi semplici esempi appare evidente la maggiore semplicità, nell’implementare interfacce anche complesse se paragonate al GBL, non va però dimenticato come l’utilizzo di PanelMatic non escluda la possibilità di utilizza LM già definiti in Java® il che chiaramente non fa altro che arricchire ulteriormente le opzio nella definizione delle interfacce utente.
Fonti: Project Kenai: PANELMATIC (Michael Bar-Sinai)
Commenti