Buttons und andere Elemente richtig anordnen

QtGUI – grafische Benutzeroberflächen mit Qt erstellen
Kommentare

C und C++ boten keine Möglichkeit zur Realisierung grafischer Benutzerschnittstellen. Eine der ersten – und für die Geschichte des Frameworks bedeutendsten – Handlungen der Entwicklerschaft bestand in der Erstellung eines GUI-Stacks.

Aufgrund der plattformunabhängigen Ausrichtung von Qt legte man bei der Entwicklung Wert darauf, dass sich die Steuerelemente automatisch an geänderte Begebenheiten anpassen. Dies führte zur Entwicklung einer hochleistungsfähigen Layout-Engine, die auch komplexeste Layouts automatisch „mitskaliert“ und so konsistentes Aussehen auf den meisten Systemen garantiert.

Das anfangs als QtGUI bezeichnete Modul wurde nach dem Erscheinen des im nächsten Heft besprochenen QML-Systems in Widgets umbenannt. Diese „Erbschaft“ wirkt sich bis heute in den als Projektdeskriptor dienenden .pro-Dateien aus, die das betreffende Modul nach folgendem Schema einbinden:

QT       += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

Widget mit Hirn

Unterschiedliche Auflösungen und Bildschirmgrößen waren spätestens seit Windows 95 Standard. Im Laufe der letzten Jahre hat sich die „Bandbreite“ erhöht: Sowohl im Bereich der dpi als auch bei der rohen physikalischen Auflösung ist der Wertebereich wesentlich angestiegen.

Auf Änderungen der Fenstergröße reagierende Entwickler arbeiteten bisher oft mit Routinen, die die Struktur des Formulars analysierten und die Größe der Steuerelemente danach nach mehr oder weniger rigiden Regeln aktualisierten. Qt kehrt diese Beziehung insofern um, als die Intelligenz hier in den Widgets und ihren Layouts liegt.

Wir wollen uns diese eher abstrakte Situation anhand des in der Skizze gezeigten Texteditors verdeutlichen. Er besteht aus einem Eingabefeld und drei darunterliegenden Buttons. Die Knöpfe sollen nach dem Schema 1-2 angeordnet sein: In der ersten Reihe befindet sich ein Knopf, die darunterliegenden Buttons liegen nebeneinander (Abb. 1).

Abb. 1: (Chaotische) Handskizzen erleichtern das Design von „intelligenten“ Formularen

Abb. 1: (Chaotische) Handskizzen erleichtern das Design von „intelligenten“ Formularen

Neu erstellte Projekte enthalten eine Datei namens mainwindow.ui. Klicken Sie diese an, um Qt Creator in den WYSIWYG-Bearbeitungsmodus zu versetzen. Dieser verhält sich – im Großen und Ganzen – wie der von Visual Studio bekannte GUI-Editor: Die Toolbar auf der linken Seite enthält Widgets, die auf das in der Mitte dargestellte Formular abgelegt werden. Die Eigenschaften des gerade ausgewählten Elements erscheinen im Kasten unten rechts.

Formulare müssen die in ihnen enthaltenen Steuerelemente auf eine bestimmte Art anordnen. Dies erfolgt über das so genannte Formularlayout, das nach dem Markieren des Formularinhalts durch die auf der Oberseite des WYSIWYG-Editors befindliche Toolbar zugewiesen werden kann (Abb. 2). Wir werden unsere Widgets in einem Vertical Layout unterbringen, das seine Kinder wie in einem Stapelspeicher übereinander anordnet.

Abb. 2: Diese Toolbar aktiviert sich nur bei Formularen, die ein oder mehrere Widgets enthalten

Abb. 2: Diese Toolbar aktiviert sich nur bei Formularen, die ein oder mehrere Widgets enthalten

Fehlende Formularlayouts sind ein klassischer Anfängerfehler: Sie führen zu einer kompletten Deaktivierung des Layoutsystems. Wenn ein Formular nicht zufriedenstellend auf Größenänderungen reagiert, so prüfen Sie das Aussehen in der Outline (Abb. 3).

Abb. 3: Ein rotes Nein-Schild weist auf ein fehlendes Formularlayout hin

Abb. 3: Ein rotes Nein-Schild weist auf ein fehlendes Formularlayout hin

Die in Abbildung 3 gezeigte Baumansicht eignet sich zur Selektion von Steuerelementen. Das Anklicken eines Widgets sorgt dafür, dass seine Eigenschaften in der darunter angezeigten Properties-Box erscheinen. Neue Steuerelemente können per Drag and Drop in den Steuerelementbaum gelegt werden – bei komplexen Layouts ist dies ein einfacher Weg zur Durchführung kleinerer Änderungen.

Da wir im Moment noch keine Steuerelemente im Formular haben, ziehen wir ein Horizontal Layout aus der Toolbar in das Formular. Platzieren Sie danach zwei Push-Buttons im roten Rahmen. Die beiden Widgets werden automatisch nebeneinander angeordnet. Vor dem Hinzufügen des Formularlayouts müssen Sie noch einen weiteren Push-Button und ein Steuerelement vom Typ Plain Text Edit einpflegen. Platzieren Sie diese übereinander, um Qt Creator das eigentliche Anordnen der Widgets zu erleichtern.

Der Lohn unserer Mühen ist in den Abbildungen 4 und 5 gezeigt – es ist schon jetzt offensichtlich, dass Qt das Aussehen der Widgets nach einer Änderung der Bildschirmausrichtung automatisch an die neue Situation anpasst.

Abb. 4: Die Textbox ist sowohl im Portrait-...

Abb. 4: Die Textbox ist sowohl im Portrait-…

Abb. 5: ...als auch im Landscape-Modus bildschirmfüllend

Abb. 5: …als auch im Landscape-Modus bildschirmfüllend

Aufgrund der Rigidität einmal eingefügter Layouts sollten Sie bei der Erstellung komplexer Formulare von unten nach oben arbeiten. Fügen Sie im ersten Schritt die Steuerelemente hinzu, gruppieren Sie sie erst danach in „Unterlayouts“. Das eigentliche Formularlayout kommt als letzter Akt hinzu.

Höheres Denken

Die Textbox ist sowohl im Editor als auch am Telefon bildschirmfüllend, während die Buttons mit weniger Platz auskommen. Das Verhalten der Steuerelemente lässt sich an mehreren Stellen an ihre Bedürfnisse anpassen.

Widgets werden im Allgemeinen von der Masterklasse QWidget abgeleitet. Dort werden drei wichtige Eigenschaften eingesetzt: minimumSize und maximumSize versorgen die Rendering Engine mit Informationen über den minimalen und den maximal verwendbaren Platz. Die beiden Werte müssen nicht unbedingt bevölkert werden: Wenn ihr Steuerelement mit jeder beliebigen Größe auskommt, so setzen Sie die Eigenschaften wie in Abbildung 6 gezeigt auf 0.

Abb. 6: Qt bietet flexible Möglichkeiten zur Einstellung des Verhaltens der Widgets

Abb. 6: Qt bietet flexible Möglichkeiten zur Einstellung des Verhaltens der Widgets

Die im deutschen Qt-Creator als „Horizontale Einstellung“ und „Vertikale Einstellung“ bezeichneten Felder erlauben die Auswahl des bei der Größenberechnung verwendeten Verhaltens. Ein auf Fixed gesetztes Widget erscheint immer in der als sizeHint festgelegten Größe.

Das Übergeben von Minimum teilt dem Framework mit, dass der in sizeHint eingegebene Wert ausreicht. Ein derartiges Steuerelement wächst nur dann, wenn andere Widgets mit dem Platz nichts anfangen können: Die seitliche Ausbreitung unserer Buttons ist ein gutes Beispiel dafür. Maximum ist irreführend: Ein damit versehenes Widget betrachtet seinen sizeHint als Maximum und schrumpft bereitwillig bis zur minimumSize herunter. Expanding und MinimumExpanding realisieren Steuerelemente, die so viel Platz wie möglich an sich reißen. Unsere PlainTextEdit-Instanz ist ein gutes Beispiel dafür: Wenn Sie die Größe des Fensters am Desktop erhöhen, so bleibt sie platztechnisch stets „obenauf“.

Platztechnischer Kollisionskurator

QtGUI kennt – im Allgemeinen – keine transparenten Steuerelemente. Daraus folgt, dass jedes Pixel immer nur einem Widget gleichzeitig gehören kann. In unserem Beispiel führt dieser Zusammenhang zu Kollisionen: Die beiden nebeneinanderliegenden Knöpfe sind „Futterkonkurrenten“, da sich jeder von ihnen gleichermaßen in X-Richtung ausbreiten möchte.

Von Haus aus teilt die Layout-Engine den Platz zwischen allen wachstumsinteressierten Steuerelementen fair auf. In unserem Fall führt dies dazu, dass die beiden Buttons gleich groß sind.

Die zur Beeinflussung der Aufteilung vorgesehenen Eigenschaften hören in deutschen Versionen des Qt Creator auf den Namen „Dehnungsfaktor“. Es handelt sich dabei um eine Art Gewichtung, die die Bedeutung des Steuerelements gegenüber seinen Nachbarn beschreibt. Als Beispiel für die Funktion sei auf Abbildung 7 verwiesen, die einen Button mit Faktor 2 und einen mit Faktor 4 beschreibt.

Abb. 7: Höhere Gewichte sorgen für größere Steuerelemente

Abb. 7: Höhere Gewichte sorgen für größere Steuerelemente

Spacer stellen zur Laufzeit unsichtbare Widgets dar, die nur der Platzierung anderer Steuerelemente dienen. Ihre Funktionsweise lässt sich am einfachsten mit einer Feder beschreiben, die sich so weit wie möglich ausbreitet. Platzieren Sie einen Horizontal Spacer in einem Layout neben einem Widget, um dieses an den Rand des Containers zu pressen.

Grid- und Formularlayouts helfen bei der Realisierung von Formularen mit geometrisch angeordneten Widgets. Das Form-Layout erstellt die aus dem Internet bekannte Kombination aus Label und Eingabefeld, während ein Grid mehr oder weniger beliebige tabellarische Anordnungen aus zusammenhängenden Kästchen aufbaut.

Quereinsteiger fühlen sich anfangs vom Layoutsystem überfordert: Die soeben vorgestellten Optionen sind im Vergleich zu den in anderen Frameworks gebotenen Einstellmöglichkeiten zahlreich. Erfreulicherweise bietet Digia im  Netz hilfreiche Kurzfassungen an.

Qt Creator unterstützt Sie bei der Arbeit insofern, als der Editor eine Livevorschau des Formulars anzeigt. Diese ist im Großen und Ganzen genau; das Verhalten des Layoutsystems lässt sich durch Verändern der Formulargröße testen.

Eventorientierte Kommunikation

Ted Faison veröffentlichte 2006 bei Apress einen – leider nur wenig beachteten – Klassiker zum Thema eventorientierte Programmierung. Wer seine Programme mit Ereignissen kommunizieren lässt, kann Komponenten wegen der reduzierten Koppelung einfacher weiterverwenden.

Qt implementierte dieses – an sich grundlegende – Designpattern von Anfang an, bezeichnete es aber als Signal-Slot-System. Jede von QObject abgeleitete Komponente ist mit einem Event Bus verbunden, der vom Framework bereitgestellt wird. Die von Objekten emittierten Signale werden in so genannten Slots entgegengenommen. Verbindungen zwischen Signal und Slot werden durch Aufruf von QObject::connect() oder durch den Ressourceneditor aufgebaut.

Wir wollen die zwei Buttons mit Funktionen verdrahten, die nach einem Anklicken ausgeführt werden. Klicken Sie dazu auf den in der Toolbar befindlichen Button, der einen auf einen Kasten zeigenden Pfeil zeigt. Der Editor wechselt daraufhin in den Signal-Slot-Modus, in dem Steuerelemente per Drag and Drop verbunden werden.

Klicken Sie einen der Buttons an und ziehen sie den Mauszeiger bei gedrückter linker Maustaste aus dem Rahmen des Steuerelements. Qt Creator zeigt daraufhin ein Massesymbol an, das Sie am Hintergrund des Formulars ablegen. Die IDE blendet dann den Verbindungseditor ein: Es handelt sich dabei um einen Dialog mit zwei Spalten, die die im Sender implementierten Signale und die im Empfänger vorhandenen Slots auflisten (Abb. 8).

Abb. 8: Signal-Slot-Beziehungen entstehen im Verbindungseditor

Abb. 8: Signal-Slot-Beziehungen entstehen im Verbindungseditor

Auf Seiten des Buttons verwenden wir das vom Framework vorgegebene clicked()-Signal. Im MainWindow kommt ein noch zu deklarierender Slot zum Einsatz, dessen Erstellung mit dem Anklicken des unter der Slot-Liste befindlichen Ändern-Buttons beginnt.

Klicken Sie auf das Plus unter der Slot-Liste, und geben Sie den Text „button1Clicked()“ ein. Nach dem Schließen des Pop-ups können Sie das clicked()-Signal und den neu angelegten Slot miteinander verdrahten, indem Sie die beiden Elemente in den Listen des Verbindungseditors auswählen.

Mit dem zweiten Steuerelement wird analog verfahren: Der einzige Unterschied besteht darin, dass der neu anzulegende Slot nun auf den Namen „button2Clicked()“ hört. Schließen Sie den WYSIWYG-Editor nach dem Speichern der Änderungen.

Qt realisiert Slots über herkömmliche Member-Funktionen. Öffnen Sie die Header-Datei mainwindow.h, und ergänzen Sie das mit public slots beginnende Snippet:

class MainWindow : public QMainWindow
{
    Q_OBJECT
    public:
        explicit MainWindow(QWidget *parent = 0);
        ~MainWindow();
    public slots:
        void button1Clicked();
        void button2Clicked();
        . . .

Bei der Verwendung des Signal-Slot-Systems ist das Angeben des Q_OBJECT-Makros zwingend erforderlich. Es muss – wie im Snippet gezeigt – direkt nach der öffnenden Klammer der Klassendeklaration stehen, und darf zudem keinesfalls von einem Semikolon flankiert werden.

button1Clicked und button2Clicked werden im Code Behind als leere Member-Funktionen angelegt. Im Moment reicht es aus, wenn Sie leere Methodenkörper erstellen:

void MainWindow::button1Clicked()
{
    
}
void MainWindow::button2Clicked()
{
    
}

Führen Sie das Programm danach sicherheitshalber am Desktop aus. Signal-Slot-Beziehungen entstehen prinzipiell erst während der Ausführung des Programms: Der Compiler kann sie während des Zusammenbaus der Applikation nicht prüfen, da die connect-Methode zwei erst zur Laufzeit sinnvoll interpretierbare Strings entgegennimmt:

QObject::connect(pushButton_3, SIGNAL(clicked()), MainWindow, SLOT(button1Clicked()));

Eventuelle Fehlkonfigurationen werden durch eine in der Konsole ausgegebene Warnung quittiert, die bei der direkten Ausführung am Windows Phone in Qt Creator nicht sichtbar ist.

Aus Platzgründen können wir hier nicht auf die Erstellung eigener Signale eingehen. Die online einsehbare Dokumentation bietet eine kompakte Einführung in die Thematik. Da Signale und Slots eines der Grundidiome von Qt darstellen, sollten angehende Entwickler unbedingt einen Blick auf den nicht sonderlich umfangreichen Text werfen.

Direkter Zugriff

Als letztes Beispiel zum Thema QtGui wollen wir die Buttons mit Logik ausstatten, die den Inhalt der am Formular befindlichen Textbox verändern. Dazu brauchen wir einen Zeiger oder einen ähnlichen Verweis, der den Zugriff auf die am Bildschirm befindliche Objektinstanz ermöglicht.

.ui-Dateien enthalten XML-Code, der im Rahmen der Kompilation des Projekts in eine in Qt Creator nicht sichtbare Headerdatei umgewandelt wird. Quasi nebenbei entsteht ein als Ui bezeichnetes Objekt, dessen Deklaration in mainwindow.h so aussieht:

namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
    Q_OBJECT
    . . .
private:
    Ui::MainWindow *ui;
};

Ui-Objekte sind vergleichsweise simpel aufgebaut. Sie enthalten globale Variable, die den Zugriff auf die im Formular befindlichen Steuerelemente erlauben. Als Name des Members wird der an die Name-Eigenschaft vergebene Wert verwendet – in praktischen Projekten ist es in höchstem Maße ratsam, die von Qt Creator vergebenen Bezeichner durch sprechendere Varianten zu ersetzen:

class Ui_MainWindow
{
public:
    QWidget *centralWidget;
    . . .
    QPushButton *pushButton_3;
    QHBoxLayout *horizontalLayout;

Als zweites – und hier nicht weiter benötigtes – Member gibt es die aus dem Konstruktor des Hauptformulars heraus aufgerufene Funktion setupUi, die für die Belebung der Steuerelemente zuständig ist. Sie ist insofern interessant, als der Generatorcode mitunter ein lehrreiches Beispiel für Dynamic UI mit Qt abgibt.

CVS, nein Danke

Qt generiert die ui_-Dateien bei jeder Rekompilation des Projekts neu, manuelle Änderungen werden dabei gnadenlos überschrieben. Achten Sie zudem darauf, dass die Files keinesfalls in Versionskontrollsysteme hochgeladen werden dürfen: Die schiere Menge banaler Änderungen sorgt über kurz oder lang für einen Zusammenbruch des Servers.

Das Anklicken des ersten Knopfes soll vom Programm mit der Anzeige einer MessageBox belohnt werden. Dazu erweitern Sie die für die Verarbeitung des vom Button emittierten Signals zuständige Funktion um folgenden Code:

void MainWindow::button1Clicked()
{
    QMessageBox *myMessageBox=new QMessageBox(this);
    myMessageBox->setText("Hallo Welt");
    myMessageBox->show();
}

Das Übergeben eines Verweises auf das MainForm-Objekt sorgt dafür, dass die MessageBox als Kind des Formulars gilt und nach dem Schließen ebendieses automatisch mit abgetragen wird:

QMessageBox wird nicht von Haus aus in den Kompilationsprozess eingebunden. Die Nutzung der Klasse setzt das Einbinden eines Headers voraus, das nach folgendem Schema erfolgt:

#include <QMessageBox>

Es ist empfehlenswert, den in spitze Klammern gesetzten Klassennamen als „Futter“ für den Include-Befehl heranzuziehen: Wer Dateinamen direkt angibt, handelt sich bei der Cross-Plattform-Kompilation aufgrund der verschiedenen Hostsysteme Probleme ein.

Formular, wechsle dich

Single-Page-Applikationen sind unter Qt-Programmierern weder beliebt noch bekannt: QtGUI-basierte Lösungen bestehen aus einer Abfolge von mehreren Fenstern. Das Anlegen eines neuen Formulars beginnt mit dem Öffnen des über Datei | Neu anzusprechenden Wizards, indem Sie eine neue Datei vom Typ Qt | Qt-Designer-Formularklasse anlegen. Als Vorlage dient Templates\Forms | Main Widget, als Klassenname wird in diesem Abschnitt SecondWindow verwendet.

Nach dem Quittieren des Assistenten generiert Qt Creator drei Dateien: Neben der für die Formulardeklaration zuständigen .ui-Datei wird Ihr Projekt auch um einen Header und eine .cpp-Datei ergänzt.

Neu erstellte Formulare enthalten von Haus aus eine Statusbar und eine Menüleiste, die unter Windows Phone eher störend aussieht und deshalb entfernt werden sollte. Der in MainWindow implementierte Handler für den zweiten Button wird danach um den für die Aktivierung des nächsten Formulars notwendigen Code ergänzt:

#include "secondwindow.h"
void MainWindow::button2Clicked()
{
    SecondWindow* mySecondWindow=new SecondWindow(this);
    mySecondWindow->showFullScreen();
}

An dieser Stelle lauert eine kleine Falle: Unsere Applikation hat kein Problem mit dem Parent-Child-System, da MainForm während der Anzeige von SecondForm geöffnet bleibt. In Applikationen kommt es jedoch immer wieder vor, dass das vorherige Formular geschlossen wird – eine bestehende Parent-Child-Beziehung zum abgetragenen Element würde in diesem Fall dafür sorgen, dass das neu am Bildschirm erschienene Formular sofort über den Jordan geht.

Damit ist die Arbeit an unserem Projekt fürs Erste abgeschlossen. Jagen Sie die Applikation auf ein Windows Phone, und probieren Sie die beiden Buttons aus. Die MessageBox sollte wie in Abbildung 9 gezeigt aussehen.

Abb. 9: Alles funktioniert wie vorgesehen

Abb. 9: Alles funktioniert wie vorgesehen

Mehr lernen

Aufgrund des eingeschränkten Platzes ist es an dieser Stelle nicht möglich, die in QtGUI enthaltenen Steuerelemente auch nur annähernd vollständig vorzustellen. Digia bietet unter online eine grobe Übersicht der Widgets an – Informationen über die einzelnen Elemente finden sich danach in der Dokumentation zur betreffenden Masterklasse.

Bei der Verarbeitung großer Datenmengen sollten die modellbasierten Item Views den einfacher zu handhabenden Item Widgets vorgezogen werden. Es handelt sich dabei um Steuerelemente, die ihre Informationen aus einer im Hintergrund stehenden Datenklasse beziehen: Der Vorteil dieser zugegebenermaßen komplexen Architektur liegt darin, dass die Synchronisation zwischen der View und dem Datenmodell entfällt.

Zu guter Letzt sei noch ein Hinweis auf Style Sheets erlaubt. Die meisten Portierungen bringen eine oder mehrere Designvorlagen mit, die das Aussehen der gerenderten Widgets an die Plattform anpassen. Geben Sie den Namen der Zuweisung zuständigen Methode QApplication::setStyle mit dem Plattformnamen in Google ein, um mehr über das Angebot zu erfahren.

Fazit

QtGUI ist die Boeing 737 der GUI-Programmierung. Das Framework gewinnt weder Sympathie- noch Designpreise, Entwickler empfinden die Arbeit stellenweise sogar als langweilig. Dieses seltsame Kompliment ist darin begründet, dass QtGUI als Arbeitstier seit Jahr und Tag für zuverlässige und bedienbare Interfaces sorgt.

Die „Nachfolgestandards“ Cascades und QML werden das Framework schon allein aufgrund der normierenden Macht des Faktischen niemals komplett ersetzen. Es gibt kaum eine Portierung von Qt, die QtGUI nicht mitbringt: Seit der Übernahme der Weiterentwicklung des Frameworks durch Digia sind die früher stellenweise verlautbarten Ersetzungsfantasien wahnhafter Maintainer verstummt.

Wer heute an einer Enterprise-Applikation arbeitet, macht mit QtGUI mit Sicherheit nichts falsch. Nach dem Überwinden der zweifellos vorhandenen Lernkurve gehen auch fortgeschrittenste Designs wie von alleine von der Hand.

QtGUI stößt immer dann an seine Grenzen, wenn „zeitgeistiges Design“ erforderlich ist. Animationsintensive Benutzerschnittstellen lassen sich mit QML besser realisieren.

Mobile Technology

Mobile TechnologyDieser Artikel ist im Mobile Technology erschienen. Mobile Technology ist der Wegbegleiter für alle, die sich professionell mit der Entwicklung für mobile Devices und den Möglichkeiten, die der Markt des Mobile Business und Marketing bereithält, beschäftigen.

Natürlich können Sie das Mobile Technology über den entwickler.kiosk auch digital im Browser oder auf Ihren Android- und iOS-Devices lesen. In unserem Shop ist das Mobile Technology ferner im Abonnement oder als Einzelheft erhältlich.

Aufmacherbild: Set of vintage buttons on old wooden table von Shutterstock.com / Urheberrecht: iravgustin

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -