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:

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)