Neues Projekt erstellen
In einem Projektverzeichnis die Datei project.pro mit folgendem Inhalt anlegen
und mit qmake project.pro
das Makefile erzeugen.
TEMPLATE = app
TARGET = BINARY_NAME
VERSION = 1.0.0
CONFIG += release
QT += printsupport widgets
SOURCES += main.cpp
HEADERS += app.h
Die Variablen für qmake bedeuten:
TEMPLATE
: Typ des Projekts;app
oderlib
oder anderesTARGET
: Name des ProgrammsQT
: List von Qt-Modulen, die eingebunden werden sollenSOURCES
,HEADERS
: Dateien mit dem Code
Man kann Variablen mit =
zuweisen und überschreiben. Listen kann man mit +=
erweitern oder mit -=
Elemente entfernen; qmake
Sprachspezifikation.
Genauere Informationen zu dem, was qmake treibt, bekommt man mit qmake -d
.
Variablen kann man auch auf der Kommandozeile angeben und damit die Einträge in
der Projektdatei überschreiben: qmake CONFIG+=debug
.
In einem bestehenden Projektverzeichnis kann man auch von qmake eine
Projektdatei mit qmake -project
erstellen lassen. Diese heißt dann wie das
Verzeichnis.
Reine Konsolenanwendung
Mit CONFIG += cmdline
kann man sich ein Projekt ohne grafische Oberfläche
bauen. Qt biete viel mehr als die Klassen für die grafischen Elemente, sodass
man damit auch reine Programme für die Kommandozeile erstellen kann.
Unter Windows kann man mit CONFIG += console
zusätzlich zur grafischen
Oberfläche das Konsolen-Fenster aktivieren, um zum Beispiel mit qDebug()
Meldungen auszugeben. Normale Anwender verwirrt dieses Fenster jedoch, weshalb
es hilfreicher ist, mit folgendem Code die Konsolenausgabe nur dann zu
aktivieren, wenn das Programm aus einer Konsole heraus aufgerufen wurde.
int main(int argc, char **argv) {
#ifdef Q_OS_WIN
// https://stackoverflow.com/questions/3360548/console-output-in-a-qt-gui-app#answer-41701133
if (AttachConsole(ATTACH_PARENT_PROCESS)) {
freopen("CONOUT$", "w", stdout);
freopen("CONOUT$", "w", stderr);
}
#endif
Qmake ohne Qt
Qmake kann auch für Anwendungen genutzt werden, die nicht auf Qt aufbauen. Mit
der Einstellung CONFIG -= qt
werden die Abhängigkeiten zu Qt entfernt und man
kann auch allgemeine C++-Projekte mit qmake verwalten. Allerdings habe ich auch
schon gelesen, dass qmake ab Qt 7 durch CMake
ersetzt
werden soll.
Zusätzliche Targets für run und gdb
Von cargo bin ich es mittlerweile gewohnt, dass ich mit einem Befehl alle Operation ausführen kann. Da beim Aufruf von make auch automatisch das Makefile aktualisiert wird, wenn sich die Projektdatei verändert hat, kann make (oder den alias m 😉) wie cargo (resp. c) nutzen.
Hierfür kann man sich auch noch eigene Targets nachrüsten, indem man in der
Projektdatei folgende Anweisungen einbaut. Ich habe mir gdb, run und dazu
den Alias r eingebaut, um in gewohnter Weise das Programm mit make r
zu
starten oder mit make gdb
zu debuggen – Muscle
memory und so.
QMAKE_EXTRA_TARGETS += run r
run.commands = ./$(TARGET)
run.depends = $(TARGET)
r.depends = run
CONFIG(debug) {
QMAKE_EXTRA_TARGETS += gdb
gdb.commands = gdb ./$(TARGET)
gdb.depends = $(TARGET)
}
Pkgconfig nutzen
Unter Unix gibt es für viele Bibliotheken eine Paketbeschreibung, in der
angegeben ist, welche Parameter für den Compiler und den Linker angegeben werden
müssen. Für qmake gibt es auch dafür eine Integration, die man mit CONFIG +=
link_pkgconfig
aktivieren kann. Daraufhin kann man dann mit PKGCONFIG += …
die entsprechenden Informationen einbinden.
Hier eine etwas komplexere Anweisung, die nur PKGCONFIG im Falle eines Unix-Systems einbindet und aufgrund zu vieler Warnung in Poppler die Warnungen deaktiviert.
unix {
CONFIG += link_pkgconfig
CONFIG += warn_off
PKGCONFIG += poppler-qt5
}
GUIs mit Qt Designer erstellen
Mit Qt Designer kann man sehr
schön GUIs nach dem WYSIWYG-Prinzip erstellen. Für diese Dateien kann qmake die
entsprechenden Anweisungen generieren, um uic aufzurufen. Hierfür muss man die
Dateien mit der Anweisung FORMS += main.ui
in der Projektdatei angeben und
erhält eine ui_main.h, die man in den gewünschten Code-Dateien mit #include
"ui_main.h"
einbinden kann.
Der generierte Code enthält ein Klasse, die die notwendigen Objekte als Attribute und die Methode setupUi() hat. Dieser Methode übergibt man beim Aufruf das Elternobjekt – z. B. ein QMainWindow – und bekommt die GUI initialisiert.
QMainWindow win;
Ui::MainWindow ui;
ui.setupUi(&win);
Der Designer ist sehr umfangreich und man kann die Oberflächen sehr gut damit vorkonfigurieren. Gewisse Einstellungen kann man dann allerdings auch erst im Code machen. Da die Designerdatei auch eine XML-Datei ist, kann man Änderungen wie einen Klassenwechsel leichter im XML vornehmen als im Designer.
Die ui-Dateien lassen sich auch dynamisch zur Laufzeit laden, falls man gewisse Teile der Oberfläche nach dem Kompilieren noch austauschen will.
Ressourcen
Unter Ressourcen versteht man bei Qt
Daten wie Bilder, Stylesheets, Textdateien u. s. w., die im Programm abgelegt
werden können. Für diese Ressourcen muss man in einer XML-Datei alle Dateien
angeben, die eingebettet werden soll. Diese Datei muss man in der Projektdatei
mit RESOURCES += resources.qrc
angeben, woraufhin qmake diese dann mit
übersetzt und entsprechend im Programm einbindet.
Auf die Ressourcen kann man innerhalb des Programms mit QFile zugreifen, indem
man dem Dateipfad einen Doppelpunkt voranstellt; QFile
styleFile(":/main.qss")
. Dies erleichtert es, das Programm zu verteilen, da
alle notwendigen Dateien (z. B. auch Übersetzungstexte) direkt im Binary
stecken.
Dynamisches Styling
Qt ermöglicht auch sehr schön die Trennung zwischen Logik, Form und Aussehen. In C++ kann man die Logik implementieren, Guis lassen sich mit Qt Designer entwerfen und das Aussehen der Gui kann man über Qss – einer Auszeichungssprache ähnlich zu CSS — gestalten. Somit könnten unterschiedliche Spezialisten für die jeweiligen Bereiche (fast) unabhängig voneinander arbeiten.
Das globale Stylesheet muss man anfangs für die komplette Anwendung laden.
QApplication app(argc, argv);
QFile styleFile(":/main.qss");
if (styleFile.exists()) {
styleFile.open(QFile::ReadOnly);
app.setStyleSheet(styleFile.readAll());
}
Für die Gui-Elemente kann man über Property Eigenschaften festlegen, die wiederum im Qss genutzt werden können, um das Aussehen anzupassen. So lässt sich das Aussehen dynamisch zur Laufzeit nach bestimmten Bedingungen anpassen. Ein Eingabefeld mit ungültigen oder unvollständigen Daten könnte so zum Beispiel eingefärbt werden.
Code für C++:
addr->setObjectName("address");
if (isValid) {
addr->setProperty("invalid", QVariant());
} else {
addr->setProperty("invalid", true);
}
addr->style()->polish(addr);
Styling mit Qss:
#address[invalid=true] {
background-color: rgb(255, 85, 127);
}
/* nur testen, ob invalid gesetzt ist */
#address[invalid] {
background-color: rgb(255, 85, 127);
}
Qss versteht auch die Klassensyntax von Css, sodass man mit
setProperty("class", "invalid");
den Qss-Code wie folgt schreiben kann:
#address.invalid {
background-color: rgb(255, 85, 127);
}
Unter Windows
Am besten bin ich mit dem Paketmanager msys2 zum Ziel gekommen. Dieses ist eine Cygwin-Umgebung, in der man die Programme wie unter Linux kompilieren kann; es gibt sogar pkg-config. Für Qt muss man das Paket mingw-w64-x86_64-qt5 installieren. Im Anschluss kann man qmake, make und gdb nutzen.
Weil auf dem Zielsystem nicht die Qt-Bibliotheken installiert seien werden und
msys2 überall zu aufwändig ist, kann man mit dem Programm windeployqt alle
notwendigen Dateien in ein Verzeichnis kopieren; Qt-Deployment for
Windows. In der project.pro
habe ich dafür folgende Einträge, um alle Dateien mit make deploy DESTDIR=bin
zusammenzupacken:
QMAKE_EXTRA_TARGETS += deploy
deploy.depends = $(TARGET)
deploy.commands = rm -fr $(DESTDIR)
deploy.commands += && mkdir $(DESTDIR)
deploy.commands += && cp -v $(TARGET) $(DESTDIR)
equals(QT_MAJOR_VERSION, 5) {
deploy.commands += && windeployqt $(DESTDIR)/$(TARGET)
} else {
deploy.commands += && windeployqt-qt6 $(DESTDIR)/$(TARGET)
}
deploy.commands += && cp -v \$\$\(ldd $(DESTDIR)/$(TARGET) |grep -v /c/ |cut -f2 -d\\> |cut -f1 -d\\(\) $(DESTDIR)