Traduzione a cura di Fabrizio Angius [qtsolutions -A-T- gmx.net]
English TOC.
Generalmente, la maggior parte delle applicazioni mostra dei dati sotto forma di liste, tabelle e alberi. Qt viene incontro a questa necessita' con una serie di classi, dalla semplice lista per testo alle tabelle piu' complesse. Iniziamo dalla parte piu' facile parlando della classe QListBox mostrata nella figura 13-1.
QListBox e' il tipo di lista piu' semplice. Generalmente viene utilizzata per mostrare un'unica colonna con elementi testuali ma puo' essere utilizzata in situazioni piu' avanzate. Per mostrare una lista di elementi testuali si utilizzano i metodi insertItem oppure insertStringList. Per svuotare la lista si utilizza il metodo clear. Una dimostrazione e' data nell'esempio 13-1.
ListBoxExample::ListBoxExample( QWidget *parent, char *name ) : QVBox( parent, name )
{
m_listBox = new QListBox( this );
QHBox *hb = new QHBox( this );
m_lineEdit = new QLineEdit( hb );
QPushButton *pbAdd = new QPushButton( "Add", hb );
QPushButton *pbClear = new QPushButton( "Clear", hb );
connect( pbAdd, SIGNAL(clicked()), this, SLOT(addItem()) );
connect( pbClear, SIGNAL(clicked()), m_listBox, SLOT(clear()) );
}
void ListBoxExample::addItem()
{
m_listBox->insertItem( m_lineEdit->text() );
m_lineEdit->setText( "" );
}
Il codice dell'esempio mostra l'implementazione di un widget che utilizza una QListBox. I puntatori m_listBox e m_lineEdit sono membri privati della classe. Notate come e' possibile connettere direttamente clear() a un segnale.
Ora sappiamo come popolare e svuotare una lista. Come facciamo a sapere quali elementi sono selezionati e come facciamo a sapere quando vengono selezionati? Una QListBox puo' avere tre tipi diversi di selezione: singola, multipla ed estesta.
E' anche disponibile una quarta modalita' che impedisce all'utente di selezionare qualcosa. La modalita' predefinita e' quella singola. Bene, ora come facciamo a sapere se c'e' un elemento selezionato e di quale elemento si tratta? Per fare una prova, aggiungiamo una label in fondo al nostro widget e uno slot che scrive sopra qual'e' la selezione corrente. Nel costruttore connettiamo il segnale selectionChanged al nostro slot. Il codice e' nell'esempio 13-2.
ListBoxExample::ListBoxExample( QWidget *parent, char *name ) : QVBox( parent, name )
{
...
connect( m_listBox, SIGNAL(selectionChanged()), this, SLOT(newSelection()) );
}
void ListBoxExample::newSelection()
{
if( !m_listBox->selectedItem() )
m_label->setText( "nothing" );
else
m_label->setText( m_listBox->selectedItem()->text() );
}
Provate l'esempio e giocate un po' con i pulsanti per generere segnali. Per esempio, notate come selectionChanged e' utilizzata al meglio con liste dal contenuto statico, dato che il segnale non viene emesso se la lista viene svuotata.
E' possibile sottoclassare QListBoxItem per avere delle liste piu' complesse. Un esempio e' dato da QListBoxPixmap e QjListBoxPixmap che rappresenta un QListBoxItem con testo e un'immagine. Il codice dell'esempio 13-3 mostra come impostare una list box con questo tipo di elementi al posto dei soliti con solo testo.
QListBox *lb = new QListBox();
QPixmap pm( 12, 12 );
pm.fill( Qt::red );
new QListBoxPixmap( lb, pm, "Red" );
pm.fill( Qt::yellow );
new QListBoxPixmap( lb, pm, "Yellow" );
pm.fill( Qt::green );
new QListBoxPixmap( lb, pm, "Green" );
pm.fill( Qt::cyan );
new QListBoxPixmap( lb, pm, "Cyan" );
pm.fill( Qt::blue );
new QListBoxPixmap( lb, pm, "Blue" );
pm.fill( Qt::magenta );
new QListBoxPixmap( lb, pm, "Magenta" );
Il risultato e' mostrato nella figura 13-2. Nel codice, osservate che gli elementi della list box vengono istanziati con un riferimento alla list box stessa. In questo modo essi si aggiungono automaticamente alla lista. Fate un confronto con il modo in cui si inseriscono elementi di solo testo. E' possibile utilizzare il metodo insertItem anche con gli elementi personalizzati. In questo caso, il codice assomiglierebbe allo slot addItem dell'esempio 13-1.
Quando si ha a che fare con dati piu' complessi, distribuiti su piu' colonne, conviene utilizzare il widget QListView al posto della QListBox. La QListView puo' mostrare dati su piu' colonne sotto forma di lista o di albero. Ogni colonna puo' avere un'intestazione per permettere agli utenti di ordinare i dati. La figura 13-3 mostra una schermata con l'applicazione dell'esempio.
Il widget list view e' piuttosto complesso, per cui l'esempio e' suddiviso in quattro parti, ognuna mostrata in un tab diverso nell'applicazione di esempio. Iniziamo con il caso piu' semplice - una banale lista con piu' colonne. Il tutto e' mostrato nel tab intitolato "List" e mostrato nella figura 13-3. Il codice per la list view e' mostrato nell'esempio 13-4.
QWidget *ListViewExample::setupListTab()
{
m_listView = new QListView();
m_listView->addColumn( "Foo" );
m_listView->addColumn( "Bar" );
m_listView->addColumn( "Baz" );
m_listView->setAllColumnsShowFocus( true );
new QListViewItem( m_listView, "(1, 1)", "(1, 2)", "(1, 3)" );
new QListViewItem( m_listView, "(2, 1)", "(2, 2)", "(2, 3)" );
new QListViewItem( m_listView, "(3, 1)", "(3, 2)", "(3, 3)" );
new QListViewItem( m_listView, "(4, 1)", "(4, 2)", "(4, 3)" );
return m_listView;
}
Il codice mostra i passi necessari per utilizzare una list view. Per iniziare, si crea una serie di colonne. Quindi si impostano le proprieta' che ci interessano. In questo caso, ci assicuriamo che la selezione venga mostrata su tutta la riga e non solo sull'elemento nella prima colonna. Infine, popoliamo la lista creando una serie di QListViewItem a cui passiamo un puntatore alla lista come parametro.
Nel secondo caso utilizziamo una struttura ad albero per mostrare gli elementi della lista. Il tutto e' mostrato nel tab intitolato "Tree". Il codice e' nell'esempio 13-5. Quando eseguite l'applicazione dell'esempio, provate a fare click sulle intestazioni delle colonne per ordinare gli elementi. Notate che ciascun ramo ("A", "B" e "C") viene ordinato indipendentemente.
QWidget *ListViewExample::setupTreeTab()
{
m_treeView = new QListView();
m_treeView->addColumn( "Tree" );
m_treeView->addColumn( "First" );
m_treeView->addColumn( "Second" );
m_treeView->addColumn( "Third" );
m_treeView->setRootIsDecorated( true );
QListViewItem *root = new QListViewItem( m_treeView, "root" );
QListViewItem *a = new QListViewItem( root, "A" );
QListViewItem *b = new QListViewItem( root, "B" );
QListViewItem *c = new QListViewItem( root, "C" );
new QListViewItem( a, "foo", "1", "2", "3" );
new QListViewItem( a, "bar", "i", "ii", "iii" );
new QListViewItem( a, "baz", "a", "b", "c" );
new QListViewItem( b, "foo", "1", "2", "3" );
new QListViewItem( b, "bar", "i", "ii", "iii" );
new QListViewItem( b, "baz", "a", "b", "c" );
new QListViewItem( c, "foo", "1", "2", "3" );
new QListViewItem( c, "bar", "i", "ii", "iii" );
new QListViewItem( c, "baz", "a", "b", "c" );
return m_treeView;
}
Il codice e' strutturato come nel caso della lista semplice. La differenza e' che al posto di usare il widget della listview come parent per tutti gli elementi, alcuni hanno altri elementi come parent. Alcuni elementi sono decorati con un "+" per indicare all'utente che ci sono altri elementi "figli" di quello.
Il terzo caso mostra un problema comune conscio alla maggior parte degli utenti windows. Se cercate di ordinare dei numeri che vengono trattati come stringhe, vi ritroverete con il dieci tra l'uno e il due. Il modo piu' veloce, ma non il piu' bello, di risolvere il tutto e' quello di riempire i numeri aggiungendo degli zeri nella parte sinistra o di trattare i numeri come cifre e non stringhe.
class SortItem : public QListViewItem
{
public:
SortItem( QListView *parent, QString c1, QString c2, QString c3, QString c4 ) : QListViewItem( parent, c1, c2, c3, c4 )
{
}
int compare( QListViewItem *i, int col, bool asc ) const
{
if( col == 2 )
return text( col ).toInt() - i->text( col ).toInt();
else
return QListViewItem::compare( i, col, asc );
}
};
L'esempio sull'ordinamento utilizza una sottoclasse personalizzata di QListViewItem mostrata nell'esempio 13-6. La sottoclasse reimplementa il metodo compare e tratta gli elementi della terza colonna come numeri e non testo. Le altre colonne utilizzano il metodo QListViewItem::compare predefinito.
Provate ad eseguire l'esempio come mostrato dalla figura 13-5. Cambiate l'ordinamento sulle varie colonne e osservate il risultato. Notate anche come ordinando la quarta colonna vengano prese in considerazione le minuscole e le maiuscole. Il codice per impostare la listview e' nell'esempio 13-7.
QWidget *ListViewExample::setupSortTab()
{
m_sortView = new QListView();
m_sortView->addColumn( "Number" );
m_sortView->addColumn( "Padded" );
m_sortView->addColumn( "Corrected" );
m_sortView->addColumn( "Alphabetical" );
m_sortView->setShowSortIndicator( true );
new SortItem( m_sortView, "1", "02", "1", "foo" );
new SortItem( m_sortView, "3", "04", "1", "bar" );
new SortItem( m_sortView, "5", "06", "2", "foo" );
new SortItem( m_sortView, "7", "08", "3", "bar" );
new SortItem( m_sortView, "9", "10", "5", "Foo" );
new SortItem( m_sortView, "11", "12", "8", "bar" );
new SortItem( m_sortView, "13", "14", "13", "foo" );
new SortItem( m_sortView, "15", "00", "21", "Bar" );
return m_sortView;
}
Il tab "Icons" mostra come sia facile inserire delle icone in una listview. Possono esserci elementi diversi in colonne diverse e piu' icone su ciascuna riga. Il codice sorgente e' mostrato nell'esempio 13-8. Come potete osservare, aggiungere un'icona e' banale quanto un'invocazione del metodo setPixmap.
QWidget *ListViewExample::setupIconTab()
{
m_iconView = new QListView();
m_iconView->addColumn( "Foo" );
m_iconView->addColumn( "Bar" );
m_iconView->addColumn( "Baz" );
m_iconView->setAllColumnsShowFocus( true );
QPixmap pm( 10, 10 );
QListViewItem *lvi = new QListViewItem( m_iconView, "(1, 1)", "(1, 2)", "(1, 3)" );
pm.fill( Qt::red );
lvi->setPixmap( 0, pm );
lvi = new QListViewItem( m_iconView, "(2, 1)", "(2, 2)", "(2, 3)" );
pm.fill( Qt::green );
lvi->setPixmap( 1, pm );
lvi = new QListViewItem( m_iconView, "(3, 1)", "(3, 2)", "(3, 3)" );
pm.fill( Qt::blue );
lvi->setPixmap( 2, pm );
lvi = new QListViewItem( m_iconView, "(4, 1)", "(4, 2)", "(4, 3)" );
pm.fill( Qt::yellow );
lvi->setPixmap( 0, pm );
pm.fill( Qt::magenta );
lvi->setPixmap( 2, pm );
return m_iconView;
}
Lavorare con una list view e' molto simile a lavorare con una list box. Il segnale selectionChanged e' ancora disponibile, insieme ai metodi isSelected e setSelected. Nelle applicazioni moderne preferisco le list view al posto delle list box dato che le colonne hanno un'intestazione che fornisce all'utente ulteriori informazioni.
Uno dei problemi che si riscontrano lavorando con una lista e' la sincronizzazione tra i dati contenuti nella lista e quelli gestiti dall'applicazione. Per esempio, supponiamo che abbiate un'applicazione che gestisce un elenco telefonico. Ciascuna voce dell'elenco telefonico ha un'immagine e alcuni dettagli ad essa associati, come nome, numero di telefono, email, indirizzo, ecc. La list view che mostra i contatti non deve necessariamente mostrare tutte queste informazioni. Per esempio, finche' non si seleziona un elemento potrebbe essere sufficiente il solo nome. Ci sono diversi modi per affrontare questo problema:
Le prime tre soluzioni sono le mie preferite. La terza se si utilizza una sola vista, la seconda se tutti i dati sono in memoria e la prima se i dati sono troppi per essere mantenuti in memoria. Per implementare la prima soluzione e' necessaria una colonna nascosta. In base a questo consiglio, ciascuna colonna da nascondere deve avere la proprieta' "width mode" impostata su manuale. Il codice per nascondere la terza colonna della list view lv, compreso il cambiamento di "width mode", e' mostrato nell'esempio 13-9.
lv->setColumnWidthMode( 3, QListView::Manual );
lv->hideColumn( 3 );
Appena i dati diventano troppo strutturati e complessi, la list view e la list box potrebbero apparire troppo semplici. A questo punto diventa necessario l'utilizzo di una tabella. Con una QTable ogni cella della tabella puo' contenere un widget diverso. Per esempio checkbox, dropdown list o semplicemente testo.
E' semplice creare una tabella per poi popolarla con elementi utilizzando i metodi setText, setPixmap e setItem.
Quello che piu' importa e' in realta' la capacita' di creare elementi personalizzati. In questo modo e' possibile fornire agli utenti una migliore interazione. Per esempio, invece di selezionare il nome di un colore da un elenco, forniremo un elemento che mostri una lista di colori effettivi e che permetta all'utente di selezionarne uno. La figura 13-7 mostra l'applicazione completa.
Quello che abbiamo fatto e' sottoclassare QTableItem per creare una nuova classe, ColorTableItem. La dichiarazione della classe e' mostrata nell'esempio 13-10.
class ColorTableItem : public QTableItem
{
public:
ColorTableItem( QTable *table, const QString &color );
QWidget *createEditor() const;
void setContentFromEditor( QWidget *w );
void paint( QPainter *p, const QColorGroup &cg, const QRect &cr, bool selected );
};
Osservando il codice qui sopra potete notare come i metodi interessanti siano paint, createEditor e setContentFromEditor. Gli ultimi due lavorano insieme mentre il primo puo' essere implementato indipendentemente dagli altri.
Il metodo paint prende il testo della cella assumendo che si tratti del nome di un colore. Quindi utilizza quel colore per riempire la cella. L'esempio 13-11 mostra l'implementazione del metodo paint.
void ColorTableItem::paint( QPainter *p, const QColorGroup &cg, const QRect &cr, bool selected )
{
if( text() == "White" )
p->setBrush( Qt::white );
else if( text() == "Gray" )
p->setBrush( Qt::gray );
else if( text() == "Black" )
p->setBrush( Qt::black );
else if( text() == "Red" )
p->setBrush( Qt::red );
else if( text() == "Yellow" )
p->setBrush( Qt::yellow );
else if( text() == "Green" )
p->setBrush( Qt::green );
else if( text() == "Cyan" )
p->setBrush( Qt::cyan );
else if( text() == "Blue" )
p->setBrush( Qt::blue );
else if( text() == "Magenta" )
p->setBrush( Qt::magenta );
p->drawRect( table()->cellRect(row(), col()) );
}
I metodi createEditor e setContentFromEditor lavorano insieme. Come prima cosa, createEditor crea il widget utilizzato per modificare quella data cella, quindi si recupera il risultato dal widget utilizzando il metodo setContentFromEditor. L'esempio 13-12 mostra il codice per createEditor. Viene creata una combo box con colori come elementi e ci si assicura anche che venga selezionato l'elemento giusto dalla lista.
QWidget *ColorTableItem::createEditor() const
{
QComboBox *cb = new QComboBox( table()->viewport() );
QObject::connect( cb, SIGNAL( activated( int ) ), table(), SLOT( doValueChanged() ) );
QPixmap pm( 100, 20 );
pm.fill( Qt::white );
cb->insertItem( pm );
pm.fill( Qt::gray );
cb->insertItem( pm );
pm.fill( Qt::black );
cb->insertItem( pm );
pm.fill( Qt::red );
cb->insertItem( pm );
pm.fill( Qt::yellow );
cb->insertItem( pm );
pm.fill( Qt::green );
cb->insertItem( pm );
pm.fill( Qt::cyan );
cb->insertItem( pm );
pm.fill( Qt::blue );
cb->insertItem( pm );
pm.fill( Qt::magenta );
cb->insertItem( pm );
if( text() == "White" )
cb->setCurrentItem( 0 );
else if( text() == "Gray" )
cb->setCurrentItem( 1 );
else if( text() == "Black" )
cb->setCurrentItem( 2 );
else if( text() == "Red" )
cb->setCurrentItem( 3 );
else if( text() == "Yellow" )
cb->setCurrentItem( 4 );
else if( text() == "Green" )
cb->setCurrentItem( 5 );
else if( text() == "Cyan" )
cb->setCurrentItem( 6 );
else if( text() == "Blue" )
cb->setCurrentItem( 7 );
else if( text() == "Magenta" )
cb->setCurrentItem( 8 );
return cb;
}
A questo punto, e come mostrato nell'esempio 13-13, setContentFromEditor non fa altro che prendere il widget che funge da editor per poi assicurarsi che si tratti di una combo box e inserire il risultato nella cella.
void ColorTableItem::setContentFromEditor( QWidget *w )
{
if( w->inherits( "QComboBox" ) )
{
switch( ((QComboBox*)w)->currentItem() )
{
case 0:
setText( "White" );
break;
case 1:
setText( "Gray" );
break;
case 2:
setText( "Black" );
break;
case 3:
setText( "Red" );
break;
case 4:
setText( "Yellow" );
break;
case 5:
setText( "Green" );
break;
case 6:
setText( "Cyan" );
break;
case 7:
setText( "Blue" );
break;
case 8:
setText( "Magenta" );
break;
}
}
else
QTableItem::setContentFromEditor( w );
}
Il codice per creare la tabella con gli elementi personalizzati e' nell'esempio 13-14. Osservate che affinche' il tutto funzioni, i nomi dei colori devono essere validi. Si puo' risolvere questo problema utilizzando una emumerazione o semplicemente QColor.
QTable t( 3, 3 );
t.setItem( 0, 0, new ColorTableItem( &t, "Red" ) );
t.setItem( 0, 1, new ColorTableItem( &t, "Green" ) );
t.setItem( 0, 2, new ColorTableItem( &t, "Black" ) );
t.setItem( 1, 0, new ColorTableItem( &t, "Yellow" ) );
t.setItem( 1, 1, new ColorTableItem( &t, "Magenta" ) );
t.setItem( 1, 2, new ColorTableItem( &t, "Blue" ) );
t.setItem( 2, 0, new ColorTableItem( &t, "Gray" ) );
t.setItem( 2, 1, new ColorTableItem( &t, "Cyan" ) );
t.setItem( 2, 2, new ColorTableItem( &t, "White" ) );
La classe QTable presenta numerose limitazioni, motivo per cui si sono diffuse soluzioni di terze parti. Una di queste e' QicsTable della ICS, disponibile con due licenze diverse esattamente come Qt. La versione open source ha licenza GPL. Risulta molto piu' flessibile sotto ogni aspetto, dall'architettura model-view-controller alle celle unificabili, agli header piu' flessibili, alle righe e colonne bloccabili, alla possibilita' di stampare tabelle intere o solo parti di esse. Nel sito della ICS e' disponibile un documento intitolato Beyond QTable, che tratta le differenze tra QTable e QicsTable.
In Qt 4, le classi QListView e QTable verranno in parte unite all'interno del framework Interview. L'architettura si basera' sulle viste e su un modello contenente i dati. In questo modo si riduce la necessita' di memorizzare i dati in piu' locazioni di memoria (p.es. come elemento di una list view e in una struttura dati dell'applicazione). Ulteriori informazioni su Qt 4 sono disponibili presso la Trolltech.
Il codice per questo capitolo puo' essere scaricato qui.
Qt Quarterly presenta un paio di articoli che riguardano l'argomento di questo capitolo: 1 e 2.
This is a part of digitalfanatics.org.
Copyright (c) 2002-2004 by Johan Thelin (e8johan -at- digitalfanatics.org). This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 (local copy) or later (the latest version is presently available at http://www.opencontent.org/openpub/). Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder. Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.