Esistono due tipi di contenitori in GTK+. Entrambi sono sottoclassi dell'astrazione di GtkContainer. Il primo tipo di contenitore deriva sempre da GtkBin, un'altra classe astratta. I derivati di GtkBin possono contenere un unico widget figlio al loro interno. Questi contenitori aggiungono alcune funzionalità al widget ospitato. Ad esempio, GtkButton è un GtkBin che rende il figlio un pulsante premibile. GtkFrame è un GtkBin che disegna un bordo attorno al figlio. GtkWindow permette ai figli di apparire in une finestra di primo livello.
Il secondo tipo di widget contenitore ha spesso un GtkContainer come suo padre. Questi contenitor possono avere più di un figlio al loro interno, e il loro fine è quello di gestire l'aspetto dell'interfaccia, cioè questo tipo di contenitore assegna le dimensioni e la posizione ai widget che contiene. Ad esempio, GtkVBox dispone i figli in verticale. GtkFixed vi permette di posizionare i widget attraverso un sistema di coordinate. GtkPacker fornisce un modello di gestione basato sullo stile di Tk.
Questo capitolo tratta unicamente il secondo tipo di contenitori. Per produrre la disposizione che desiderate senza forzare nessuna dimensione, dovrete capire come utilizzarli. Il fine è quello di evitare assunzioni sulla dimensione della finestra, dello schermo, dell'aspetto dei widget, dei caratteri, e così via. La vostra applicazione deve adattarsi automaticamente al cambiamento di questi fattori.
Per capire il modello dei contenitori, dovrete prima capire come i widget GTK+ decidono le proprie dimensioni. È molto semplice in realtà. Esistono solo due concetti, requisition e allocation. Questi corrispondono a due fasi della creazione del modello.
La requisition di un widget consiste in una altezza e larghezza, la dimensione di cui il widget vorrebbe assumere. Questo viene rappresentato tramite una struttura GtkRequisition:
Widget differenti scelgono le dimensioni da richiedere in modi diversi. GtkLabel, ad esempio, chiede una dimensione sufficiente a mostrare il testo dell'etichetta. La maggior parte dei widget contenitori basano la loro richiesta di dimensioni su quella richiesta dai widget contenuti. Ad esempio, se posizionate alcuni pulsanti in un contenitore questo chiederà di essere largo a sufficienza per contenerli tutti quanti.
La prima fase della modellazione è di iniziare con il widget di primo livello come la GtkWindow. Dato che si tratta di un contenitore, GtkWindow chiede ai widget contenuti al suo interno la dimensione che desiderano assumere, i quali, eventualmente, la richiederanno ai propri figli, ricorsivamente. Completata l'interrogazione su tutti i widget, GtkWindow otterrà finalmente indietro un GtkRequisition al proprio figlio. Basandosi sulla tipologia della GtkWindow, questa può essere in grado o meno di espandersi per accontentare le richieste di dimensione.
A questo punto inizia la seconda fase della modellazione. GtkWindow decide quanto spazio è attualmente disponibile per i widget al suo interno, e comunica questa decisione al proprio figlio. Questa viene denominata la allocation del figlio, rappresentata dalla seguente struttura:
I membri width e height sono identici al GtkRequisition. Questi rappresentano la dimensione del widget. Un GtkAllocation include inoltre le coordinate del figlio rispetto al padre. I GtkAllocation vengono assegnati ai widget figli dai rispettivi padri.
I widget devono rispettare il GtkAllocation fornitogli. GtkRequisition è unicamente una richiesta. I widget devono essere in grado di gestire qualunque dimensione.
Avendo mostrato il processo di creazione, è facile comprendere quale ruolo ricoprano i contenitori. Il loro scopo è quello di assemblare le requisition di ciascun figlio in un'unica richiesta per passare al widget padre superiore. Quindi suddividere le allocation ricevuta fra i figli. Come accade questo esattamente è dipendente dal contenitore particolare.
Un GtkBox gestisce una riga (GtkHBox) o colonna (GtkVBox) di widget. Per GtkHBox, tutti i widget assumono la stessa altezza. Il lavoro del contenitore è quello di distribuire lo spazio orizzontale disponibile tra questi. GtkHBox utilizza parte dello spazio orizzontale disponibile per lasciare degli spazi (chiamati "spacing") tra i vari widget. GtkVBox è pressoché identico, ma esegue l'operazione nella direzione opposta (suddivide lo spazio verticale disponibile piuttosto che quello orizzontale). GtkBox è una classe di base astratta, GtkVBox e GtkHBox possono essere utilizzati attraverso la sua interfaccia. Questi tipi di contenitori sono indubbiamente i più utili.
Per creare un GtkBox, dovete utilizzare uno dei costruttori mostrati in
Figura 2
e
Figura 3
. Il funzione costruttore del contenitore accetta due argomenti. Se uguale a TRUE, homogeneous significa che tutti i widget figli otterranno lo stesso spazio. spacing specifica lo spazio di distanziamento tra ciascuno dei widget contenuti. Esistono funzioni per cambiare questa spaziatura e rimuovere la omogeneità dopo che il contenitore è stato creato.
Un contenitore può contenere due serie di widget. La prima viene inserita
all'inizio ("start") del contenitore stesso. La seconda alla fine
("end"). Se inserite tre widget all'inizio del contenitore, il primo
apparirà in alto oppure a sinistra. Il secondo seguirà il primo, mentre il
terzo presso il centro del contenitore. Se inserite poi altri tre widget
allo stesso modo partendo dalla fine il primo apparirà in basso oppure a destra, il secondo accanto a questo e infine il terzo nel centro. Inserendo questi sei widget, l'ordine dall'alto/sinistra al basso/destra sarà: 1, 2, 3, 3, 2, 1.
Figura 5
mostra questa operazione per un GtkVBox. L'ordine di "impacchettamento" è importante unicamente rispetto alle estremità del contenitore. Ad esempio, possiamo avere l'inserimento dall'inizio e dalla fine, alternati, ottenendo lo stesso risultato.
L'inserimento viene controllato da tre parametri, che sono identici sia se iniziamo dall'inizio del contenitore che dalla fine. Il significato di questi è piuttosto complicato, poiché interagiscono con l'impostazione homogeneous del contenitore e di tutti gli altri widget inseriti.
Ecco come un GtkBox calcola la sua richiesta di spazio per la direzione interessata (orizzontale per il GtkHBox, verticale per il GtkVBox) :
La dimensione totale richiesta da ciascun widget viene considerata come la richiesta di ciascun widget sommata a due volte il valore del padding utilizzato durante l'inserimento del widget. Il padding di un widget è l'ammontare dello spazio vuoto da lasciare su entrambi i lati di questo. In breve, Dimensione Widget = (Dimenesione Richiesta dal Widget) + (2 * (Padding del widget)).
Se il contenitore è omogeneo, la dimensione di base richiesta per l'intero contenitore è uguale alla dimensione (richiesta + padding) del widget contenuto più grande, moltiplicato il numero di widget presenti. In un contenitore omogeneo, tutti i widget sono grandi quanto quello più grande.
Se il contenitore non è omogeneo, la dimensione di base richiesta per l'intero contenitore è la somma della dimensione (richiesta + padding) di ciascun widget contenuto.
Lo spacing all'interno di tutto il contenitore
determina la quantità di spazio vuoto da lasciare tra ciascun widget
contenuto. Questo valore viene quindi moltiplicato per il numero di
widget meno uno, e aggiunto alla richiesta di dimensione globale. Notare
che lo spacing non è proprio di un widget
contenuto, è lo spazio fra questi e non è influenzato dai parametri
expand e fill. Il padding, in altre parole, è lo spazio attorno ciascun widget contenuto ed è influenzato dai parametri di impacchettamento di questo.
Tutti i contenitori hanno uno spessore del bordo. Due volte lo spessore del bordo viene aggiunto alla richiesta di dimensione totale, che rappresenta il bordo su ciascuno dei lati del contenitore. In questo modo, la dimensione totale richiesta per un GtkBox è: (Somma delle dimensioni dei widget contenuti) + (Spacing * (Numero di widget - 1)) + (2 * (Spessore del bordo)).
Dopo aver calcolato la richiesta di dimensione e inviatola al contenitore superiore, il GtkBox riceverà la sua dimensione di allocation da distribuire tra i widget contenuti, in questo modo:
Lo spazio sufficiente per lo spessore del bordo e la spaziatura fra i widget vengono sottratti dalla dimensione restituita. Lo spazio rimanente è disponibile per i widget contenuti. Questo spazio viene suddiviso in due sezioni: l'ammontare effettivamente richiesto dal widget (richiesta + padding) più gli "extra". Extra = (Dimensione Allocation) - (Somma delle dimensioni dei widget contenuti).
Se il contenitore non è omogeneo, lo spazio "extra" viene suddiviso fra i widget che hanno il parametro expand uguale a TRUE. Questi widget possono espandersi fino a occupare lo spazio disponibile. Se nessuno dei widget si può espandere, lo spazio "extra" viene utilizzato al centro del contenitore, fra i widget inseriti partendo dall'inizio e quelli inseriti alla fine.
Se il contenitore è omogeneo, lo spazio extra viene distribuito in base alle necessità. I widget che hanno richiesto maggiore spazio ricevono un "extra" inferiore, in modo tale che alla fine tutti abbiano lo stesso ammontare di spazio disponibile. Il parametro expand viene ignorato dai contenitori omogenei. Lo spazio extra è distribuito fra tutti i widget contenuti, non solo a quelli che possono espandersi.
Quando un widget richiede maggiore spazio extra, esistono due possibilità. Può essere aggiunto maggiore padding attorno al widget, oppure il widget viene espanso. Il parametro fill determina quale di questi casi verrà a verificarsi. Se uguale a TRUE, il widget contenuto si espanderà fino a riempire lo spazio. Se fill invece è uguale a FALSE, il padding del widget verrà incrementato fino a riempire lo spazio. Notare che fill non ha alcun effetto se il parametro expand è impostato uguale a FALSE e il contenitore non è omogeneo, poichè il widget non riceverà mai spazio aggiuntivo da occupare
Puff!!! Ma chi è che vuole pensare a tutte queste cose? Fortunatamente, esistono alcuni modelli di utilizzao, quindi non dovrete ogni volta risolvere particolari equazioni per capire come utilizzare un widget. Gli autori del Tutorial di GTK+ suddividonoo questa pratica in cinque casi pratici. Seguiamo adesso i loro consigli.
Esistono tre modi interessanti per inserire un widget in un contenitore non omogeneo. Primo, potete inserire tutti i widget alla fine del contenitore, mantenendo la loro dimensione naturale. Questo significa impostare il parametro expand uguale a FALSE:
Il risultato è mostrato in
Figura 6
. Il parametro expand è l'unico che conta in questo caso. Nessun widget contenuto riceve dello spazio extra, quindi non è possibile per loro riempirlo anche se fill è impostato a TRUE.
Figura 6. Contenitore non omogenio, con expand = FALSE
Potete disseminare i widget all'interno del contenitore, permettendogli di mantenere le loro dimensioni naturali come mostrato in
Figura 7
. Questo significa porre il parametro expand uguale a TRUE:
Figura 7. Contenitore non omogeneo, con expand = TRUE e fill = FALSE
Infine potete riempire il contenitore con i widget (facendo sì che quelli di maggiori dimensioni ottengono minore spazio) impostando il parametro fill uguale a TRUE:
Esistono unicamente due modi interessanti per utilizzare un contenitore omogeneo. Ricordate sempre che il parametro expand è irrilevante per i contenitore di questo tipo. Quindi questi due casi corrispondono alle possibili impostazioni del parametro fill.
Se fill è posto uguale a FALSE, quello che otterrete è la
Figura 9
. Notare che il contenitore è suddiviso logicamente in tre settori uguali, ma solo il widget più grande occupa tutto lo spazio messo a disposizione per ciascuno di questi. Gli altri ricevono maggior padding per riempire lo spazio restante. Se fill è posto invece uguale a TRUE, ottenete la
Figura 10
, dove tutti i widget assumono la stessa identica dimensione.
La
Figura 11
mostra tutte e cinque le tecniche di contenimento. (Sono inserite in un GtkVBox omegeneo con il parametro fill posto uguale a TRUE e una spaziatura di due pixel). Questo dovrebbe mostrare le varie differenze. Tenete bene in mente che è possibile modificare i parametri di padding e spacing, per incrementare/decrementare lo spazio vuoto fra i widget contenuti. Ad ogni modo, è possibile creare facilmente una disposizione terrificante utilizzando incoerentemente la spaziatura. È una buona idea cercare di tenere i widget allineati e coerentemente spaziati.
Figura 11. I cinque metodi di impacchettamento
Un ultimo appunto: notare che i parametri expand e fill sono rilevanti unicamente quando la dimensione assegnata al contenitore è maggiore di quella richiesta dai widget contenuti. Per questo motivo tali parametri determinano unicamente come distribuire lo spazio extra. Tipicamente questo appare quando l'utente modifica la finestra ridimensionandola maggiormente rispetto alla dimensione di partenza. Per questo dovete sempre tentare di rimensionare la finestra, per assicurarvi che i contenitori svolgano al meglio il loro compito.
Il secondo contenitore più utilizzato è GtkTable. Una GtkTable divide una regione di spazio in varie celle. È possibile assegnare a ciascun rettangolo, formato da una o più celle, un widget da contenere. Potete immaginare una GtkTable come un foglio di carta millimetrata (con maggiore flessibilità: le linee delle griglie posso essere ovviamente non equidistanti).
Una GtkTable fornisce l'usuale costruttore, e alcune funzioni per inserire i widget al suo interno. Queste sono mostrate ne
Figura 12
. Quando create una tabella, dovete specificare il numero di celle che intendete utilizzare, per un fatto di pura efficienza. La tabella crescerà automaticamente se posizionate dei widget in celle fuori da quelle stabilite. Come i contenitori, le tabelle possono essere omogenee o meno.
I primi due argomenti per gtk_table_attach() sono la
tabella e il widget da posizionare al suo interno. I quattro successivi
specificano la linea della griglia ove verrà collocato il widget. Le linee
della griglia vengono numerate partendo dall'angolo in alto a sinistra
(NordOvest), partendo da 0. Quindi una tabella di 2 per 3 avrà le linee verticali 0, 1, 2 e orizzontali 0, 1, 2, 3. Gli ultimi due argomenti indicano l'ammontare del padding per porre ai lati destro e sinistro del widget (xpadding) e alto e basso (ypadding). Questo argomento è analogo al padding dei contenitori.
Gli argomenti GtkAttachOptions richiedono di una breve spiegazione. Ecco un sommario di possibili valori. I valori sono maschere di bit, quindi è possibile più di uno oppure unirli attraverso l'operatore OR.
GTK_EXPAND specifica che questa sezione della tabella verrà espansa, fino a occupare tutto lo spazio disponibile, in modo molto simile al parametro expand per i contenitori precedentemente descritti.
GTK_FILL specifica che i widget contenuti verranno espansi fino a occupare tutto lo spazio disponibile. Di rilievo unicamente se è impostata anche GTK_EXPAND, poichè GTK_EXPAND permette l'esistena di spazio extra.
GTK_SHRINK determina le azioni da eseguire in caso sia fornito spazio insufficiente per soddisfare le richieste dei singoli widget. Se viene impostata GTK_SHRINK, al widget verrà fornito un spazio minore di quello richiesto in modo da riuscire ad utilizzare lo spazio totale disponibile. Se non è impostata, invece, viene fornita al widget la dimensione richiesta. Questo può generare sovrapposizioni dei widget nelle tabelle, oppure questi possono essere "tagliati" dai bordi della tabelle, poiché tentano di disegnare fuori dalla GdkWindow della tabella.
È inoltre possibile specificare la spaziatura per righe e colonne, in aggiunta al padding che circonda ciascun singolo widget. I termini "spaziatura" e "padding" hanno lo stesso significato, ma vengono applicati diversamente nelle tabelle e nei contenitori. Il file gtk/gtktable.h elenca tutte le funzioni disponibili per GtkTable.
Il codice seguente crea una tabella con quattro celle e tre widget al suo interno. Uno di questi occupa due celle. I widget sono inseriti utilizzando parametri diversi:
È molto istruttivo osservare la tabella risultante quando la finestra viene ridimensionata. Per prima cosa, un piccolo elenco dei widget che sono stati inseriti:
Il primo widget riceverà sempre lo spazio richiesto. Non si espanderà né si ridurrà mai.
Il secondo si può espandere unicamente in verticale.
Il terzo può espandersi in qualunque direzione.
La dimensione naturale della finestra è mostrata nella
Figura 13
. Notare che alcune celle devono rimanere allineate. (Ricordate che un pulsante con una etichetta richiederà spazio sufficiente per contenere l'intero testo dell'etichetta). Il flag GTK_FILL obbliga la GtkTable ad allocare spazio extra per i widget, invece di lasciare spazio vuoto fra di essi.
Figura 13. GtkTable prima del ridimensionamento
Adesso immaginate che l'utente ridimensioni la finestra, espandendola, in
verticale. Notare che lo spazio extra viene fornito ai widget con il flag
GTK_EXPAND attivato per le direzione
verticale (i widget due e tre del nostro esempio) mentre il widget
nell'angolo in alto a sinitra rimane invariato. La
Figura 14
mostra questo stato.
Figura 14. GtkTable dopo l'espansione delle finestra in verticale.
Successivamente immaginate che l'utente ridimensioni nuovamente la finestra, questa volta in orizzontale. Unicamente il widget numero tre può espandersi in questa direzione, come viene mostrato in
Figura 15
.
Figura 15. GtkTable dopo l'espansione delle finestra in orizzontale
La
Figura 16
mostra cosa accade se l'utente ha ridotto la tabella verticalmente, fino a che non c'è più spazio sufficiente per soddisfare le richieste dei widget al suo interno. Il widget numero due si ridurrà, mentre il numero uno otterrà tutto lo spazio verticale di cui ha bisogno.
Figura 16. GtkTable dopo la riduzione in verticale della finestra
Infine,
Figura 17
mostra il risultato di una riduzione orizzontale. Il widget numero tre è quello che ha la peggio.
Figura 17. GtkTable dopo la riduzione in orizzontale della finestra
È una buona idea tentare di ridimensionare la finestra durante la sua creazione, per assicurarsi che tutto sia creato nel modo migliore.
Dato che gtk_table_attach() è piuttosto complicata, esiste una versione chiamata gtk_table_attach_defaults(), che semplifica questo tipo di operazione. Questa versione inserisce un widget con le opzioni GTK_EXPAND e GTK_FILL e nessun padding.
È una grande tentazione utilizzare gtk_table_attach_defaults() tutte le volte per ridurre i tempi, ma non dovreste farlo. Infatti, sembra strano a dirlo ma questa viene utilizzata raramente. Questa funzione è utile unicamente se le impostazioni di default che fornisce sono esattamente quelle di cui avete bisogno. La maggior parte delle volte, dovete controllare attentamente i parametri di inserimento in una tabella per ottenere un buon risultato quando questa viene ridimensionata.
I contenitori e le tabelle sono i widget di modellazione maggiormente utilizzati. Ad ogni modo, ne esistono altri per casi molto particolari.
GtkButtonBox è un particolare tipo di contenitore, appropriato per le "aree di azione" delle finestre di dialogo.
GtkPacker supporta lo stile di contenimento di Tk, utile se siete pratici di Tk.
GtkLayout fornisce un'area di scorrimento infinita. In generale, le aree di scorrimento di GTK+ sono limitate a 30.000 pixels, poiché questa è la dimensione massima di una finestra X.
GtkFixed permette di posizionare manualmente i widget, in base alle coordinate da voi fornite.
È possibile scavalcare manualmente la gestione della geometria di
GTK+. Questo risulta sbagliato il 95% delle volte, poiché la geometria in
GTK+ è essenzialmente una scelta dell'utente, basata sui temi e sul
ridimensionamento delle finestre principali. Se dovete apportare modifiche manualmente, probabilmente il motivo è che state utilizzando il tipo di contenitore sbagliato, oppure dovete scrivere un widget di contenimento personalizzato.
Potete forzare la posizione o la dimensione di un widget utilizzando le
funzioni mostrate in
Figura 19
. In ogni caso, è
raramente una buona idea utilizzarle. In particolare,
gtk_widget_set_usize() non deve essere utilizzata per
impostare la dimensione di una finestra principale. Solitamente viene
impostata la dimensione solo tramite i dati salvati e recuperati dalla
configurazione, oppure perché l'utente specifica la geometria della
finestra da riga di comando. Sfortunatamente, se utilizzate gtk_widget_set_usize() non sarà permesso all'utente di ridimensionare la finestra, e vi insulterà via posta elettronica. Piuttosto che forzare una dimensione, potete specificare una dimensione iniziale attraverso l'uso di gtk_window_set_default_size(), mostrata ine
Figura 20
. gtk_widget_set_usize() è scongliata anche per utilizzarla sui widget "normali". La maggior parte delle volte potete ottenere migliori risultati utilizzando il widget di contenimento giusto.
Queste funzioni possono accettere -1 come argomento x, y, width oppure height. Le funzioni ignorano qualunque argomento uguale a -1. Questo vi permette di impostare uno solo dei due argomenti, lasciando il valore di default per l'altro.