De C++ taal is een krachtige tool om native code te compileren voor specifieke CPU en OS systemen. Daardoor is het ook vaak een logische keuze om complexe interfaces in te ontwerpen en ontwikkelen, van Photoshop tot Google Chrome. Interfaces brengen echter weer andere problemen met zich mee: elk besturingssysteem heeft ondertussen zijn eigen interpretatie.
De Windows standaard was MFC die de WinAPI wrapt, voor OSX was dit de Carbon API. Ondertussen zijn beide frameworks vervangen door WPF (Ook in C#) en Cocoa (enkel in Objective-C). Voor Linux waren er 2 grote Window Managers: KDE en Gnome, die beiden hun eigen UI framework implementeerden: Qt en GTK.
We betreden nu het domein van “frameworks”: libraries die we samen met een programmeertaal gebruiken om sneller tot het gewenst resultaat te komen. Het zou nogal dom zijn om elke keer opnieuw een Button in de UI te moeten “uitvinden” - die zijn gewoon meegeleverd als predefined klasse in elk framework.
Wat is een Framework?
a basic structure underlying a system, concept, or text.
In ons geval een set van libraries waar we mee linken (met g++
) zodat we #include <ui_component.h>
in onze code kunnen gebruiken zonder die zelf te moeten maken. Dat brengt buiten een hoop componenten en includes, een aantal erg belangrijke nadelen met zich mee:
Q_OBJECT
macro in je klasse.Qt en GTK zijn cross-platform UI frameworks: die kan je zowel op UNIX als op Windows compilen. Schrijf je je programma met behulp van Qt, dan zal (met minimale aanpassingen) het zowel voor UNIX als voor Windows kunnen compileren, en dan zal je doelpubliek vergroten. Qt werkt zelfs op iOS en Android - dus waarom ooit Android-specifieke Widgets leren?
Download Qt hier. Je zal merken dat 2gb aan HDD ruimte opgeslokt wordt: dat is erg veel voor een framework! Er zit echter een gigantische hoeveelheid aan bruikbare spullen in:
Met als belangrijkste extra’s Qt Designer en Qt Creator, de grafische UI designer (zoals je SceneGraph kent van JavaFX) en een hele eigen IDE.
Er zijn zéér veel goede Qt tutorials te vinden, waaronder:
De beste manier om te leren hoe Qt werkt is met experimentjes in de Creator.
In de main.cpp file zal je altijd dit vinden:
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
Dit noemen we het bootstrappen van de Qt applicatie waarbij je een specifiek scherm aanmaakt en toont. a.exec()
blokkeert de main thread totdat de UI stopt (bij het afsluiten van alle schermen bijvoorbeeld).
MainWindow
is een subklasse van QMainWindow
:
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_slider_sliderMoved(int position);
private:
Ui::MainWindow *ui;
};
Merk hier framework-specifieke zaken op:
Q_OBJECT
macro moet in elk Qt object aanwezig zijn dat signals en slots gebruikt.private slots
accessor bestaat natuurlijk niet in C++. Qt voorziet een aparte plek om slots te definiëren.Qt genereert deze code voor jou - de destructor verwijdert de onderliggende ui pointer met delete
.
In de source file include je beide headers:
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
De setupUi
methode voegt de componenten toe aan het scherm die jij met de Qt Designer op het scherm gesleept hebt. Dankzij deze (ietwat vreemde) manier kan je overal in MainWindow
met de ui->
pointer reference aan componten geraken. Merk op dat je dus géén definities van componenten in je eigen header terugziet, die leven allemaal in ui_mainwindow.h
.
Een actie aan een knop hangen kan op twee manieren: in de Designer of manueel. In beide gevallen gebruik je Qt’s slots en signals. Een “Signal” is een bericht dat uitgestuurd wordt, waarop eender wie kan luisteren. Een “slot” definiëert de ruimte die gebruikt wordt om dat signaal op te vangen. Het concept is hetzelfde als het observer pattern.
De volgende code verbindt een progress bar valueChanged
slot met een C++11 lambda callback als signal:
connect(ui->progress, &QProgressBar::valueChanged, [](const int &newVal) {
std::cout << newVal << std::endl;
});
Dit kan je zelf in de constructor van je window toevoegen. connect
geeft een QMetaObject::Connection
object terug, zodat je dit kan disconnecten wanneer je zelf wilt.
Een eenvoudigere manier om zaken met elkaar te verbinden is via de UI designer rechtermsuiknop op een element -> “Go to slot…” en een voorstel selecteren. Op dat moment wordt de juiste slot code gegenereerd.
Vergeet Q_OBJECT
niet als je gebruik maakt van slots en signals.
Elk UI object leidt af van QObject
, net zoals in JavaFX. De Qt Inheritance Hierarchy is in de documentatie beschikbaar, waarvan voor ons de belangrijkste klassen bijvoorbeeld QTextEdit
en QLabel
zijn:
Voor een eigen widget implementatie lijkt ons overerven van QWidget
dus een goede keuze.
Waar dient die vreemde macro nu eigenlijk voor? Hoe werkt private slots:
? Qt gebruikt een Meta-Object Compiler (MOC). Dit programma scant header files en genereert C++ source files met metadata die nodig is om onder andere:
signal:
)Q_PROPERTY
)Q_CLASSINFO
)Die gegenereerde source file moet ook meegecompileerd worden. Qt projecten worden meestal met QMake
gecompileerd die de MOC automatisch afhandelt. Lees ook Why Does Qt Use Moc for Signals and Slots?
Qt komt met een eigen Makefile
generator in de vorm van QMake
in plaats van CMake
dat centraal staat in CLion. Het is mogelijk om CLion Qt projecten te laten builden gegeven enkele wijzigingen in de CMakeLists.txt
file om Qt libraries te linken. Het probleem is echter de gegenereerde source files door MOC. Dit zou ons echter te ver leiden.
QMessageBox
indien een ongeldige waarde ingegeven.connect()
in de constructor van een QMainWindow
klasse kan uitvoeren? Waar is deze methode gedefiniëerd?