Introduzione a Qt

Benvenuto nel mondo di Qt, il toolkit grafico multipiattaforma. In questa guida, ti verranno spiegate le basi di Qt, creando un semplice Blocco Note. Dopo aver letto questa guida, dovresti iniziare ad approfondire leggendo la guida di Qt, per trovare le informazioni che ti servono per sviluppare le tue applicazioni.

Hello Notepad

In questo esempio, creeremo un piccolo editor di testo in una finestra sul desktop. Questo è il programma con interfaccia grafica più semplice da utilizzare con Qt.

Risultato

Ecco il codice:

  1. #include <QApplication>
  2. #include <QTextEdit>
  3.  
  4. int main(int argv, char **args)
  5. {
  6.     QApplication app(argv, args);
  7.     QTextEdit textEdit;
  8.     textEdit.show();
  9.  
  10.     return app.exec();
  11. }

Osserviamo il codice riga per riga. Nelle prime due righe includiamo le intestazioni per QApplication e QTextEdit, le due classi che verranno utilizzate in questo esempio. Il nome del file da includere per ogni classe corrisponde al nome di questa.

La riga 6 serve a creare un oggetto di tipo QApplication. Questo oggetto gestisce le risorse necessarie all’applicazione e serve a far funzionare un programma Qt che presenta un’interfaccia grafica. Richiede argv e args perché Qt accetta alcuni parametri a riga di comando.

La riga 8 crea un oggetto di tipo QTextEdit, un elemento visuale dell’interfaccia grafica. Il termine corretto utilizzato in Qt è Widget. Altri esempi sono le barre di scorrimento, le etichette e i pulsanti radio. Una widget può essere anche il contenitore per altre widget; una finestra di dialogo o la finestra principale dell’applicazione, ad esempio.

La riga 9 mostra l’editor sullo schermo usando il bordo delle finestre. Dato che le widget possono anche fungere da contenitori (come QMainWindow, che contiene barre degli strumenti, menu, una barra di stato e altre), è possibile mostrare una singola widget nella propria finestra. Le widget non sono visibili di default; è necessario quindi usare la funzione show() per mostrarle sullo schermo.

La linea 11 avvia il loop di eventi di QApplication. Quando un’applicazione Qt è in esecuzione vengono generati degli eventi che vengono inviati alle varie widget dell’applicazione. Esempi di eventi sono i clic del mouse e la digitazione da tastiera. Quando scrivi del testo nell’editor, questo riceve gli eventi di pressione dei tasti e risponde disegnando il testo inserito.

Per testare l’applicazione, apri un prompt dei comandi ed entra nella directory dove hai salvato il file .cpp. Per compilare il programma digita:

  1. qmake -project
  2. qmake
  3. make

Questi comandi genereranno un eseguibile nella directory part1 (su Windows è possibile dover usare nmake invece di make. Inoltre, l’eseguibile si troverà in part1/debug oppure in part1/release). qmake è lo strumento di Qt per generare i makefile che verranno poi letti da make per generare il programma vero e proprio. Usando lo switch -project si possono generare i file di configurazione per qmake (con estensione .pro). Più avanti imparerai a scrivere file .pro personalizzati.

Approfondimenti

ArgomentoCollegamento
Widgets e geomentria delle finetre Window and Dialog Widgets [doc.qt.nokia.com]
Eventi e loro gestioneThe Event System [doc.qt.nokia.com]

Aggiungere un pulsante per chiudere l’applicazione

In un’applicazione, normalmente, serviranno più di una widget. Introduciamo ora un QPushButton sotto l’editor di testo. Il pulsante uscirà dal nostro Blocco Note quando premuto (ad esempio quando si effettua un click del mouse su di esso).

Risultato

Osserviamo ora il codice:

  1. #include <QtGui>
  2. int main(int argv, char **args)
  3. {
  4.     QApplication app(argv, args);
  5.  
  6.     QTextEdit textEdit;
  7.     QPushButton quitButton("Quit");
  8.     QObject::connect(&quitButton, SIGNAL(clicked()), qApp, SLOT(quit()));
  9.     QVBoxLayout layout;
  10.     layout.addWidget(&textEdit);
  11.     layout.addWidget(&quitButton);
  12.  
  13.     QWidget window;
  14.     window.setLayout(&layout);
  15.  
  16.     window.show();
  17.     return app.exec();
  18. }

La riga 1 include QtGui, che contenente tutte le classi di Qt per la gestione dell’interfaccia grafica.

La riga 10 utilizza il sistema di Segnali e Slot per fare in modo che l’applicazione venga chiusa quando si preme il pulsante Quit. Uno slot è una funzione che può venire invocata a runtime usando il suo nome (come una stringa di letterali). Un segnale è una funzione che quando chiamata invoca gli slot registrati ad essa; questi metodi sono chiamati connettere lo slot al segnale ed emettere il segnale.

quit() è uno slot di QApplication che chiude l’applicazione. clicked() è un segnale che QPushButton emette quando viene premuto. La funzione statica QObject::connect() si prende cura di connettere lo slot al segnale. SIGNAL e SLOT sono due macro che hanno come parametri le funzioni signal e slot da connettere.

La riga 12 crea un QVBoxLayout. Come detto in precedenza, una widget può contenere altre widget. E’ possibile impostare la geometria (posizione e grandezza) delle widget figlie direttamente, ma usare un layout è di solito più semplice. Un layout controlla e modifica di conseguenza la geometria dei figli. QVBoxLayout, ad esempio, li posiziona in colonna.

Le righe 13 e 14 aggiungono l’editor e il pulsante. Nella riga 17, impostiamo il layout in una widget.

Approfondimenti

ArgomentoCollegamento
Segnali e slotsSignals & Slots [doc.qt.nokia.com]
LayoutsLayout Management [doc.qt.nokia.com] , Widgets and Layouts [doc.qt.nokia.com] , Layout Examples [doc.qt.nokia.com]
I widgets di QtQt Widget Gallery [doc.qt.nokia.com] , Widget Examples [doc.qt.nokia.com]

Ereditare da QWdget

Quando l’utente chiude un applicazione, potresti voler mostrare un pop-up che chiede all’utente se desidera veramente uscire. In questo esempio, creeremo una sottoclasse di QWidget e aggiungeremo uno slot che connettiamo al pulsante Quit.

Risultato

  1. class Notepad : public QWidget
  2. {
  3.     Q_OBJECT
  4. public:
  5.     Notepad();
  6.  
  7. private slots:
  8.     void quit();
  9.  
  10. private:
  11.     QTextEdit *textEdit;
  12.     QPushButton *quitButton;
  13. };

La macro Q_OBJECT deve essere subito dopo la parentesi graffa; serve infatti a dichiarare la nostra classe come QObject (ovviamente, deve anche ereditare da QObject). Un oggetto di tipo QObject aggiunge moltissime importanti funzionalità ad una normale classe C++. Tra queste la possibilità di controllare il nome della classe a runtime. Si può anche controllare i tipi dei parametri di uno slot ed invocarlo.

La riga 13 dichiara lo slot quit() usando la macro slot. La funzione creata in questo modo può essere collegata a qualunque segnale con la stessa signature (ogni slot senza parametri quindi).

Invece che impostare l’interfaccia grafica e connettere lo slot nella funzione main(), effettueremo questo passaggio nel costruttore di Notepad$.

  1. Notepad::Notepad()
  2. {
  3.     textEdit = new QTextEdit;
  4.     quitButton = new QPushButton(tr("Quit"));
  5.    
  6.     connect(quitButton, SIGNAL(clicked()), this, SLOT(quit()));
  7.    
  8.     QVBoxLayout *layout = new QVBoxLayout;
  9.     layout->addWidget(textEdit);
  10.     layout->addWidget(quitButton);
  11.    
  12.     setLayout(layout);
  13.    
  14.     setWindowTitle(tr("Notepad"));
  15. }

Come si può vedere nella definizione della classe usiamo i puntatori ai QObject textEdit e quitButton. E’ necessario allocare gli oggetti nella memoria heap e mai copiarli.

Usiamo ora la funzione tr() attorno ad ogni stringa visibile all’utente finale. Questa funzione è necessaria se si vuole distribuire la propria applicazione in più di una lingua (ad esempio Inglese e Italiano). Non approfondiremo qui questo argomento ma è possibile approfondire usando il seguente link a Qt Linguist.

Approfondimenti

ArgomentoCollegamento
tr() e internazionalizzazioneQt Linguist Manual [doc.qt.nokia.com] , Writing Source Code for Translation [doc.qt.nokia.com] , Hello tr() Example [doc.qt.nokia.com] , Internationalization with Qt [doc.qt.nokia.com]
QObjects ed il modello a oggeti di Qt (essenziale per comprendere Qt)Object Model [doc.qt.nokia.com]
qmake ed il sistema di compilazione di Qtqmake Manual [doc.qt.nokia.com]

Creare un file .pro

In questo esempio, scriveremo il nostro file .pro invece che avvalerci dell’opzione -project di qmake.

  1. HEADERS =  notepad.h
  2. SOURCES =  notepad.cpp \
  3.            main.cpp

I seguenti comandi da terminale compilano l’esempio.

  1. qmake
  2. make

Usare QMainWindow

Moltissime applicazioni trarranno beneficio dall’uso di QMainWindow; essa ha il proprio layout ed è possibile aggiungere una barra dei menu, widget ancorabili, barre degli strumenti e una barra di stato. QMainWindow ha un’area centrale che può essere occupata da ogni genere di widget. Nel nostro caso, posizioneremo il nostro editor qui.

Risultato

Osserviamo la nuova definizione di Notepad.

  1. #include <QtGui>
  2.  
  3. class Notepad : public QMainWindow
  4. {
  5.     Q_OBJECT
  6.  
  7. public:
  8.     Notepad();
  9.  
  10. private slots:
  11.     void open();
  12.     void save();
  13.     void quit();
  14.  
  15. private:
  16.     QTextEdit *textEdit;
  17.  
  18.     QAction *openAction;
  19.     QAction *saveAction;
  20.     QAction *exitAction;
  21.  
  22.     QMenu *fileMenu;
  23. };

Includiamo due slot aggiuntivi che possono aprire e salvare un documento. Questi verranno implementati nella sezione seguente.

Spesso, in una finestra MainWindow, lo stesso slot può venire invocato da widget differenti, ad esempio da un menu o da un pulsante. Per semplificarne l’implementazione Qt fornisce QAction, che può essere inserita in widget differenti e connessa ad uno slot. Ad esempio sia QMenu che QToolBar possono creare rispettivamente menu e pulsanti rispettivamente dalla stessa QAction. Ne vedremo il funzionamento tra breve.

Le QAction sono create mediante l’inserimento del testo che dovrebbe apparire sulle widget a qui vengono aggiunte (nel nostro caso in un menu). Se vogliamo anche aggiungerle ad una toolbar, possiamo anche inserire un icona.

Quando un menu riceve un click del mouse, l’oggetto invocherà lo slot associato.

Approfondimenti

ArgomentoCollegamento
Main windows e classi main windowApplication Main Window [doc.qt.nokia.com] , Main Window Examples [doc.qt.nokia.com]
Applicazioni MDIQMdiArea [doc.qt.nokia.com] , MDI Example [doc.qt.nokia.com]

Salvataggio e caricamento

In questo esempio, implementeremo le funzionalità di open() e save().

Risultato

Iniziamo con open():

  1. QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"), "",
  2.     tr("Text Files (*.txt);;C++ Files (*.cpp *.h)"));
  3.  
  4. if (fileName != "") {
  5.     QFile file(fileName);
  6.     if (!file.open(QIODevice::ReadOnly)) {
  7.         QMessageBox::critical(this, tr("Error"),
  8.             tr("Could not open file"));
  9.         return;
  10.     }
  11.     QString contents = file.readAll().constData();
  12.     textEdit->setPlainText(contents);
  13.     file.close();
  14. }

Il primo passo consiste nel chiedere all’utente il nome del file da aprire. In Qt si può usare QFileDialog, che è una finestra in cui l’utente può scegliere un file. L’immagine sopra mostra la schermata in Kubuntu. La funzione statica getOpenFileName() mostra una finestra modale e non ritorna alcun valore finché un file non viene selezionato. Ritorna la stringa vuota se si è premuto il tasto Cancel.

Se fileName è diverso dalla stringa vuota, proviamo ad aprirlo con open(), che ritorna true se il file può essere aperto. Non ci dilunghiamo in questa sede sulla gestione degli errori ma per approfondimenti è sempre possibile vedere la sezione apposita. Se il file non può essere aperto, usiamo QMessageBox per mostrare una finestra con un messaggio di errore (si veda la classe QMessageBox per maggiori dettagli).

Leggere un file è semplice usando la funzione readAll() che ritorna tutti i dati del file in un QByteArray. constData() ritorna tutti i dati nel tipo const char*, per il quale QString ha un costruttore. I contenuti vengono poi mostrati nell’editor. Per finire chiudiamo il file con close().

Proseguiamo ora con save().

  1. QString fileName = QFileDialog::getSaveFileName(this, tr("Save File"), "",
  2.     tr("Text Files (*.txt);;C++ Files (*.cpp *.h)"));
  3.  
  4. if (fileName != "") {
  5.     QFile file(fileName);
  6.     if (!file.open(QIODevice::WriteOnly)) {
  7.         // error message
  8.     } else {
  9.         QTextStream stream(&file);
  10.         stream << textEdit->toPlainText();
  11.         stream.flush();
  12.         file.close();
  13.     }
  14. }

Quando scriviamo i contenuti di un editor di testo in un file si usa la classe QTextStream che lavora intorno a QFile. Lo stream di testo può scrivere delle QString direttamente sul file in quanto QFile accetta solamente dati grezzi (char*) usando le funzioni wirte di QIODevice.

Approfondimenti

ArgomentoCollegamento
Files e dispositivi di I/OQFile [doc.qt.nokia.com] , QIODevice [doc.qt.nokia.com]