DIGITAL
Qt ist ein Toolkit, das dabei hilft Programme (und insbesondere Programmoberflächen) plattformübergreifend zu entwickeln. Bei der richtigen Verwendung kann Ihr Programm nicht nur auf Windows und gängigen POSIX-Systemen, sondern mittlerweile auch auf Android, iOS, Windows Phone und eingebetteten Systemen ausgeführt werden.
In dieser Übersicht soll ein kleiner Teil von Qt (in seiner aktuellen Version Qt5.15) vorgestellt werden, der für die Lösung der Aufgaben ausreichen sollte.
QtCreator:
Wählen Sie beim Erstellen eines Projektes den Projekttyp: Application (Qt) -> Qt Widgets AApplication.
CMake:
Sofern Sie bereits mit CMake vertraut sind, ist das Bauen gegen Qt einfach durch target_link_libraries
möglich:
cmake_minimum_required(VERSION 3.0.2)
project(GuiProgramm)
find_package(Qt5Widgets REQUIRED)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
add_executable(GuiProgramm gui.cpp)
target_link_libraries (GuiProgramm Qt5::Widgets)
Bauen kann man dann wie mit Cmake üblich mit: cmake .; make
QMake:
QMake ist wie CMake ein Makefile-Generator, der jedoch für Qt spezialisiert ist.
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = GuiProgramm
TEMPLATE = app
DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
CONFIG += c++17
SOURCES += gui.cpp
Bauen kann man dann ähnlich wie mit Cmake mit: qmake .; make
Bevor Sie die Klassen aus Qt verwenden können, müssen Sie zuvor ein QApplication
-Objekt erstellen. Dieses kümmert sich darum, dass Kommandozeilenargumente an die GUI Ihres Programms richtig übergeben werden, Events/Interaktiv bearbeitet werden, Ihr Programm richtig initialisiert wird uvm.
#include <QtWidgets>
// argc bekommt die Anzahl der Kommandozeilenargumente übergeben,
// argv bekommt die Argument selbst übergeben
// Bsp.:
// Windows:
// abc.exe blub bla
// Linux/OS X:
// ./abc blub bla
// führt zu
// argc ist 3
// argv ist {"./abc", "blub", "bla"}
int main(int argc, char *argv[])
{
# Initialisierung des Programms und Ihrer GUI
QApplication app(argc, argv);
// ...
// Ihr Programmcode
// ...
// Starten der Ereignisschleife (engl. event loop; Abfangen von Klicken,
// Tippen, etc.) und beenden des gesamten Programms beim Beenden des Qt-Teils
// (dies geschieht standardmäßig beim Schließen aller Fenster).
// Diese beiden Schritte zu trennen ist innerhalb dieses Praktikums nicht nötig.
return app.exec();
}
Achtung: Vergessen Sie beim Testen nicht QApplication
zu initialisieren und am Ende auszuführen (app.exec()
). Falls Sie es vergessen, werden Sie unter Umständen ein schwarzes Fenster sehen, auf jeden Fall aber sind dann Ihre Interaktionsmöglichkeiten mit der Anwendung sehr beschränkt.
Hallo Welt Testen Sie den folgenden Code und versuchen Sie nachzuvollziehen, was geschieht:
#include <QtWidgets>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QWidget w;
QLabel *l = new QLabel(&w);
l->setText("Hallo Welt!");
// Alternativ:
/* QLabel *l2 = new QLabel("Nochmals hallo!",&w); */
w.setGeometry(120,120,200,50);
w.move(100,300);
w.setWindowTitle("Tadaa, ein Fenster");
w.show();
return app.exec();
}
Die QWidget
-Klasse ist die Basisklasse aller anderen Benutzerschnittstellen-Objekte. Das bedeutet Buttons, Textfelder, Fenster usw. liefern dieselbe Funktionalität. Wie Sie vielleicht bereits durch das „Hallo Welt“ bemerkt haben, repräsentiert QWidget()
ein leeres Fenster1. Hier sind noch ein paar Funktionen von QWidget
:
#include <QtWidgets>
#include <QDesktopWidget>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QWidget fenster1;
QWidget fenster2;
QWidget fenster3;
QWidget fenster4;
// Fensternamen
fenster1.setWindowTitle("Das ist Fenster 1");
fenster2.setWindowTitle("Das ist Fenster 2 (max)");
fenster3.setWindowTitle("Das ist Fenster 3 (min)");
fenster4.setWindowTitle("Das ist Fenster 4 (center)");
// typische Fenstermanipulation
fenster1.resize(300,500); // Größe ändern.
fenster1.move(25,50); // auf dem Bildschirm verschieben
// Zentrieren auf dem Bildschirm (etwas komplizierter, aber auch möglich)
fenster4.move(QApplication::desktop()->screenGeometry(&fenster4).center()
- fenster4.rect().center());
// Anzeigen der Fenster
fenster1.show();
fenster2.showMaximized();
fenster3.showMinimized();
fenster4.show();
return app.exec();
}
Meistens werden Sie keine „losen“ GUI-Objekte in einer Methode haben wollen. Stattdessen können Sie auch eine Klasse erstellen, die selbst von QWidget
ableitet, um eigene Widgets zu erstellen (natürlich können Sie von allen QWidget
-Klassen ableiten, etwa die QMainWindow-Klasse, zu der wir später kommen werden).
#include <QtWidgets>
class MyGuiProgram : public QWidget {
public:
// statische (optionale) Methode zum Starten
// des GUI-Programms
static int spawn(int argc, char *argv[]) {
QApplication app(argc, argv);
MyGuiProgram P;
return app.exec();
}
// Konstruktor
MyGuiProgram() : QWidget() /* dies ruft den Konstruktor der Basisklasse auf */ {
// gute Idee: Lagern Sie Code aus, um Ihren Code
// übersichtlich zu halten
this->setWindowTitle("Nice Title");
this->init_mainWindow(); //ausgelagerte GUI-Erstellung
this->showMaximized();
}
void init_mainWindow() {
// new ist wichtig, sonst wird QLabel nach der Funktion zerstört
// delete muss dabei nicht mehr aufgerufen werden!
QLabel *L = new QLabel("What a label!", this);
}
};
int main(int argc, char *argv[])
{
return MyGuiProgram::spawn(argc, argv);
}
new
aufgerufen wurde, aber kein delete
? Das liegt daran, dass QWidget
das Speichermanagement mit übernimmt. Wird MyGuiProgram
gelöscht, werden auch alle Kindelemente automatisch mitgelöscht.
Bevor wir uns spezielleren Themen widmen möchten wir Ihnen einige Elemente präsentieren, die Ihnen beim Erstellen Ihrer GUI behilflich sein kann.
Wie bereits erwähnt, sind auch GUI-Elemente wie Buttons oder Text-Eingabefelder selbst QWidgets - daher erben sie auch die oben aufgeführten Funktionen und können also selbst auch bereits alleinstehend in einem automatisch erstellten Fenster angezeigt werden. Z.B.:
QPushButton b("Drück mich!");
b.resize(150,200);
b.move(10,20);
b.show();
Im Umkehrschluss bedeutet das aber auch, dass QWidget selbst kein Fenster darstellt, sondern lediglich einen leeren "Container". Einzelne Elemente anzuzeigen ist nicht so spannend; bevor wir zur Schachtelung von Elementen kommen, sollen nun einige nützliche Elemente und ihre Funktionsweisen erklärt werden. Probieren Sie die Elemente ruhig aus. Alternativ können Sie ihr Aussehen und mehr Informationen zu diesen auch unter den eingangs genannten Seiten entnehmen. Schauen Sie sich ruhig auch die oben genannte offizielle Dokumentation an, um Genaueres zu erfahren.
Anmerkung: Alle QWidgets
haben stets zusätzliche Konstruktoren die ein weiteres QWidget*
als letztes Argument erhalten, das das neue Widget als Hauptelement anzeigen soll (und dessen Speicherverwaltung übernimmt, siehe oben).
QPushButton b("Drück mich!",this);
b.resize(150,200);
b.move(10,20);
Wie man strukturierte Layouts (ohne absolute Positionierung) baut schauen wir uns weiter unten an.
„Klickbare“ Buttons
// in .h: static void myfunction();
void MyGuiProgram::myfunction() {
std::cout << "clicked button" << std::endl;
}
...
QPushButton* btn = new QPushButton("&Drück mich!"); // & bedeutet: Alt+D löst den Button aus
btn->setDefault(true); // Standard-Button (wird bei Enter gedrücht)
btn->setEnabled(false); // ausgegraut
// connect Verbindet ein Ereignis mit einem beliebigen Funktionsaufruf.
// Hier wird das Signal/Ereignis „clicked“ mit dem Slot/der Funktion
// myfunction verbunden.
connect(btn, &QPushButton::clicked, &MyGuiProgram::myfunction);
// kürzer und sauberer mithilfe von Lambdas
connect(btn, &QPushButton::clicked, [=](){
std::cout << "clicked button lambda" << std::endl;
});
Falls connect nicht gefunden wird, kann stattdessen QObject::connect(...)
verwendet werden, bzw. statt QObject
kann auch QPushButton::connect(...)
verwendet werden, um Type-checks auf den Slots zu erlauben.
Andere Ereignisse sind: QPushButton::released
und QPushButton::pressed
. QPushButton::clicked
wartet bis beide Ereignisse eingetroffen sind.
QPushButton
werden die wichtigsten Signals/Ereignisse von QAbstractButton
geerbt, https://doc.qt.io/qt-5/qabstractbutton.html#signals .
QApplication
zu initialisieren und am Ende Ihres Codes auszuführen (app.exec()
). Falls Sie es vergessen, werden Sie unter Umständen nur ein schwarzes Fenster sehen.
Nicht-veränderbaren (durch den User) Text oder Bilder (wird später behandelt) anzeigen.
QLabel *lbl = new QLabel("Ha! Du kannst mich nicht ändern!");
QLabel *lbl2 = new QLabel();
lbl2->setText("nachträglich verändert"); // nur nicht durch den User veränderbar
lbl2->setAlignment(Qt::AlignRight); // Ausrichtung
Veränderbarer Text
QLineEdit *le = new QLineEdit("Initialtext");
QLineEdit *le2 = new QLineEdit(this);
// Einstellungen
le->setMaxLength(10); // Maximallänge
le->setAlignment(Qt::AlignRight); // Ausrichtung
le->setFont(QFont("Arial", 20)); // Schriftart (Arial, Größe 20)
// direkte Validierung
le2->setValidator(new QIntValidator()); // nur Ganzzahlen
le2->setValidator(new QDoubleValidator()); // nur Fließkommazahlen
le2->setValidator(new QDoubleValidator(0.99, 99.99, 2)); // (festes Format)
// signals (wie bei QPushButton)
connect(le2, &QLineEdit::inputRejected, [=](){
std::cout << "ungültige Eingabe" << std::endl;
});
connect(le2, &QLineEdit::cursorPositionChanged, [=](int oldPos, int newPos){
std::cout << "neue Position" << newPos << std::endl;
});
connect(le2, &QLineEdit::textEdited, [=](QString s){
std::cout << "neuer Text:" << s.toStdString() << std::endl;
});
// ..., &QLineEdit::textChanged, ...
// ..., &QLineEdit::editingFinhsed, ...
// ..., &QLineEdit::selectionChanged, ...
// ..., &QLineEdit::returnPressed, ...
Checkbox mit Text-Annotation
QCheckBox *cb = new QCheckBox("Possibility 1");
cb->setChecked(true); // Standardmäßig an oder aus?
cb->setText("abc");
cb->text(); // getter (hier: "abc")
cb->isChecked();
// Funktionsaufruf bei Statusänderung, Verwendung wie bei den anderen Signals oben.
// connect( ..., &QCheckBox::toggled, ... );
Jedes Widget hat das optionale Argument parent
. Durch Setzen dessen auf ein anderes Widget, wird das neu erstellte als Teil des Widgets parent
dargestellt.
// Initialisierung wie üblich
QApplication app(argc, argv);
QWidget win;
win.setGeometry(10, 30, 300, 200); // Position: (10,30) auf dem Bildschirm
// Größe: (300,200)
QPushButton b*tn = new QPushButton("Jim",&win);
btn.move(20,20); // Position (20,20) in parent=win
QPushButton *btn2 = new QPushButton("Jim2",&win);
btn2.move(120,120); // Position (120,120) in parent=win
// Anzeigen und Starten
win.show();
app.exec();
Auf diese Art und Weise können ganze Layouts definiert werden. Erstellen Sie Widgets und arrangieren Sie darin rekursiv andere Widgets. Diese Herangehensweise hat jedoch einige offensichtliche Nachteile:
Aus diesem Grund gibt es sogenannte Layout Manager, die die absolute Positionierung für Sie übernehmen und bei der dynamische Änderungen eines Layouts helfen können.
Worin sich alle Layouts ähneln, ist, dass man anstelle eines Widgets auch immer ein anderes Layout einsetzen kann. So können selbst sehr verschachtelte GUIs strukturiert werden. Das äußerste Layout muss per win.setLayout(layout)
zugewiesen werden.
Diese Layouts arrangieren Widgets und Layouts in einer horizontalen bzw. vertikalen Reihe. Es folgt ein Beispiel, das beides verwendet.
Interessant ist hier insbesondere das Verhalten bei Größenänderungen des Fensters:
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QWidget win;
QPushButton *btn1 = new QPushButton("Jim");
QPushButton *btn2 = new QPushButton("Benjamin");
QLabel *lbl = new QLabel("Entscheide dich:");
QPushButton *btn3 = new QPushButton("blau");
QPushButton *btn4 = new QPushButton("rot");
QVBoxLayout *vbox = new QVBoxLayout();
vbox->addWidget(btn1); // erst btn1
vbox->addWidget(btn2); // darunter btn2
vbox->addStretch(); // skalierter leerer Platz
vbox->addWidget(lbl); // darunter ein Label
QHBoxLayout *hbox = new QHBoxLayout();
vbox->addLayout(hbox); // darunter ein neues Layout (als Widget)
hbox->addWidget(btn3); // links: btn3
hbox->addStretch(); // Mitte: gestreckte leere Fläche
hbox->addWidget(btn4); // rechts: btn4
win.setLayout(vbox);
win.show();
return app.exec();
}
Diese Layouts arrangieren Widgets und Layouts in einer typischen Formularansicht: Zeilenweise links ein Label, rechts ein Widget:
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QWidget win;
QFormLayout *form = new QFormLayout();
form->addRow(new QLabel("test"), new QLineEdit());
form->addRow(new QLabel("name"), new QPushButton());
QHBoxLayout *hbox = new QHBoxLayout();
hbox->addWidget(new QCheckBox("check1"));
hbox->addWidget(new QCheckBox("check2"));
hbox->addWidget(new QCheckBox("check3"));
form->addRow(new QLabel("check"), hbox);
win.setLayout(form); // Wichtig! Erst hier wird das Layout an win angefügt
win.show();
}
Das Grid-Layout ermöglicht das Arrangieren der Widgets in einem Gitter. Es können dabei sowohl einzelne Gitterzellen durch je ein Widget befüllt werden, als auch mehrere Zellen durch ein Widget:
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QWidget win;
QGridLayout *grid = new QGridLayout();
// einzelne Zellen
QString str = "Knopf %1 / %2";
for (int i=1; i<=4; i++)
for (int j=1; j<=3; j++)
grid->addWidget(new QPushButton(str.arg(i).arg(j)), i,j); // Position (i,j)
// mehrere Zellen
grid->addWidget(new QPushButton("Knopf Quad"), 1,3, 2, 2); // Position (1,3), Größe (2,2)
grid->addWidget(new QPushButton("Knopf Lang"), 1,5, 4, 1);
grid->addWidget(new QPushButton("Knopf Breit"), 4,1, 1, 2);
// Unterlayouts können übrigens auch hinzugefügt werden
// grid.addLayout(...);
win.setLayout(grid); // Wichtig! Erst hier wird das Layout an win angefügt
win.show();
return app.exec();
}
Um das Arbeiten mit Ihrem Programm zu erleichtern, können Sie Tooltips einsetzen. Diese werden angezeigt, sobald Sie mit der Maus über das jeweilige Element fahren und warten.
QPushButton *btn = new QPushButton("&Drück mich!"); // & bedeutet: Alt+D löst den Button aus
btn->setToolTip("Dies erscheint erst, wenn die Maus über den Button fährt und dann wartet.");
// PS: Tooltips können auch bei allen anderen QWidgets angewandt werden.
Angenommen Sie erwarten eine Benutzereingabe, z.B. das Auswählen einer Datei (mittels QFileDialog
) oder Sie müssen dem Benutzer etwas mitteilen. Durch einen Dialog kann dies erreicht werden. Die folgenden Beispiele sollten bei der Erstellung Ihrer Benutzeroberflächen als Anreiz reichen. (Eine weitere, hier nicht aufgeführte, Dialogklasse ist QMessageBox
)
#include <QtWidgets>
void dialog(QPushButton *b) {
QDialog d;
d.setWindowTitle("Dialogtitel");
// kann der Dialog umgangen werden um andere Fenster zu bedienen?
d.setWindowModality(Qt::ApplicationModal); // nein (Standard)
// d.setWindowModality(Qt::WindowModal); // ja
// ... wie QWidget verwendbar
auto btnok = new QPushButton("Rot",&d);
auto btnno = new QPushButton("Blau",&d);
auto hbox = new QHBoxLayout();
hbox->addWidget(btnok);
hbox->addWidget(btnno);
d.setLayout(hbox);
// akzeptieren / ablehnen
QPushButton::connect(btnok, &QPushButton::clicked, [&d]() { d.accept(); });
QPushButton::connect(btnno, &QPushButton::clicked, [&d]() { d.reject(); });
// bei Fehler / Erfolg an Hauptfenster melden
QPushButton::connect(&d, &QDialog::accepted, [b]() {
b->setText("Willkommen in der Matrix!");
});
QPushButton::connect(&d, &QDialog::rejected, [b]() {
b->setText("Ach ist das Wunderland schön!");
});
// Extras
btnok->setDefault(true); // btn wird bei Enter gedrückt
d.exec(); // Fenster ausführen
}
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QWidget w;
QPushButton *b = new QPushButton("Die &Entscheidung", &w);
b->resize(200,50);
QPushButton::connect(b, &QPushButton::clicked, [b]() { dialog(b); });
w.show();
return app.exec();
}
#include <QtWidgets>
void dialog(QPushButton *b) {
QFileDialog d;
d.setWindowTitle("Dialogtitel");
// bei Auswahl einer Datei
QFileDialog::connect(&d, &QFileDialog::fileSelected, [b](QString file) {
b->setText(file);
});
d.exec(); // Fenster ausführen
}
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QWidget w;
QPushButton *b = new QPushButton("Die &Entscheidung", &w);
b->resize(500,30);
QPushButton::connect(b, &QPushButton::clicked, [b]() { dialog(b); });
w.show();
return app.exec();
}
Manchmal benötigen Sie Programmcode, der nach einem Zeitintervall einmalig oder periodisch aufgerufen werden soll. Dieses Beispiel zeigt Ihnen, wie Sie dies mit Qt erreichen können.
fn ist wie bei allen anderen Slots auch entweder ein Pointer auf die aufzurufende Funktion oder aber ein Lambda-Ausdruck
QTimer timer;
timer.setInterval(300); // Timer-Reset alle 300ms
// QTimer::connect(&timer, &QTimer::timeout, fn);
// Z.B.:
QTimer::connect(&timer, &QTimer::timeout, []() {
std::cout << "timeout" << std::endl;
});
timer.start(3000); // 3000: Anzahl der Millisekunden, nach denen der Timer die
// Ausführung von fn erstmals startet. (Standardmäßig ms=0)
//timer.stop(); // Timer anhalten
//timer.setSingleShot(true); // einmalige Ausführung von fn
//timer.isActive(); // gibt true zurück, falls der Timer an ist
Das Arbeiten mit Bildern ist für einige Anwendungen unerlässlich. Das betrifft nicht nur das Anzeigen von bestehenden Bildern, sondern auch das Erstellen neuer Bilddaten. In Qt gibt es dabei zwei Arten von Bildrepräsentationen: QPixmap
und QImage
.
QPainter
gemalt werden kann. QPainter
kann dabei alles, was ein Zeichenprogramm wie Paint auch kann: Quadrate, Kreise, Text zeichnen und dabei eine Auswahl an Pinseln verwenden.Als erstes schauen wir uns das Arbeiten mit reinen Pixeldaten an.
Bilder sind keine Widgets in Qt und können daher nicht direkt angezeigt werden. Glücklicherweise liefert das Widget QLabel
diese Möglichkeit nach, indem man diesem ein QPixmap
als anzuzeigendes Objekt übergibt. (Gehen Sie davon aus, dass die Konvertierung von QImage
zu QPixmap
schnell von statten geht.) Erinnern Sie sich daran, dass ein Pixmap fest ist und daher beim Neuzeichnen erneut erstellt werden muss. Im folgenden Beispiel gehen wir davon aus, dass sich ein Bild mit dem Namen bild.png
in demselben Ordner befindet, in dem auch das Skript gestartet wird.
int main(int argc, char *argv[]) {
// in QLabel wird das Bild angezeigt
QLabel display;
// Laden der Datei in eine Pixmap (nicht direkt bearbeitbar)
QPixmap bild("bild.png");
display.setPixmap(bild);
// Alternativ: QImage (muss zu QPixmap konvertiert werden)
QImage bild("bild.png");
display.setPixmap(QPixmap::fromImage(bild));
// Fenster anzeigen
display.show();
// wie immer auch ...
app.exec();
}
Sie können ein Pixmap folgendermaßen skalieren - beachten Sie, dass es sowohl möglich ist die Skalierung an der vorgegeben Größe eines displays (QLabel
) festzumachen, aber auch die Größe direkt festzulegen:
// Möglichkeit 1
QLabel display;
display.resize(200,300);
QPixmap pixmap = ... // wie oben QPixmap(...) oder QPixmap::fromImage(...)
QPixmap scaledpixmap = pixmap.scaled(display.size(), Qt::KeepAspectRatio);
display.setPixmap(scaledpixmap);
// Möglichkeit 2
QLabel display;
QPixmap pixmap = ... // wie oben QPixmap(...) oder QPixmap.fromImage(...)
QPixmap scaledpixmap = pixmap.scaled(200,300, Qt::KeepAspectRatio);
display.setPixmap(scaledpixmap);
// wir erstellen zuerst das Bild
int breite = 400, hoehe = 300;
// QImage-Formate: http://doc.qt.io/qt-5/qimage.html#Format-enum
QImage img(breite, hoehe, QImage::Format_RGBA8888);
img.fill(Qt::white); // Bild mit Standardwerten füllen (Wichtig!)
// das Format der Pixelfarbe lautet 0xAARRGGBB, wobei
// RR, GG, BB jeweils die Hex-Darstellung der Farbwerte für
// Rot, Grün und Blau zwischen 0 und 255 ist (d.h. 195 entspräche C3).
// 0 bedeutet, dass die Farbe nicht vorhanden ist,
// 255 ist der Maximalwert jeder Farbe.
// AA beschreibt die Transparenz (0 für Transparent, 1 für Sichtbar)
img.setPixel(10,20, 0xff009900); // Dunkelgrün (der A-/Opacity-Wert ist vorne)
// das Setzen von Hex-Werten bedarf einiger Übung und Gewöhnung.
// Glücklicherweise ist es auch möglich, die Farbwerte im 10er-System
// anzugeben (Werte liegen jeweils zwischen 0 und 255).
img.setPixelColor(20,40,QColor(20,30,155));
// Anzeigen in QLabel display
display.setPixmap(QPixmap::fromImage(img));
// neue Manipulation ist erst beim Neu-Zeichnen sichtbar!
img.setPixelColor(80,80,QColor(20,30,155));
// erneut Zeichnen
display.setPixmap(QPixmap::fromImage(img));
Nicht jedes Programm, das mit Bildern arbeitet, muss eine GUI mitliefern und dieses Bild erst auf dem Bildschirm anzeigen. Hier sehen Sie eine Möglichkeit, das Bild zu speichern (kann natürlich auch zusätzlich angezeigt werden).
# wir erstellen zuerst das Bild
int breite = 400, hoehe = 300;
QImage img(breite, hoehe, QImage::Format_RGBA8888);
img.fill(Qt::white); // Bild mit Standardwerten füllen (Wichtig!)
# Ändern von Pixelinformationen
img.setPixel(10,20, 0xff009900); // Dunkelgrün (der A-/Transparency-Wert ist vorne)
img.save("neues_bild.png")
Mit QPainter können sie Ellipsen, Kreissegmente, Rechtecke, Linien, Polygonzüge, Grafiken und Text auf einem Bild zeichnen. Beachten Sie bitte die Dokumentation für eine vollständigere Auflistung der Funktionen:
#include <QtWidgets>
#include <iostream>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QLabel display;
int breite = 400, hoehe = 600;
display.resize(breite,hoehe);
// unsere Hauptzeichenfläche
QImage *canvas = new QImage(breite,hoehe, QImage::Format_RGBA8888);
QPainter painter(canvas);
// Linien
float linienbreite = 2;
painter.setPen(QPen(Qt::blue, linienbreite, Qt::DotLine,
Qt::SquareCap, Qt::BevelJoin));
painter.drawLine(50,50,50,100);
painter.setPen(QPen(QColor(100,200,300,50), 5*linienbreite,
Qt::SolidLine, Qt::RoundCap, Qt::BevelJoin));
painter.drawLine(70,50,70,100);
// Rechtecke (beachten Sie die Verwendung von Stiften (Pen) und Pinseln (Brush)
painter.fillRect(100,50,150,200,Qt::white);
painter.setPen(QPen(Qt::red, linienbreite, Qt::DashLine,
Qt::SquareCap, Qt::BevelJoin));
painter.drawRect(120,70,170,220);
painter.setPen(Qt::NoPen);
painter.setBrush(Qt::green);
painter.drawRect(220,170,270,320);
// Ellipsen
painter.setBrush(Qt::NoBrush);
painter.setPen(QPen(QColor(0,150,150,100), 20));
painter.drawEllipse(QPoint(230,180),30,50);
// Text
painter.setPen(Qt::black);
float groesse = 20;
painter.setFont(QFont("Helvetica", groesse));
painter.drawText(100,300, "Hallo Welt!");
display.setPixmap(QPixmap::fromImage(*canvas));
display.show();
return app.exec();
}
Sofern sie eine odere mehrere QPixmap aufwändig gezeichnet haben, können sie dies verwenden, um sie übereinander zu setzen. (Nicht gezeichnete Pixel sind transparent) Dies eignet sich nicht nur für Zeichenprogramme, bei denen man auf mehreren Ebenen zeichnen können möchte, (um sich etwas Arbeit zu sparen, insbesondere, wenn man nur Teile des Bildes austauchen will) sondern auch etwa für Spiele, bei denen der Hintergrund fest ist und nur der Vordergrund neu gezeichnet wird. Insbesondere dann ist diese Variante von Vorteil, da das Zeichnen selbst auch bereits Zeitintensiv sein kann:
// Qlabel zum Anzeigen des Bildes
QLabel display;
display.setGeometry(QRect(0,0,width*zoom, height*zoom));
// unsere Hauptzeichenfläche
canvas = QPixmap(1000,500); // oder QImage
painter = QPainter(canvas);
// Hintergrund Ebene
ebene1 = QPixmap(1000,500); // oder QImage
painter1 = QPainter(ebene1);
// ... langsamer code, komplexes Zeichnen der Ebene
// Objekt auf eigener Ebene
ebene2 = QPixmap(200,300); // oder QImage
painter2 = QPainter(canvas);
// langsamer code, komplexes Zeichnen der Ebene
// ... in der redraw-Schleife
// weiß färben
painter.fillRect(0,0,1000,500,Qt.white);
// Hintergrund hinzufügen, (0,0) ist die Startposition
painter.drawPixmap(0,0,ebene1); // bei QImage: .drawImage(...)
// Objekt hinzufügen
painter.drawPixmap(234,345,ebene2); // bei QImage: .drawImage(...)
// skalieren auf die Größe der Zeichenfläche
// für Canvas vom Typ: QPixmap
display.setPixmap(canvas);
// für canvas vom Typ: QImage
display.setPixmap(QPixmap.fromImage(canvas));
Das QMainWindow
gibt bereits eine GUI-Struktur vor, die bei der GUI-Erstellung häufig verwendet wird: Statusleiste, Menü, anheftbare Widgets (hier nicht behandelt):
#include <QtWidgets>
#include <iostream>
int main(int argc, char *argv[]) {
// Initialisierung wie üblich
QApplication app(argc, argv);
QMainWindow main;
main.resize(500,600);
main.setWindowTitle("Hauptfenster");
// Zentrale Teil kann wie Üblich gefüllt werden
QWidget *win = new QWidget();
main.setCentralWidget(win);
QHBoxLayout *hbox = new QHBoxLayout();
win->setLayout(hbox);
hbox->addWidget(new QPushButton("links"));
hbox->addWidget(new QPushButton("Mitte"));
hbox->addWidget(new QPushButton("rechts"));
// MainWindow hat auch eine Statusleiste
// (diese kann selbst auch Widgets anzeigen)
QStatusBar *stat = main.statusBar();
// stat->addWidget(new QLabel("Statustext")) // linksbündig, wird evtl.
// von showMessage überblendet
stat->addPermanentWidget(new QLabel("Statustext2")); // rechtsbündig
stat->showMessage("wird 2s lang eingeblendet", 2000);
// stat->showMessage("wird angezeigt, bis gelöscht wird");
// stat->clearMessage(); // löschen angezeigter Nachrichten
// ... und auch eine Menüleiste
QMenuBar *menu = main.menuBar();
menu->setNativeMenuBar(false); // optional: z.B. OS X stellt das Menü anders dar
// ... Menü-Reiter: Datei
QMenu *m1 = menu->addMenu("&Datei");
m1->addAction("Neu");
// Menüeintrag mit Tastenkürzel
m1->addAction("Öffnen");
QMenu *m1sub = m1->addMenu("Untermenü");
m1sub->addAction("Versteckt");
m1->addSeparator();
QAction *close = new QAction("Beenden", &main);
close->setShortcut(Qt::CTRL+Qt::Key_Q);
close->setStatusTip("Schließt das Programm");
QAction::connect(close, &QAction::triggered, [&main](){
main.close();
});
m1->addAction(close);
// ... Menü-Reiter: Bearbeiten
QMenu *m2 = menu->addMenu("&Bearbeiten");
for (int i=0; i<3; i++) {
m2->addSeparator();
for (int j=0; j<4; j++)
m2->addAction("Platzhalter");
}
menu->addSeparator();
// ... Menü-Reiter: Info
QMenu *m3 = menu->addMenu("Info");
QAction *info = m3->addAction("Info");
info->setShortcut(Qt::Key_I);
QAction::connect(info, &QAction::triggered, [](){
QDialog dialog;
dialog.resize(200,150);
QLabel *L = new QLabel("Programmversion 3.1415",&dialog);
dialog.exec();
});
// Anzeigen
main.show();
return app.exec();
}
Nicht jede Eingabe soll in Text-Eingabefeldern erfolgen. Häufig benötigt man Eingaben, die erst vorverarbeitet werden müssen. Sie haben im letzten Abschnitt die Möglichkeit kennengelernt Tastenkürzel für Menüeinträge zu definieren. Die Definition war dabei lokal an das jeweilige Menü und das Kürzel selbst gebunden. Im Folgenden sind die Funktionen globaler Natur. Mit anderen Worten - alle Tasten rufen zuerst dieselbe Funktion auf, erst darin wird entschieden was mit dem „Event“ gemacht wird.
Zuerst konzentrieren wir uns hier auf das Drücken einer Taste (keyPressEvent
), die vorgehensweise beim Event fürs Loslassen der Taste (keyReleaseEvent
) ist dabei ganz analog.
Ähnlich zu den Signalen, bei denen Sie per connect
die aufzurufende Funktion an das Objekt gebunden haben, können Sie bei den hier aufgeführten Events die entsprechende Funktion überschreiben. In dem folgenden Besipiel wird jedes mal, wenn Sie eine beliebige Taste drücken, dies in der Statusleiste kenntlich gemacht. Im Spezialfall der Escape-Taste wird das Fenster geschlossen:
#include <QtWidgets>
#include <iostream>
class CustomWindow : public QMainWindow {
public:
void keyPressEvent( QKeyEvent* event ) {
QString str(event->key());
if (event->key() == Qt::Key_Escape)
close();
else if (event->key() == Qt::Key_Up)
statusBar()->showMessage("Taste mit key-code down gedrückt", 1000);
else if (event->key() == Qt::Key_Down)
statusBar()->showMessage("Taste mit key-code up gedrückt", 1000);
else
statusBar()->showMessage("Taste mit key-code "+str+" gedrückt", 1000);
}
};
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
CustomWindow main = CustomWindow();
main.resize(500,600);
main.show();
return app.exec();
}
Tastenanschläge können nun abgefangen werden. Doch welcher Tasten-Code gehört zu welcher Taste? Die leichteste Möglichkeit besteht darin, die Konstanten (wie etwa bei der Escape-Taste) zu verwenden. Beachten Sie, dass an dieser Stelle nicht zwischen Groß- und Kleinschreibung unterschieden wird. Häufig benutzte Tasten können sie der offiziellen Qt-Dokumentation entnehmen. Unter anderem gibt es:
Qt::Key_Escape
Qt::Key_Return
Qt::Key_Print
(Drucken-Taste)Qt::Key_Left
, Qt::Key_Right
, Qt::Key_Up
, Qt::Key_Down
(Pfeiltasten)Qt::Key_F1
, Qt::Key_F2
, Qt::Key_F...
, Qt::Key_F35
(Funktionstasten)Qt::Key_0
, Qt::Key_1
, Qt::Key_...
, Qt::Key_9
(Zifferntasten)Qt::Key_A
, Qt::Key_B
, Qt::Key_...
, Qt::Key_Z
Ganz analog zu den Tastenanschlägen kann auch die Maus abgefangen werden. Es ändert sich einzig die Form, in der das übergebene Event-Objekt event
vorliegt. Eine vollständige Liste aller Mouse-Buttons können sie wieder der offiziellen Qt-Dokumentation entnehmen.
#include <QtWidgets>
#include <iostream>
class CustomWindow : public QMainWindow {
public:
CustomWindow() : QMainWindow() {
setMouseTracking(true);
}
void mouseMoveEvent( QMouseEvent* event ) {
QString str = "Maus-Position (";
statusBar()->showMessage(str
+ QString::number(event->x()) + ","
+ QString::number(event->y())+")", 1000);
// wird nur bei Bewegung erkannt
// sonst mousePressEvent etc
if (event->buttons() & Qt::LeftButton)
statusBar()->showMessage(str
+ QString::number(event->x()) + ","
+ QString::number(event->y())
+ ") + Linke Maustaste", 1000);
}
};
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
CustomWindow main = CustomWindow();
main.resize(500,600);
main.show();
return app.exec();
}
Neben den Tastatur- und Mausevents gibt es noch einige mehr, was die Darstellung und Interaktion mit Widgets betrifft (z.B. resize Event
, leaveEvent
, ...). Entnehmen Sie die genauere Funktionsweise bitte der Dokumentation - im Moment sind diese für die Bearbeitung der Übungsaufgaben nicht nötig.
[1] Das stimmt nicht ganz, aber für den Moment nehmen wir das an.