giovedì 30 aprile 2009

Qt: la gestione dei layout

I layout della libreria Qt, classi ereditare da QLayout, sono molto potenti e semplici da usare. I programmatori di Trolltech hanno fatto un così buon lavoro che dalla prima versione della libreria ad oggi esistono pochissimi layout (QGridLayout, QHBoxLayout, QVBoxLayout) capaci di svolgere tutte le funzioni che complessi layout manager svolgono in altri GUI-toolkit.
Tuttavia la gestione di un layout in Qt potrebbe portare a qualche dubbio circa dove gli oggetti sono collegati e a come funzionano. Consideriamo il seguente esempio:

QWidget* container = new QWidget();
QHBoxLayout* layout = new QHBoxLayout( container );
QPushButton* b1 = new QPushButton("Pulsante 1", container);
QPushButton* b2 = new QPushButton("Pulsante 2", container);
layout->addWidget( b1 );
layout->addWidget( b2 );
container->setLayout( layout );

Il fatto "anomalo" è che i pulsanti vengono creati avendo come parent il widget container, e poi vengono anche aggiunti al layout, che a sua volta è usato dal container per disporre i widget. Ma perché è necessario "aggiungere" i componenti sia al pannello contenitore che al layout? La ragione di cio' risiede nel modo in cui i layout funzionano.
Ogni layout mantiene al suo interno una struttura interna con riferimenti ai widget da visualizzare (es. una lista); quando il widget che ha impostato il layout necessita di una visualizzazione, il layout scorre la lista dei suoi widget e calcola la geometria di ognuno di essi (ad esempio la dimensione di un pulsante). Per ogni componente il layout invoca setGeometry() in modo da specificare come e dove visualizzare il widget. Invece la relazione di parentela fra i widget è necessaria per la gestione della memoria Qt. Ecco quindi che ogni widget deve essere aggiunto ad un genitore affinché si abbia la gestione della memoria, e ad un layout affinché quest'ultimo possa calcolarne le dimensioni.
Rapportando il codice di cui sopra a Java si nota come quest'ultimo risulta molto piu' compatto:

JPanel container = new JPanel();
FlowLayout layout = new FlowLayout();
container.setLayout( layout );
JButton b1 = new JButton("Pulsante 1");
JButton b2 = new JButton("Pulsante 2");
container.add( b1 );
container.add( b2 );

Quello che si nota è che i componenti vengono aggiunti solo al pannello container, che si fa carico poi di aggiungerli (o comunicare la loro presenza) al layout manager quando necessario. L'approccio Qt è invece inverso, si delega la gestione dei componenti al layout manager che li deve gestire esplicitamente. Inoltre è possibile avere casi in cui un componente non deve essere aggiunto al layout (magari perché non visibile) ma deve far parte comunque del widget (ad esempio per la gestione della memoria). Ad ogni modo l'approccio Qt è convertibile in quello Java adattando il codice di un Widget affinché passi la sua lista di Widget al layout nel momento in cui questo la debba conoscere. E' una scelta implementativa.

Da notare poi che nelle ultime versioni della libreria è anche possibile scrivere codice piu' compatto, come il seguente:

QWidget* container = new QWidget();
QHBoxLayout* layout = new QHBoxLayout( container );
QPushButton* b1 = new QPushButton("Pulsante 1");
QPushButton* b2 = new QPushButton("Pulsante 2");
layout->addWidget( b1 ); // internamente esegue b1->setParent( container );
layout->addWidget( b2 ); // internamente esegue b2->setParent( container );
container->setLayout( layout );

In questo caso i componenti sono aggiunti direttamente e solo al layout, che viene poi usato come layout manager di un widget principale. Si presti attenzione però al fatto che il layout manager fa solo da "passacarte" fra il widget principale e quelli secondari. Infatti ogni widget aggiunto al layout manager non appartiene al layout (ovvero non ha come parent il layout manager, poiché un widget può avere come parent solo un altro widget), bensì il layout manager imposta il parent di ogni widget al container che sta usando il layout.


Differenze con Java
L'approccio Java al layout è molto simile, ma la lista dei componenti non è nota a priori al layout, che riceve in ingresse al momento necessario il parent, ossia il componente contenitore (ad esempio un JPanel). Il layout richiede quindi al componente la lista di tutti i componenti e li posiziona geometricamente usando setBounds() su ogni componente.
La differenza fra i due approcci risulta quindi nella posizione in cui la lista dei componenti da sottoporre a layout viene gestita.

Nessun commento: