Beginnen met programmeren in Qt

Welkom in de wereld van Qt — de cross-platform GUI toolkit. In deze “getting started guide” zullen we u basis Qt kennis bijbrengen door een simpele kladblok applicatie te maken. Na het lezen van deze oefening, zult u klaar zijn om in onze overzichten en API documentatie te duiken, en alle informatie te vinden die u nodig heeft voor de applicatie die u ontwikkelt.

Hallo Kladblok

In dit eerste voorbeeld zullen we een eenvoudig venster op de desktop maken met daarin een text edit. Dit representeert het simpelste Qt programma met een GUI.

Hier is de code:

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

Laten we dit stuk code regel voor regel doornemen. In de eerste twee regels includen we de header files voor QApplication [doc.qt.nokia.com] en QTextEdit [doc.qt.nokia.com], die de twee classes zijn die we voor dit voorbeeld nodig hebben. Alle Qt classes hebben een header met zelfde naam.

Regel 6 creƫert een QApplication [doc.qt.nokia.com] object. Dit object beheert alle resources die nodig zijn voor een Qt applicatie met een GUI. Het heeft argv en args nodig omdat Qt applicaties enkele command line argumenten accepteren.

Regel 8 creert een QTextEdit [doc.qt.nokia.com] object. Een text edit is een visueel element in de GUI. In Qt noemen we dit soort elementen widgets. Voorbeelden van andere widgets zijn scroll bars, labels en radio knoppen. Een widget can ook een container zijn voor andere widgets, een dialog of een main applicatie bijvoorbeeld.

Regel 9 toont de text edit op het scherm in zijn eigen window frame. Omdat widgets ook als containers functioneren (bijvoorbeeld een QMainWindow [doc.qt.nokia.com], die toolbars, menus, een status bar, en een aantal andere widgets bezit), is het mogelijk om een enkele widget te tonen in zijn eigen window. Widgets zijn niet standaard zichtbaar. De functie show() [doc.qt.nokia.com] zorgt dat ze zichtbaar worden.

Regel 11 zorgt ervoor dat de QApplication [doc.qt.nokia.com] zijn event loop ingaat. Wanneer een Qt applicatie draait worden events gegenereerd en verzonden naar de widgets van de applicatie. Voorbeelden van events zijn muisklikken en toetsaanslagen. Wanneer je text typt in de text edit widget, ontvangt het toetsaanslagevents en reageert dan door de text te tekenen.

Om de applicatie te draaien open je een command prompt en voert de directory in waarin het .cpp bestand van het programma staat. De volgende opdrachten bouwen dan je programma.

  1. qmake -project
  2. qmake
  3. make

Dit laat een executable achter in de part1 map (onthoud dat op Windows je mogelijk gebruik moet maken van nmake in plaats van make. Daarnaast word de executable in de map part1/debug of part1/release geplaatst). qmake is Qt’s build applicatie dat een configuratie bestand accepteert. qmake genereert dit voor ons wanneer het argument -project word meegegeven. Aan de hand van het configuratie bestand (eindigend met .pro) produceert qmake een Makefile dat je programma voor je zal bouwen. We zullen later kijken naar hoe we zelf een .pro kunnen schrijven.

Extra informatie

OnderwerpHier
Widgets and Window GeometryWindow and Dialog Widgets [doc.qt.nokia.com]
Events and event handlingThe Event System [doc.qt.nokia.com]

Afsluitknop toevoegen

In een echte applicatie heb je normaal gesproken meer dan een widget nodig. We zullen nu de een QPushButton [doc.qt.nokia.com] introduceren onder de text edit. De knop zal de kladblok applicatie afsluiten wanneer er op wordt geklikt.

Laten we naar de code kijken.

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

Regel 1 includet QtGui [doc.qt.nokia.com], welke alle GUI classes van Qt bevat.

Regel 10 gebruikt de Signals en Slots mechanismen van Qt om te zorgen dat de applicatie afsluit wanneer op de Quit knop geklikt wordt. Een slot is een functie die als een programma draait, aangeroepen kan worden via zijn naam (als een string). Een signal is een functie die wanneer hij uitgevoerd wordt de slots zal aanroepen die erbij geregistreerd zijn; dat noemen we het slot verbinden met het signal en het signal uitzenden.

quit() [doc.qt.nokia.com] is een slot van QApplication [doc.qt.nokia.com] dat de applicatie afsluit. clicked() [doc.qt.nokia.com] is een signal dat QPushButton [doc.qt.nokia.com] uitzendt als erop geklikt wordt. De statische functie QObject::connect() [doc.qt.nokia.com] zorgt voor het verbinden van het slot met het signal. SIGNAL en SLOT zijn twee macros die de zogenaamde function signatures van het signal en het slot die verbonden dienen te worden als parameter nemen. We moeten ook pointers geven aan de objecten die het signal moeten zenden en ontvangen.

Regel 12 maakt een QVBoxLayout [doc.qt.nokia.com]. Zoals eerder besproken kunnen widgets ook een container zijn voor andere widgets. Het is mogelijk om de grenzen (afmeting en positie) van child widgets direct in te stellen, maar het is meestal gemakkelijker om een layout te gebruiken. Een layout beheert de grenzen van de widgets die in een andere widget geplaatst zijn, de zogenaamde kinderen van die container widget. QVBoxLayout, bijvoorbeeld, plaatst de kinderen in een verticale rij onder elkaar.

Regel 13 en 14 voegen het tekst bewerkings vak en de knop toe aan de layout. Met regel 17 plaatsen we de layout in een widget.

Extra informatie

OnderwerpHier
Signals and 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]
The widgets that come with QtQt Widget Gallery [doc.qt.nokia.com], Widget Examples [doc.qt.nokia.com]

Subklasse van QWidget

Het kan zijn dat als de gebruiker de applicatie afsluit je een dialoogscherm wil tonen met de vraag of hij/zij de applicatie echt wil afsluiten. In dit voorbeeld leiden we een klasse af van QWidget, en we voegen een slot toe dat we met de Quit knop verbinden.

Laten we naar de code kijken:

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

De Q_OBJECT [doc.qt.nokia.com] macro moet als eerste in de class definitie, het definieert onze class als een QObject [doc.qt.nokia.com] (Dit betekent dat je ook QObject [doc.qt.nokia.com] moet overerven). Een QObject [doc.qt.nokia.com] voegt meerdere extra opties toe aan een normale C++ class. Met name, de class naam en slot namen kunnen tijdens het runnen worden opgevraagd, daarnaast is het mogelijk om de parameters van een slot op te vragen en deze aan te roepen.

Regel 13 definieert het slot quit(). Dit wordt makkelijk gemaakt door de slots macro. Het quit() slot kan nu worden verbonden met signals die dezelfde function signature hebben (ieder signal dat geen parameters heeft).

In plaats van het opzetten van de GUI en het verbinden van het slot in de main() functie, maken we nu gebruik van de constructor van 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. }

Zoals je als zag in de class definitie, gebruiken we pointers voor onze QObjects [doc.qt.nokia.com] (textEdit en quitButton). Als een regel, moet je QObjects [doc.qt.nokia.com] altijd op de heap alloceren en er nooit een kopie van maken.

We maken nu gebruik van de functie tr() [doc.qt.nokia.com] rond onze zichtbare strings. Deze functie is nodig als je de applicatie in meer dan een taal wil aanbieden (bijvoorbeeld: Engels en Chinees). Hierover zullen we hier niet in meer detail op ingaan, maar je kan de Qt Linguist link van de extra informatie tabel volgen.

Extra informatie
OnderwerpHier
tr() and internationalizationQt Linguist Manual [doc.qt.nokia.com], Writing Source Code for Translation [doc.qt.nokia.com], Hello tr [doc.qt.nokia.com]() Example, Internationalization with Qt [doc.qt.nokia.com]
QObjects and the Qt Object model (This is essential to understand Qt)Object Model [doc.qt.nokia.com]
qmake and the Qt build systemqmake Manual [doc.qt.nokia.com]

Aanmaken van een .pro bestand

Voor dit voorbeeld maken we ons eigen .pro bestand, in plaats van gebruik te maken van de optie -project van qmake.

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

De volgende opdrachten bouwen dan het voorbeeld.

  1. qmake
  2. make

Gebruik maken van QMainWindow

Veel applicaties zullen ########## van het gebruik van een QMainWindow [doc.qt.nokia.com], welke zijn eigen layout heeft waaraan je bijvoorbeeld een menu balk, dock widgets, tool balken, en een status balk kan toevoegen. QMainWindow [doc.qt.nokia.com] heeft een centrum gebied dat gebruikt kan worden voor ieder type widget. In ons geval zullen we daar ons text edit plaatsen.

Laten we kijken naar de class definitie van 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. };

We includen nog 2 slots die documenten kunnen openen en opslaan. Deze zullen in de volgende sectie implementeren.

In een main window moet hetzelfde slot vaak door meerdere widgets worden aangeroepen. Voorbeelden hiervan zijn menu items en knoppen op een tool bar. Om dit makkelijker te maken heeft Qt QAction [doc.qt.nokia.com], welke aan meerdere widgets meegegeven kan worden en verbonden kan worden aan een slot. Zowel een QMenu [doc.qt.nokia.com] als een QToolBar [doc.qt.nokia.com] kunnen bijvoorbeeld menu items en tool buttons maken van dezelfde QActions [doc.qt.nokia.com]. We zullen zo zien hoe dit werkt.

Net als de vorige keer, gebruiken we de constructor van Notepad voor het aanmaken van de GUI.

  1. Notepad::Notepad()
  2. {
  3.     saveAction = new QAction(tr("&Open"), this);
  4.     saveAction = new QAction(tr("&Save"), this);
  5.     exitAction = new QAction(tr("E&xit"), this);
  6.  
  7.     connect(openAction, SIGNAL(triggered()), this, SLOT(open()));
  8.     connect(saveAction, SIGNAL(triggered()), this, SLOT(save()));
  9.     connect(exitAction, SIGNAL(triggered()), qApp, SLOT(quit()));
  10.  
  11.     fileMenu = menuBar()->addMenu(tr("&File"));
  12.     fileMenu->addAction(openAction);
  13.     fileMenu->addAction(saveAction);
  14.     fileMenu->addSeparator();
  15.     fileMenu->addAction(exitAction);
  16.  
  17.     textEdit = new QTextEdit;
  18.     setCentralWidget(textEdit);
  19.  
  20.     setWindowTitle(tr("Notepad"));
  21. }

QActions [doc.qt.nokia.com] worden aangemaakt met de text die zichtbaar moet zijn op de widgets waar we ze aan toevoegen (in ons geval, menu items). Als we ze ook aan een tool bar wilden toevoegen, zouden we een icon [doc.qt.nokia.com] kunnen meegeven aan de actions.

Wanneer er op een menu item word geklikt, zal dit de action triggeren, en het bijbehorende slot zal worden aangeroepen.

Extra informatie
OnderwerpHier
Main windows and main window classesApplication Main Window [doc.qt.nokia.com], Main Window Examples [doc.qt.nokia.com]
MDI applicationsQMdiArea [doc.qt.nokia.com], MDI Example [doc.qt.nokia.com]

Opslaan en laden

In dit voorbeeld zullen we de functionaliteit van de open() en save() slots, die we in het vorige voorbeeld hebben toegevoegd, implementeren.

We zullen beginnen met het open() slot:

  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. }

De eerst stap is om aan de gebruiker de naam van het bestand dat geopend moet worden te vragen. Qt heeft een QFileDialog [doc.qt.nokia.com], welke een dialog is waarin de gebruiker een bestand kan selecteren. Het plaatje hierboven toont dit dialog in Kubuntu. De static getOpenFileName() [doc.qt.nokia.com] functie toont een modal file dialog, en keert niet terug totdat de gebruiker een file heeft geselecteerd. Het geeft de bestands locatie van het bestand dat is geselecteerd terug, of een lege string wanneer de gebruiker op cancel heeft gedrukt.

Als we eenmaal een bestands naam hebben, proberen we dit bestand te openen met open() [doc.qt.nokia.com], welke true terug geeft als het bestand geopend kon worden. We zullen niet ingaan op de error afhandeling, maar je kan daarvoor de links in het extra informatie deel volgen. Als het bestand niet kon worden geopend, gebruiken we QMessageBox [doc.qt.nokia.com] om een dialog te tonen met daarin het error bericht (bekijk de QMessageBox [doc.qt.nokia.com] class beschrijving voor meer informatie).

Het lezen van data is eigenlijk heel triviaal door middel van de readAll() [doc.qt.nokia.com] functie, welke alle data in het bestand teruggeeft als een QByteArray [doc.qt.nokia.com]. De constData() [doc.qt.nokia.com] geeft alle data terug als een array van const char*, waar QString een constructor voor heeft. De inhoud kan dan worden getoond in de text edit. We sluiten dan het bestand af met close() [doc.qt.nokia.com] om de file descriptor terug te geven aan het besturings systeem.

Laten we nu kijken naar de implementatie van het save() slot.

  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. }

We schrijven de inhoud van de text edit naar het bestand naar welke word gewezen in het QFile [doc.qt.nokia.com] object, hiervoor gebruiken we de QTextStram [doc.qt.nokia.com] class. De text stream kan een QString [doc.qt.nokia.com] direct naar het bestand schrijven; QFile [doc.qt.nokia.com] accepteert alleen ruwe data (char*) met de write() [doc.qt.nokia.com] functies van het QIODevice [doc.qt.nokia.com].

Extra informatie
OnderwerpHier
Files and I/O devicesQFile [doc.qt.nokia.com], QIODevice [doc.qt.nokia.com]