Einstieg in die Programmierung mit Qt
[toc align_right="yes" depth="3"]
Einstieg in die Programmierung mit Qt
Willkommen in der Welt von Qt — dem Cross-Plattform-Werkzeug für graphische Benutzeroberflächen (Graphical User Interface = GUI). In dieser Einführung vermitteln wir grundlegende Kenntnisse zu Qt und entwicklen dazu eine einfache Notizzettel-Anwendung. Nach dem Studium dieser Anleitung sollten Sie in der Lage sein, sich eingehend mit unseren Übersichten und der Dokumentation unserer Programmierschnittstellen (APIs) zu beschäftigen, um die benötigten Informationen für die Entwicklung Ihrer Anwendung zu finden.
Den englischen Originaltext zu dieser Übersetzung finden Sie unter der URL "http://doc.qt.nokia.com/gettingstartedqt.html":http://doc.qt.nokia.com/gettingstartedqt.html.
Hallo Notizzettel
In diesem ersten Beispiel werden wir einen einfachen Notizzettel (Notepad) erzeugen und als Fenster auf dem Schreibtisch anzeigen. Das stellt das einfachste Qt-Programm mit grafischer Oberfläche dar.
Der Quelltext dazu:
<br />#include <QApplication&gt;<br />#include <QTextEdit&gt;
int main(int argv, char **args)<br />{<br /> QApplication app(argv, args);
QTextEdit textEdit;<br /> textEdit.show();
return app.exec&amp;#40;&#41;;<br />}<br />
Wir werden den Code Zeile für Zeile analysieren. In den ersten zwei Zeilen binden wir die Header-Dateien für "QApplication":http://doc.qt.nokia.com/qapplication.html und "QTextEdit":http://doc.qt.nokia.com/qtextedit.html ein, die zwei für dieses Beispiel benötigten Klassen. Für alle Qt-Klassen gibt es Header-Dateien, die genauso benannt sind wie die Klasse selbst.
Zeile 6 erzeugt ein "QApplication":http://doc.qt.nokia.com/qapplication.html-Objekt. Es verwaltet die Ressourcen für die gesamte Anwendung und wird immer für eine Qt-Anwendung mit GUI benötigt. Das Objekt benötigt argv und args, weil Qt-Anwendungen einige Kommandozeilen-Optionen auswerten können.
Zeile 8 erzeugt ein "QTextEdit":http://doc.qt.nokia.com/qtextedit.html-Objekt. Ein Texteditor ist ein sichtbares Element der GUI. In Qt werden diese Elemente "Widgets" genannt (ein Kunstwort aus Window (Fenster) und Gadget (Vorrichtung)). Beispiele für weitere Widgets sind "scroll bars" (Rollbalken), "Labels" (Beschriftungen/Markierungen) und "radio buttons" (eine Auswahl von Optionen, von denen nur eine ausgewählt werden kann). Ein Widget kann als Container für andere Widgets benutzt werden, beispielsweise ein Dialog oder das Hauptfenster einer Anwendung.
Zeile 9 zeigt den Texteditor als eigenständiges Fenster auf dem Bildschirm an. Da Widgets auch als Container fungieren können (z.B. ein "QMainWindow":http://doc.qt.nokia.com/qmainwindow.html, das Werkzeugleisten, Menüs, eine Statuszeile und einige andere Widgets enthält), kann ein einzelnes Widget auch als einzelnes Fenster auf dem Bildschirm dargestellt werden. Ein Widget ist von Haus aus nicht sichtbar, erst die Funktion "show()":http://doc.qt.nokia.com/qwidget.html#show macht es sichtbar.
In Zeile 11 startet "QApplication":http://doc.qt.nokia.com/qapplication.html seine Ereignis-Schleife ("event loop"). Wenn eine Qt-Anwendung läuft, werden verschiedene Ereignisse ("events") erzeugt und an die Widgets der Anwendung weitergereicht. Dabei handelt es sich z.B. um das Drücken einer Maustaste oder einen Tastaturanschlag. Wenn Sie in einem Texteditor Text eingeben, erhält er verschiedene "Taste gedrückt"-Ereignisse und reagiert darauf, indem er den eingegebenen Text auf dem Bildschirm darstellt.
Um die Anwendung auszuführen, öffnen Sie eine Kommanndozeilen-Umgebung (shell) und wechseln Sie in das Verzeichnis, in dem sich die .cpp-Datei ihres Programms befindet. Folgende Befehle erzeugen das Programm:
<br />qmake -project<br />qmake<br />make<br />
Damit wird eine ausführbare Datei im Verzeichnis teil1 erzeugt (bitte beachten Sie: unter Windows müssen Sie ggf. nmake anstelle von make verwenden. Außerdem wird die ausführbare Datei in teil1\debug oder teil1\release erzeugt). qmake ist das Erstellungs-Werkzeug von Qt und benötigt eine Konfigurationsdatei. qmake erzeugt diese Datei für uns, wenn wir es mit dem Argument project aufrufen. Zusammen mit der Konfigurationsdatei (mit der Endung .pro) generiert qmake ein Makefile, mit dem das Programm erstellt werden kann. Wir sehen uns später an, wie man .pro-Dateien selbst schreibt.
h3. Weitere Informationen
|. Über… |. Hier… |
|Widgets und Fenster-Geometrie | "Window and Dialog Widget":http://doc.qt.nokia.com/application-windows.html |
|Ereignisse und ihre Behandlung | "The Event System":http://doc.qt.nokia.com/eventsandfilters.html |
h2. Einen Knopf zum Beenden hinzufügen
Eine richtige Anwendung benötigt üblicherweise mehr als ein Widget. Wir werden jetzt einen "QPushButton":http://doc.qt.nokia.com/qpushbutton.html unterhalb des Notizzettels einfügen. Die Anwendung wird beendet, wenn der Knopf gedrückt wird, d.h. mit der Maus auf ihn gecklickt wird.
http://doc.qt.nokia.com/4.7/images/gs2.png
Sehen wir uns den Code dazu an:
<br />#include <QtGui&gt;
<br />int main(int argv, char **args)<br />{<br /> QApplication app(argv, args);
<br /> QTextEdit textEdit;<br /> QPushButton quitButton("Quit&quot;);
<br /> QObject::connect(&quitButton, SIGNAL (clicked()), qApp, SLOT (quit()));
<br /> QVBoxLayout layout;<br /> layout.addWidget(&textEdit);<br /> layout.addWidget(&quitButton);
<br /> QWidget window;<br /> window.setLayout(&layout);
<br /> window.show();
<br /> return app.exec&amp;#40;&#41;;<br />}<br />
Zeile 1 bindet "QtGui":http://doc.qt.nokia.com/qtgui.html ein. Diese Header-Datei enthält alle GUI-Klassen von Qt.
In Zeile 10 verwenden wir den Signal-Slot-Mechanismus von Qt, um die Anwendung zu beenden sobald der Beenden-Knopf gedrückt wurde. Ein "Slot" ist eine spezielle Funktion, die zur Laufzeit über ihren Namen (als reine Zeichenkette) aufgerufen werden kann. Ein "Signal" ist ebenfalls eine spezielle Funktion. Wird sie aufgerufen, werden wiederum die bei ihr registrierten Slots ausgeführt. Wir nennen das "den Slot mit dem Signal verbinden" ("connect") bzw. das Signal aussenden ("emit").
"quit()":http://doc.qt.nokia.com/qcoreapplication.html#quit ist ein Slot in "QApplication":http://doc.qt.nokia.com/qapplication.html und beendet die Anwendung. "clicked()":http://doc.qt.nokia.com/qabstractbutton.html#clicked ist ein Signal, das von "QPushButton":http://doc.qt.nokia.com/qpushbutton.html ausgesendet wird, wenn er gedrückt wird. Die statische Funktion "QObject::connect()":http://doc.qt.nokia.com/qobject.html#connect dient dazu, den Slot mit dem Signal zu verbinden. SIGNAL () und SLOT() sind zwei Makros, die die Funktions-Signaturen für die zu verbindenden Signale und Slots erhalten. Außerdem müssen Zeiger auf die Objekte angegeben werden, die das Signal aussenden und empfangen.
Zeile 12 erzeugt ein "QVBoxLayout":http://doc.qt.nokia.com/qvboxlayout.html. Wie bereits erwähnt, können Widgets andere Widgets enthalten. Es ist möglich, die Geometrie (Größe und Position) eines Unter-Widgets (Kind-Widget) direkt zu setzen. Üblicherweise ist es aber einfacher, eine Layout-Klasse zu verwenden, die die Geometrie der Kinder eines Widgets verwaltet. Ein "QVBoxLayout":http://doc.qt.nokia.com/qvboxlayout.html beispielsweise ordnet die Kinder in einer Zeile nebeneinander an.
Zeile 13 und 14 fügen den Editor und den Knopf in das Layout ein. In Zeile 17 weisen wir das Layout einem Widget zu.
h3. Weitere Informationen
|. Über… |. Hier… |
|Signale und Slots | "Signals & Slots":http://doc.qt.nokia.com/signalsandslots.html%7C
|Layouts | "Layout Management":http://doc.qt.nokia.com/layout.html, "Widgets and Layouts":http://doc.qt.nokia.com/widgets-and-layouts.html, "Layout Examples":http://doc.qt.nokia.com/examples-layouts.html |
|Die von Qt mitgelieferten Widgets | "Qt Widget Gallery":http://doc.qt.nokia.com/gallery.html, "Widget Examples":http://doc.qt.nokia.com/examples-widgets.html |
h2. Von QWidget abgeleitete Klassen
Wenn der Benutzer die Anwendung beenden möchte, wollen Sie ihn möglicherweise vorher fragen, ob er/sie das wirklich will. In diesem Beispiel erstellen wir eine von "QWidget":http://doc.qt.nokia.com/qwidget.html abgeleitete Klasse und fügen einen Slot hinzu, der mit dem Beenden-Knopf verbunden wird.
http://doc.qt.nokia.com/4.7/images/gs3.png
Sehen wir uns den Quelltext an:
<br />class Notepad : public QWidget<br />{<br /> Q_OBJECT
<br />public:<br /> Notepad();
<br />private slots:<br /> void quit();
<br />private:<br /> QTextEdit *textEdit;<br /> QPushButton *quitButton;<br />};<br />
sie muss natürlich auch von "QObject":http://doc.qt.nokia.com/qobject.html (oder einer davon abgeleiteten Klasse) erben. "QObject":http://doc.qt.nokia.com/qobject.html fügt einer normalen C++-Klasse noch einige weitere Fähigkeiten hinzu. Insbesondere können die Namen der Klasse und der Slots zur Laufzeit abgefragt werden. Außerdem ist es möglich, die Parameter eines Slots abzufragen und die dazugehörige Funktion aufzurufen.
Das Makro "Q_OBJECT":http://doc.qt.nokia.com/qobject.html#Q_OBJECT muss als erstes in der Definition der Klasse auftauchen. Es markiert unsere Klasse als "QObject":http://doc.qt.nokia.com/qobject.html
In Zeile 13 wird der Slot quit() deklariert. Das geht mit dem Makro slots ganz einfach. Der Slot quit() kann nun mit einem Signal mit passender Signatur (jedes Signal, das keinen Parameter hat) verbunden werden.
Die Benutzeroberfläche erzeugen wir nicht mehr in der Funktion main(), sondern im Konstruktor von Notepad. Dort verbinden wir auch den Slot.
<br />Notepad::Notepad()<br />{<br /> textEdit = new QTextEdit;<br /> quitButton = new QPushButton(tr("Quit&quot;));
connect(quitButton, SIGNAL (clicked()), this, SLOT (quit()));
QVBoxLayout '''layout = new QVBoxLayout;<br /> layout->addWidget(textEdit);<br /> layout->addWidget(quitButton);
<br /> setLayout(layout);
<br /> setWindowTitle(tr("Notepad&quot;));<br />}<br />
Wie sie in der Definition der Klasse gesehen haben, benutzen wir Zeiger auf unsere "QObject":http://doc.qt.nokia.com/qobject.html-Objekte (textEdit und quitButton). Grundsätzlich gilt, dass Sie Instanzen von "QObject":http://doc.qt.nokia.com/qobject.html (und davon abgeleiteten Klassen) auf dem Heap erzeugen und sie niemals kopieren sollten.
Von jetzt an kapseln wir die für den Benutzer sichtbaren Zeichenketten in der Funktion "tr()":http://doc.qt.nokia.com/qobject.html#tr. Das ist notwendig, wenn Sie die Anwendung in mehreren Sprachen (z.B. Deutsch und Englisch) zur Verfügung stellen möchten. Auf Einzelheiten gehen wir an dieser Stelle nicht ein. Mehr erfahren Sie über den Link zu Qt Linguist in der Tabelle mit weiteren Informationen.
h3. Weitere Informationen
|. Über… |. Hier… |
|tr() und "Internationalisierung" |"Qt Linguist Manual":http://doc.qt.nokia.com/linguist-manual.html, "Writing Source Code for Translation":http://doc.qt.nokia.com/i18n-source-translation.html, "Hello tr() Example":http://doc.qt.nokia.com/linguist-hellotr.html, "Internationalization with Qt":http://doc.qt.nokia.com/internationalization.html |
|"QObjects":http://doc.qt.nokia.com/qtwebkit-bridge.html#qobjects und das Objekt-Model von Qt |"Object Model":http://doc.qt.nokia.com/object.html |
|qmake und das Erstellungs-System von Qt|"qmake Manual":http://doc.qt.nokia.com/qmake-manual.html |
h3. Eine .pro-Datei erzeugen
Für dieses Beispiel werden wir eine eigene .pro-Datei erstellen, und nicht die Option project von qmake.
<br />HEADERS = notepad.h<br />SOURCES = notepad.cpp main.cpp<br />
Folgende Befehle erzeugen das Programm
<br />qmake<br />make<br />
h2. Ein QMainWindow verwenden
Viele Anwendungen werden vom Einsatz eines "QMainWindow":http://doc.qt.nokia.com/qmainwindow.html (Hauptfenster) profitieren. Es hat sein eigenes Layout, dem Sie eine Menüzeile, Dock Widgets (es gibt keine vernünftige deutsche Übersetzung hierfür), Werkzeugleisten und eine Status-Zeile hinzufügen können. "QMainWindow":http://doc.qt.nokia.com/qmainwindow.html hat einen zentralen Bereich, der von jeder Art von Widget ausgefüllt werden kann. In unserem Beispiel werden wir hier den Texteditor platzieren.
http://doc.qt.nokia.com/4.7/images/gs4.png
Sehen wir uns die neue Definition der Klasse Notepad an.
<br />#include <QtGui&gt;
<br />class Notepad : public QMainWindow<br />{<br /> Q_OBJECT
<br />public:<br /> Notepad();
<br />private slots:<br /> void open();<br /> void save();<br /> void quit();
<br />private:<br /> QTextEdit *textEdit;
<br /> QAction *openAction;<br /> QAction *saveAction;<br /> QAction *exitAction;
<br /> QMenu '''fileMenu;<br />};<br />
Wir fügen zwei weitere Slots hinzu, mit denen ein Dokument gelesen und gespeichert werden kann. Wir werden sie im folgenden Abschnitt implementieren.
In einem Hauptfenster muss der selbe Slot oftmals von mehreren verschiedenen Widgets aus aufgerufen werden. Dazu gehören beispielsweise Menüpunkte und Knöpfe in einer Werkzeugleiste. Um diese Aufgabe zu erleichtern, gibt es in Qt die Klasse "QAction":http://doc.qt.nokia.com/qaction.html, die mit mehreren Widgets verknüpft und mit einem Slot verbunden werden kann. So erzeugen beispielsweise sowohl "QMenu":http://doc.qt.nokia.com/qmenu.html als auch "QToolBar":http://doc.qt.nokia.com/qtoolbar.html Menüpunkte und Werkzeug-Knöpfe aus den selben "QActions":http://doc.qt.nokia.com/qaction.html. Wie das funktioniert werden wir in Kürze sehen.
Wie vorhin bauen wir die Benutzeroberfläche im Konstruktor von Notepad zusammen.
<br />Notepad::Notepad()<br />{<br /> saveAction = new QAction(tr("&Open&quot;), this);<br /> saveAction = new QAction(tr("&Save&quot;), this);<br /> exitAction = new QAction(tr("E&amp;xit&quot;), this);
<br /> connect(openAction, SIGNAL (triggered()), this, SLOT (open()));<br /> connect(saveAction, SIGNAL (triggered()), this, SLOT (save()));<br /> connect(exitAction, SIGNAL (triggered()), qApp, SLOT (quit()));
<br /> fileMenu = menuBar()<s>>addMenu(tr("&File&quot;));<br /> fileMenu</s>>addAction(openAction);<br /> fileMenu->addAction(saveAction);<br /> fileMenu->addSeparator();<br /> fileMenu->addAction(exitAction);
<br /> textEdit = new QTextEdit;<br /> setCentralWidget(textEdit);
<br /> setWindowTitle(tr("Notepad&quot;));<br />}<br />
"QActions":http://doc.qt.nokia.com/qaction.html werden mit einem Text erzeugt, der auf/mit den Widgets dargestellt werden soll, denen wir sie zuordnen (in unserem Fall Menüpunkte). Wenn wir sie auch in einer Werkzeugleiste verwenden wollen, könnten wir ihnen noch "Icons":http://doc.qt.nokia.com/qicon.html (kleine Bilder) zuweisen.
Wenn jetzt ein Menüpunkt aufgerufen wird, so wird die dazugehörige Aktion getriggert und der mit ihr verbundene Slot wird aufgerufen.
h3. Weitere Informationen
|. Über… |. Hier… |
| Hauptfenster und die zugehörigen Klassen | "Application Main Window":http://doc.qt.nokia.com/mainwindow.html, "Main Window Examples":http://doc.qt.nokia.com/examples-mainwindow.html |
| MDI-Anwendungen | "QMdiArea":http://doc.qt.nokia.com/qmdiarea.html, "MDI Example":http://doc.qt.nokia.com/mainwindows-mdi.html |
h2. Speichern und einlesen
In diesem Beispiel implementieren wir die beiden Slots open() und save(), die wir zuvor ergänzt haben.
http://doc.qt.nokia.com/4.7/images/gs5.png
Wir kümmern uns zuerst um den Slot open() zum öffnen eines Dokuments:
<br />void Notepad::open()<br />{<br /> QString fileName = QFileDialog::getOpenFileName(this, tr("Open File&quot;), "",<br /> tr("Text Files ('''.txt);;C++ Files ('''.cpp'''.h)"));
<br /> if (fileName != "") {<br /> QFile file&amp;#40;fileName&amp;#41;;<br /> if (!file.open(QIODevice::ReadOnly)) {<br /> QMessageBox::critical(this, tr("Error&quot;),<br /> tr("Could not open file&quot;));<br /> return;<br /> }<br /> QString contents = file.readAll().constData();<br /> textEdit</s>>setPlainText(contents);<br /> file.close();<br /> }<br />}<br />
Als erstes müssen wir den Benutzer nach der zu öffnenden Datei fragen. Qt stellt einen "QFileDialog":http://doc.qt.nokia.com/qfiledialog.html zur Verfügung, mit dem ein Benutzer eine Datei auswählen kann. Die Abbildung oben zeigt den Dialog wie er unter Kubuntu aussieht. Die statische Funktion "getOpenFileName()":http://doc.qt.nokia.com/qfiledialog.html#getOpenFileName zeigt einen modalen Dialog zur Dateiauswahl und kehrt erst zurück, wenn der Benutzer eine Datei ausgewählt oder die Aktion abgebrochen hat. Sie liefert den Pfad zur ausgwählten Datei zurück, oder eine leere Zeichenkette, falls der Benutzer die Auswahl abgebrochen hat.
Haben wir einen Dateinamen erhalten, versuchen wir die Datei mit "open()":http://doc.qt.nokia.com/qiodevice.html#open zu öffnen. Die Funktion liefert true zurück, wenn das erfolgreich war. Die Fehlerbehandlung wollen wir an dieser Stelle nicht weiter vertiefen, mehr erfahren Sie in den Links im Kasten "Weitere Informationen". Falls die Datei nicht geöffnet werden kann, benutzen wir eine "QMessageBox":http://doc.qt.nokia.com/qmessagebox.html um einen Dialog mit Fehlermeldung anzuzeigen (Weitere Details finden Sie in der Klassenbeschreibung von "QMessageBox":http://doc.qt.nokia.com/qmessagebox.html).
Das eigentliche Einlesen der Daten ist einfach, wir benutzen die Funktion "readAll()":http://doc.qt.nokia.com/qiodevice.html#readAll, die den gesamten Inhalt der Datei in einem "QByteArray":http://doc.qt.nokia.com/qbytearray.html zurückliefert. Die Funktion "constData()":http://doc.qt.nokia.com/qbytearray.html#constData liefert die Daten des Arrays als const char zurück, was wiederum zur Initialisierung im Konstruktor eines "QString":http://doc.qt.nokia.com/qstring.html verwendet werden kann. Der Inhalt kann damit im Texteditor dargestellt werden. Wir schließen die Datei mit "close()":http://doc.qt.nokia.com/qiodevice.html#close, um die belegten Ressourcen (z.B. den File-Descriptor) an das Betriebssystem zurückzugeben.
Sehen wir uns nun den Slot save() zum abspeichern des Textes an:
<br />void Notepad::open()<br />{<br /> QString fileName = QFileDialog::getSaveFileName(this, tr("Save File&quot;), "",<br /> tr("Text Files ('''.txt);;C++ Files ('''.cpp '''.h)"));
<br /> if (fileName != "") {<br /> QFile file&amp;#40;fileName&amp;#41;;<br /> if (!file.open(QIODevice::WriteOnly)) {<br /> // error message<br /> } else {<br /> QTextStream stream(&file);<br /> stream << textEdit->toPlainText();<br /> stream.flush();<br /> file.close();<br /> }<br /> }<br />}<br />
Zum Schreiben des Textes in eine Datei benutzen wir die Klasse "QTextStream":http://doc.qt.nokia.com/qtextstream.html, ein Aufsatz (Wrapper) für "QFile":http://doc.qt.nokia.com/qfile.html-Objekte. Der "QTextStream":http://doc.qt.nokia.com/qtextstream.html kann QStrings direkt in eine Datei schreiben; "QFile":http://doc.qt.nokia.com/qfile.html hingegen kann nur einfache Zeichenketten (char) über die Funktion "write()":http://doc.qt.nokia.com/qiodevice.html#write von "QIODevice":http://doc.qt.nokia.com/qiodevice.html schreiben.
Weitere Informationen
Über… | Hier… |
---|---|
Dateien und Ein-Ausgabe-Geräte | "QFile":http://doc.qt.nokia.com/qfile.html, "QIODevice":http://doc.qt.nokia.com/qiodevice.html |