Qt für C++ Anfänger: Das Grundgerüst

Die Erstellung des GUIs Bearbeiten

Die grafische Benutzeroberfläche wird mit dem Tool "Qt Designer" erstellt. Unter Linux lässt es sich mit dem Kommando "designer" bzw. "designer-qt4" aufrufen.

Der Taschenrechner soll später mal so aussehen:

 

Er besteht also aus einem Fenster (als Template bitte Main Window wählen) mit drei Textfeldern, drei "Eingabezeilen" und einem Knopf, um die Berechnung zu starten. Die Elemente, die ihr im Qt-Designer dazu zusammen klicken müsst, sind die folgenden, bitte auch genau mit diesen technischen Namen:

QLabel: LabelA, LabelB, LabelC
QLineEdit: InputA, InputB, ResultC
QPushButton: Calculate

Bitte auf Groß/Kleinschreibung achten. Die technischen Namen sind nicht die, die man im obigen Screenshot sieht, sondern die, die im Fenster "Property Editor" im Bereich QObject oben stehen; das Feld heißt objectName. Damit haben wir dann ein ui-File; wir speichern es unter Taschenrechner.ui in einem Verzeichnis, wo wir auch in Zukunft alle Files des Projektes speichern. Wo das Verzeichnis liegt, ist egal, es sollte aber bitte "Taschenrechner" genannt werden.

Das Code-Grundgerüst Bearbeiten

Damit wir nun auch wirklich ein C++-Programm erstellen können, brauchen wir ein Code-Gerüst, an dem wir später weitere Arbeiten vornehmen. Das erste Gerüst wird es uns erlauben, das Programm zu übersetzen und auszuführen – allerdings noch ohne weitere Funktionalität.

Wir werden drei C++-Files haben:


//--- main.cpp - start ---

#include "Taschenrechner.h"
#include <QApplication>

int main( int argc, char* argv[])
{
	QApplication a(argc, argv);
	Taschenrechner w;
	w.show();
	return a.exec();
}

//--- main.cpp - end ---


//--- Taschenrechner.h - start ---

#ifndef TASCHENRECHNER_H
#define TASCHENRECHNER_H

#include "ui_Taschenrechner.h"

class Taschenrechner : public QMainWindow, public Ui::MainWindow{
	Q_OBJECT

	public:
		Taschenrechner (QMainWindow *parent = 0);
		~Taschenrechner();
};
#endif //TASCHENRECHNER_H

//--- Taschenrechner.h - end ---


//--- Taschenrechner.cpp - start ---

#include "Taschenrechner.h"

Taschenrechner::Taschenrechner(QMainWindow *parent) : QMainWindow(parent){
	setupUi(this);
}

Taschenrechner::~Taschenrechner(){
}

//--- Taschenrechner.cpp - end ---


Auch hier bitte auf Groß- und Kleinschreibung achten, insbesondere wird das Wort "Taschenrechner" immer mit einem großen "T" geschrieben. Bitte erstellt die Files so wie hier angegeben; es macht erstmal nichts, wenn man den Code hier nicht versteht; die Erklärungen im Detail folgen weiter unten.

Das erste Übersetzen Bearbeiten

Wechsle in einer Shell in unser Projektverzeichnis und führe nacheinander folgende Befehle aus:

qmake -project
qmake
make
./Taschenrechner

Beachte: Bei manchen Distributionen heißt der Befehl qmake-qt4.

qmake -project Bearbeiten

Erzeugt eine .pro-Datei; die so genannte Projekt-Datei. Der Name des Files ist der Name des Projektverzeichnisses. In dieser Datei stehen im wesentlichen alle Dateien, die zu unserem Projekt gehören. Wenn man neue Dateien erstellt, also nicht nur die bestehenden ändert, muss dieser Befehl erneut aufgerufen werden. Man kann die .pro-Datei mit einem Texteditor öffnen und bekommt eine ganz gute Vorstellung, was darin steht.

qmake Bearbeiten

qmake erzeugt das Makefile.

make Bearbeiten

make übersetzt am Ende unsere Quelldateien basierend auf dem Makefile. Es werden auch noch Qt-spezifische Header-Dateien erstellt. Die Datei ui_Taschenrechner.h enthält den Qt-Code für die Benutzerschnittstelle (ui steht für engl. user interface). Das Endergebnis ist eine ausführbare Datei mit dem Namen des Projekts, welche identisch mit dem Namen unseres Projektverzeichnisses ist. Ist unter Windows der Aufruf von make nicht erfolgreich, muss der komplette Pfad zur make.exe eingegeben werden: z.B. <QT-Verzeichnis>\QT\Symbian\SDKs\Symbian3Qt474\epoc32\tools\make.

Das Grundgerüst im Detail Bearbeiten

Taschenrechner.h Bearbeiten

ist das Header File für unsere Klasse Taschenrechner. In diesem Header File finden wir die so genannten Prototypen unserer Funktionen. Also die Beschreibung welche Funktionen zur Verfügung stehen, hier wird aber keine Funktion implementiert.

Nun zu den Details:

03 #ifndef TASCHENRECHNER_H
04 #define TASCHENRECHNER_H
<...>
15 #endif //TASCHENRECHNER_H

sorgt dafür, dass der Header nur einmal eingebunden wird. Dabei handelt es sich um sogenannte Includewächter. Das sind Makros, die vom Präprozessor abgearbeitet werden, bevor der Compiler das Programm übersetzt. In Zeile 03 wird nachgeschaut, ob das Flag TASCHENRECHNER_H gesetzt ist. Ist es nicht gesetzt, wird der ganze Code bis zur Zeile 15 zum Übersetzen "freigegeben". Das #endif in Zeile 15 schließt diese if-Bedingung ab. #ifndef steht hier für "if not defined". Gesetzt wird ein solches Flag über die Präprozessor-Anweisung #define in Zeile 04. Beim allerersten Auffinden des obigen Blocks ist das Flag TASCHENRECHNER_H also noch nicht gesetzt und wird abgearbeitet. Durch diese Abarbeitung wird das Flag in Zeile 04 nun gesetzt. Trifft der Präprozessor nun ein weiteres Mal auf diesen Code, wird dieser einfach nicht mehr beachtet, was auch nicht mehr nötig ist, da alle Prototypen schon bekannt sind. Somit wird also verhindert, dass Definitionen vom Compiler mehrfach verarbeitet werden, was zu Fehlern beim Übersetzen führen würde. Die Form der Namensgebung dieses Flags (als eine Ableitung aus dem Dateinamen) ist eine weit verbreitete Konvention. Im Prinzip würde es jeder andere Name auch tun – was aber nicht zu empfehlen ist. Es würde Verwechslungen wahrscheinlicher machen.

06 #include "ui_Taschenrechner.h"

Dieses Header-File wurde von make als erstes erzeugt, wenn man dort rein schaut, erkennt man sehr leicht, dass hier alle UI Elemente zu finden sind.

08 class Taschenrechner : public QMainWindow, public Ui::MainWindow {
09	Q_OBJECT
10
11 public:
12	Taschenrechner (QMainWindow *parent = 0);
13	~Taschenrechner();
14 };

Dies ist die Klassendeklaration unserer Hauptklasse. In Zeile 08 wird der Klassenname "Taschenrechner" angegeben; außerdem zeigt dies an, dass unsere Klasse abgeleitet ist von QMainWindow ": public QMainWindow". Wollen wir beispielsweise einen QDialog erstellen, wäre hier die Ableitung auf QDialog zu ändern. In "ui_Taschenrechner.h" findet die Definition der Taschenrechnermaske "MainWindow" im Namensraum "Ui" statt. "Ui::MainWindow" ist seinerseits nur ein Container bzw. eine Ableitung der eigentlichen Maskendefinition in "Ui_MainWindow".

Zeile 09("Q_OBJECT") ist ein weiteres Makro, welches immer bei Qt in der Klasse, die Signals und Slots behandelt, angegeben werden muss. Diese werden dann durch den Meta-Object Compiler ausgewertet. Vergisst man das Makro, werden Signale und Slots nicht behandelt. Unsere Klasse hat zur Zeit zwei Methoden deklariert. Beide sind "public", können also von einer beliebigen Funktion aufgerufen werden. Bei den Methoden handelt es sich um den Konstruktor und Destruktor. Die Methode, die den gleichen Namen hat wie die Klasse selbst (Zeile 12), ist immer der Konstruktor und die mit dem gleichen Namen, aber einer vorangestellten Tilde (~), ist immer der Destruktor (Zeile 13). Diese Methoden werden immer beim Anlegen oder Zerstören eines Objekts der Klasse aufgerufen. Dem Konstruktor wird hier noch ein Parameter übergeben, der dem Objekt ein Elternwidget zuweist. In unserem Beispiel ist der Standard bei nicht-übergebenem Argument, parent = 0.

Taschenrechner.cpp Bearbeiten

01 #include "Taschenrechner.h"
02
03 Taschenrechner::Taschenrechner(QMainWindow *parent) : QMainWindow(parent){	
04 	setupUi(this);
05 }
06
07 Taschenrechner::~Taschenrechner(){
08 }

Hier werden die deklarierten Funktionen aus der Headerdatei wirklich implementiert. In Zeile 01 wird das Header-File inkludiert. Damit ist bekannt, welche Funktionen es geben wird. In Zeile 03 – 05 wird der Konstruktor implementiert. Das ": QMainWindow(parent)" nach dem Konstruktor sorgt dafür, dass der Konstruktor der Klasse QMainWindow, von der unsere Klasse abgeleitet worden ist, vor dem Aufruf mit dem angegebenen Argument aufgerufen wird. Die einzige Anweisung, die für den Konstruktor implementiert ist, ist in Zeile 04 gegeben. Es handelt sich um eine Qt-spezifische Funktion, die in dem File ui_Taschenrechner.h zu finden ist. Der Aufruf dieser Funktion legt letztendlich das UI an.

In den Zeilen 07 und 08 ist der Destruktor definiert; dieser ist leer. Es werden keine speziellen Funktionen ausgeführt. Um die Speicherfreigabe nach dem Zerstören des Objekts kümmert sich Qt von selbst. Man beachte hier, dass das Fehlen des Returntyps (bspw. "void") spezifisch für Konstruktoren und Destruktoren ist! Dies hat nichts mit Qt zu tun, sondern tritt bei vielen objektorientierten Programmiersprachen auf.

main.cpp Bearbeiten

01 #include "Taschenrechner.h"
02 #include <QApplication>
03
04 int main( int argc, char* argv[]){
05	QApplication a(argc, argv);
06	Taschenrechner w;
07	w.show();
08	return a.exec();
09 }

Die Main-Funktion, der Startpunkt eines C++ Programms, enthält nicht sehr viel. Es wird sich auch im Laufe der Zeit nicht viel an dem Aussehen ändern. Zeile 01 bindet das Headerfile ein, damit die Klasse "Taschenrechner" bekannt ist. Das wird in Zeile 06 wichtig sein. Zeile 04 und 09 stellen das Gerüst der Main-Funktion dar. In Zeile 05 wird ein Qt Objekt "QApplication" (durch Zeile 02 eingebunden) erstellt und die Kommandozeilenparamter werden an dieses Objekt übergeben. Der Objektname ist einfach "a". In Zeile 06 wird eine Instanz der Klasse "Taschenrechner" erstellt, diese Instanz wird "w" genannt, (kommt wohl von Window oder Widget) man könnte aber jeden beliebigen Namen wählen. In Zeile 07 rufen wir die Funktion show() auf. Diese Funktion haben wir nicht explizit implementiert, durch die Ableitung von QMainWindow steht uns aber diese Funktion zur Verfügung. QMainWindow hat diesen "Slot" von QWidget geerbt. Diese Funktion sorgt dafür, dass das Widget angezeigt wird. Sprich: Ließe man sie weg, würde das Programm immer noch "funktionieren"; man würde bloß nichts sehen. Zeile 08 übergibt die Kontrolle des Programms. Die Kontrolle heißt dabei, dass der Aufruf a.exec() Qt anweist, auf Events zu hören. Ohne diese Anweisung wäre also keine Userinteraktion möglich. a.exec() wird erst beendet, wenn das Qt-Programm beendet wird. Das heißt also, dass das "return" in Zeile 08 erst ausgeführt wird, wenn das Qt-Programm beendet wird.