Traduzione a cura di Fabrizio Angius [qtsolutions -A-T- gmx.net]
English TOC.

6. Un'applicazione Qt scritta a mano

Questo capitolo e' un piccolo esempio per introdurre la scrittura di segnali e slot. Lo scopo e' quello di creare un'applicazione usando solo la riga di commando e un editor di testo. L'applicazione completa viene mostrata nella figura 6-1.

The resulting application

Figura 6-1 L'applicazione completa.

Nell'ultimo capitolo abbiamo usato i layout per definire e controllare il posizionamento dei widget. Questo approccio puo' essere seguito anche scrivendo tutto il codice manualmente, ma per cambiare un po' questa volta useremo i widget QVBox e QHBox. Una QVBox e' una scatola che dispone i propri widget figlio su una linea verticale, mentre QHBox li dispone orizzontalmente. Per implementare il layout che ci serve dovremmo annidare box verticali e orizzontali. Date un'occhiata alla figura 6-2 per farvi un'idea. Osservate che i box verticali e orizzontali sono identificati dalla direzione delle frecce sui lati di ogni box.

Il layout da implementare usando QVBox e QHBox

Figura 6-2 Il layout da implementare usando QVBox e QHBox.

Implementeremo l'intera applicazione come un unico widget. Questo va bene perche' si tratta solo di un esempio relativamente banale. Se avessimo dovuto usare i layout sarebbe stato meglio utilizzare un dialog. I diversi approcci che e' possibile seguire vengono discussi nel prossimo capitolo, per cui abbiate ancora un po' di pazienza.

Dato che la scatola piu' esterna (nella figura 6-2) e' una QVBox, facciamo ereditare il nostro widget da QVBox. Chiamiamo la classe HandMade e salviamo il file header come handmade.h. Il codice viene mostrato nell'esempio 6-1.

Notate gli #ifndef, #define ed #endif che circondano il codice. E' un modo per proteggere il file header da dichiarazioni multiple. In questo modo ci assicuriamo che la dichiarazione della classe venca vista una sola volta dal compilatore.

La prima cosa che facciamo all'interno di queste direttive e' dichiarare la classe che vogliamo far ereditare da QVBox. Ci riferiamo a tutte le altre classi colo con puntatori, per cui l'unica cosa che il compilatore deve sapere e' che si tratta di classi (questo perche' i puntatori occupano tutti la stessa quantita' di memoria, indipendentemente dal tipo di dato a cui puntano). Quindi, invece di includere subito i file header con le dichiarazioni delle classi inseriamo direttamente le dichiarazioni. In questo modo riduciamo il numero di file header che devono essere processati e quindi anche il tempo di compilazione. Includeremo gli header solo quando avremmo la necessita' di usare un membro di queste classi (o di dichiararle staticamente), per cui lo faremo solo nell'implementazione della nostra classe.

Dopo queste dichiarazioni inseriamo la dichiarazione della nostra classe. La prima cosa che deve fare una classe Qt (p.es. una classe derivata da QObject) e' specificare la macro Q_OBJECT. Questo ci assicura che segnali, slot e altri meccanismi di Qt funzionino correttamente.

La classe di per se' contiene solo un costruttore e due slot. I parametri del costruttore sono quelli standard di Qt - un widget padre e un nome. Il widget padre viene usato da Qt per la gestione della memoria (e altro) mentre il nome viene usato per il debug. Questi parametri saranno passati alla superclasse nell'implementazione. I due puntatori che dichiariamo (lcd e lb) verranno usati all'interno degli slot. Quelli di voi che si ricordano dell'ultimo capitolo e dello slot init() usato al posto del costruttore si staranno chiedendo quando bisogna utilizzare quale approccio. Il costruttore generato dal Designer (o, meglio, da un tool chiamato uic) crea il widget del dialog per poi invocare init() se esiste. Questo ci permette di non dover sottoclassare il dialog prodotto dal Designer solo per aggiungere un nostro costruttore.

#ifndef HANDMADE_H
#define HANDMADE_H

#include <qvbox.h>

class QLCDNumber;
class QListBox;

class HandMade : public QVBox
{
  Q_OBJECT

public:
  HandMade( QWidget *parent=0, char *name=0 );

protected slots:
  void addNumber();
  void removeNumber();

protected:
  QLCDNumber *lcd;
  QListBox *lb;
};

#endif

Esempio 6-1

Il prossimo passo consiste nell'implementare la classe. Il codice e' mostrato nell'esempio 6-2 ed e' contenuto nel file handmade.cpp.

Il file inizia includendo tutti gli header necessari - compreso handmade.h. Dato che il codice e' ampiamente commentato, ne lascio la comprensione come un esercizio per il lettore. Vale la pena osservare che i box sono riempiti o dall'alto verso il basso o da sinistra verso destra. E' un buon esercizio cercare di correlare la figura 6-2 al codice. Ricordate che il box piu' esterno viene gestito dalla superclasse QVBox. Inoltre, notate come i parametri parent e name vengano passati facilmente alla superclasse dal costruttore.

#include <qlcdnumber.h>
#include <qlistbox.h>
#include <qpushbutton.h>
#include <qhbox.h>
#include <qslider.h>

#include "handmade.h"

HandMade::HandMade( QWidget *parent, char *name ) : QVBox( parent, name )
{
  // Un box orizzontale per la lista, lo slider e l'LCD
  QHBox *hb = new QHBox( this );

  // La lista viene inserita per prima (quindi sulla sinistra)
  lb = new QListBox( hb );

  // Inseriamo un box verticale per sistemare l'LCD sopra allo slider
  // Lo mettiamo a destra della lista
  QVBox *vb = new QVBox( hb );

  // Creiamo prima l'LCD che verra' quindi messo in cima al box
  lcd = new QLCDNumber( vb );
  // Lo slider viene creato dopo per cui finira' sotto all'LCD
  QSlider *s = new QSlider( Horizontal, vb );
  // Connettiamo il segnale valueChanged(int) dello slider all'LCD
  connect( s, SIGNAL(valueChanged(int)), lcd, SLOT(setValue(int)) );

  // Un box orizzontale per i pulsanti, sotto a lista, slider ed LCD
  hb = new QHBox( this );

  // Il pulsante add andra' a sinistra
  QPushButton *pb = new QPushButton( "Add", hb );
  // Connettilo allo slot addNumber()
  connect( pb, SIGNAL(clicked()), this, SLOT(addNumber()) );
  // Il pulsante remove finiria' a destra di quello add
  pb = new QPushButton( "Remove", hb );
  // Connettilo allo slot removeNumber
  connect( pb, SIGNAL(clicked()), this, SLOT(removeNumber()) );
}

Esempio 6-2

Dopo aver scritto il codice dell'esempio 6-2 passiamo all'implementazione. Il codice viene mostrato nell'esempio 6-3 e inserito in un file chiamato handmade.cpp. Anche questo codice e' fortemente commentato, per cui non ci dovrebbero essere grossi problemi nell'interpretarlo (date un'occhiata alla documentazione Qt se non riuscite a capire un metodo). Notate che stiamo usando i puntatori dichiarati nella definizione della classe (lcd e lb), al contrario del puntatore pb che era accessibile solo al costruttore.

void HandMade::addNumber()
{
  // Inserisci semplicemente il numero mostrato dall'LCD
  lb->insertItem( QString::number( lcd->intValue() ) );
}

void HandMade::removeNumber()
{
  // Ritorna se non ci sono elementi selezionati
  if( lb->currentItem() < 0 )
    return;

  // Elimina l'elemento selezionato
  lb->removeItem( lb->currentItem() );
}

Esempio 6-3

Dobbiamo infine aggiungere l'ultimo pezzo di codice. Nel file main.cpp implementiamo un banale metodo main che crea un'istanza della classe che abbiamo scritto per poi avviare il ciclo degli eventi di Qt. Il codice viene mostrato nell'esempio 6-4.

#include <qapplication.h>

#include "handmade.h"

int main( int argc, char **argv )
{
  QApplication a( argc, argv );

  HandMade *hm = new HandMade();
  a.setMainWidget( hm );
  hm->show();

  return a.exec();
}

Esempio 6-4

Per creare il Makefile ci serve una descrizione del progetto. Usate il codice dell'esempio 6-5 e inseritelo in un file chiamato handmade.pro. Quindi eseguite qmake && make && ./handmade da una console per creare il Makefile, usatelo ed eseguite il file prodotto. (Il doppio ampersand - && - ci assicura che un commando venga eseguito solo se quello precedente e' terminato con successo. potevamo creare la descrizione del progetto eseguendo qmake -project, ma volevamo avere un'applicazione scritta completamente a mano.

SOURCES = handmade.cpp main.cpp
HEADERS = handmade.h

Esempio 6-5

Una volta eseguita l'applicazione, vi sembrera' che qualcosa non funzioni. L'LCD non cambia quando muovete lo slider! La spiegazione la potete trovare leggendo i messaggi di errore stampati su console (spero l'abbiate eseguito da una console). Il messaggio d'errore viene mostrato nell'esempio 6-6.

QObject::connect: No such slot QLCDNumber::setValue(int)
QObject::connect: (sender name: 'unnamed')
QObject::connect: (receiver name: 'unnamed')

Esempio 6-6

Prima ho fatto un accenno ai parametri standard di Qt, dicendo che il nome veniva usato per il debug. Se avessimo dato un nome ai widget, ora lo avremmo visto nei messaggi d'errore al posto dei vari "unnamed", rendendo piu' semplice il debug del codice. In questo piccolo esempio non sara' difficile trovare il problema anche senza i nomi. In base alla documentazione Qt, lo slot mancante dell'LCD, setValue(int), si chiama display(int). Basta sistemarlo come mostrato nell'esempio 6-7.

  // Connettiamo il segnale valueChanged(int) dello slider all'LCD
  connect( s, SIGNAL(valueChanged(int)), lcd, SLOT(display(int)) );

Esempio 6-7

Dopo aver ricompilato il tutto, l'applicazione dovrebbe funzionare correttamente (eseguite solo make, non serve qmake dato che non abbiamo aggiunto altri file). Provate a spostare lo slider e ad aggiungere o rimuovere elementi dalla lista.

Riassunto

L'esempio di questo capitolo mostra come e' semplice produrre un'applicazione con Qt evitando di usare il Designer. In realta', si e' soliti gestire i layout con il Designer, mentre il codice continua ad avere un ruolo fondamentale per fornire tutte le funzionalita'.

Avete visto quanti messaggi di errore riguardano segnali e slot. Risulta quindi fonadamentale provare tutte le connessioni, ma cio' puo' essere fatto facilmente utilizzando il Designer.

Avete anche visto quanto e' semplice creare un progetto con qmake. Si tratta di un approccio decisamente piu' semplice di quanto non lo sia la scrittura di un makefile.

Infine, avete visto come e' semplice combinare widget gia' esistenti per creare un widget composto.

Il codice sorgente per questo capitolo lo trovate qui.

Esercizi

  1. Fate incrementare lo slider di uno ogni volta che si aggiunge preme il pulsante add.
  2. Create un widget, LCDSlider contenente l'LCD e lo slider derivati dal QVBox. Fornite lo slot setValue(int) per modificare il valore visualizzato. Usate il nuovo widget nel codice fornito in questo capitolo.

Letture consigliate

This is a part of digitalfanatics.org and is valid XHTML.