Scribble-Beispiel
Das Scribble-Beispiel zeigt, wie man einige der Event-Handler von QWidget neu implementiert, um die für die Widgets der Anwendung erzeugten Ereignisse zu empfangen.
Wir reimplementieren die Maus-Ereignishandler, um das Zeichnen zu implementieren, den Paint-Ereignishandler, um die Anwendung zu aktualisieren und den Resize-Ereignishandler, um das Aussehen der Anwendung zu optimieren. Außerdem wird der Close-Event-Handler neu implementiert, um die Close-Ereignisse abzufangen, bevor die Anwendung beendet wird.
Das Beispiel zeigt auch, wie man QPainter verwendet, um ein Bild in Echtzeit zu zeichnen und Widgets neu zu malen.
Mit der Scribble-Anwendung können die Benutzer ein Bild zeichnen. Das Menü File gibt den Benutzern die Möglichkeit, eine bestehende Bilddatei zu öffnen und zu bearbeiten, ein Bild zu speichern und die Anwendung zu beenden. Während des Zeichnens können die Benutzer über das Menü Options die Stiftfarbe und die Stiftbreite auswählen sowie den Bildschirm löschen. Außerdem bietet das Menü Help dem Benutzer Informationen über das Scribble-Beispiel im Besonderen und über Qt im Allgemeinen.
Das Beispiel besteht aus zwei Klassen:
ScribbleArea
ist ein benutzerdefiniertes Widget, das ein QImage anzeigt und dem Benutzer erlaubt, darauf zu zeichnen.MainWindow
stellt ein Menü über demScribbleArea
bereit.
Wir beginnen mit der Überprüfung der Klasse ScribbleArea
. Dann werden wir die Klasse MainWindow
betrachten, die ScribbleArea
verwendet.
Definition der Klasse ScribbleArea
class ScribbleArea : public QWidget { Q_OBJECT public: ScribbleArea(QWidget *parent = nullptr); bool openImage(const QString &fileName); bool saveImage(const QString &fileName, const char *fileFormat); void setPenColor(const QColor &newColor); void setPenWidth(int newWidth); bool isModified() const { return modified; } QColor penColor() const { return myPenColor; } int penWidth() const { return myPenWidth; } public slots: void clearImage(); void print(); protected: void mousePressEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; void paintEvent(QPaintEvent *event) override; void resizeEvent(QResizeEvent *event) override; private: void drawLineTo(const QPoint &endPoint); void resizeImage(QImage *image, const QSize &newSize); bool modified = false; bool scribbling = false; int myPenWidth = 1; QColor myPenColor = Qt::blue; QImage image; QPoint lastPoint; };
Die Klasse ScribbleArea
erbt von QWidget. Wir implementieren die Funktionen mousePressEvent()
, mouseMoveEvent()
und mouseReleaseEvent()
neu, um die Zeichnung zu implementieren. Wir reimplementieren die Funktion paintEvent()
, um den Scribble-Bereich zu aktualisieren, und die Funktion resizeEvent()
, um sicherzustellen, dass die QImage, auf der wir zeichnen, zu jedem Zeitpunkt mindestens so groß ist wie das Widget.
Wir benötigen mehrere öffentliche Funktionen: openImage()
lädt ein Bild aus einer Datei in den Scribble-Bereich und ermöglicht es dem Benutzer, das Bild zu bearbeiten; save()
schreibt das aktuell angezeigte Bild in eine Datei; clearImage()
löscht das im Scribble-Bereich angezeigte Bild. Wir benötigen die private Funktion drawLineTo()
, um das Zeichnen durchzuführen, und resizeImage()
, um die Größe eines QImage zu ändern. Der print()
-Slot kümmert sich um das Drucken.
Außerdem benötigen wir die folgenden privaten Variablen:
modified
istrue
wenn es ungespeicherte Änderungen an dem im Scribble-Bereich angezeigten Bild gibt.scribbling
istrue
wenn der Benutzer die linke Maustaste innerhalb des Scribble-Bereichs drückt.penWidth
undpenColor
enthalten die aktuell eingestellte Breite und Farbe für den in der Anwendung verwendeten Stift.image
speichert das vom Benutzer gezeichnete Bild.lastPoint
speichert die Position des Cursors beim letzten Mausklick oder Mausbewegung.
Implementierung der Klasse ScribbleArea
ScribbleArea::ScribbleArea(QWidget *parent) : QWidget(parent) { setAttribute(Qt::WA_StaticContents); }
Im Konstruktor setzen wir das Qt::WA_StaticContents -Attribut für das Widget, das angibt, dass der Inhalt des Widgets in der linken oberen Ecke verwurzelt ist und sich nicht ändert, wenn die Größe des Widgets geändert wird. Qt verwendet dieses Attribut, um Paint-Events bei Größenänderungen zu optimieren. Dies ist eine reine Optimierung und sollte nur für Widgets verwendet werden, deren Inhalt statisch ist und in der linken oberen Ecke verankert ist.
bool ScribbleArea::openImage(const QString &fileName) { QImage loadedImage; if (!loadedImage.load(fileName)) return false; QSize newSize = loadedImage.size().expandedTo(size()); resizeImage(&loadedImage, newSize); image = loadedImage; modified = false; update(); return true; }
In der Funktion openImage()
laden wir das angegebene Bild. Dann ändern wir die Größe des geladenen QImage so, dass es in beiden Richtungen mindestens so groß wie das Widget ist, indem wir die private Funktion resizeImage()
verwenden und die Mitgliedsvariable image
auf das geladene Bild setzen. Am Ende rufen wir QWidget::update() auf, um ein neues Bild zu erstellen.
bool ScribbleArea::saveImage(const QString &fileName, const char *fileFormat) { QImage visibleImage = image; resizeImage(&visibleImage, size()); if (visibleImage.save(fileName, fileFormat)) { modified = false; return true; } return false; }
Die Funktion saveImage()
erstellt ein QImage Objekt, das nur den sichtbaren Teil des aktuellen image
abdeckt, und speichert es mit QImage::save(). Wenn das Bild erfolgreich gespeichert wurde, setzen wir die Variable modified
des Scribble-Bereichs auf false
, da es keine ungesicherten Daten gibt.
void ScribbleArea::setPenColor(const QColor &newColor) { myPenColor = newColor; } void ScribbleArea::setPenWidth(int newWidth) { myPenWidth = newWidth; }
Die Funktionen setPenColor()
und setPenWidth()
setzen die aktuelle Stiftfarbe und -breite. Diese Werte werden für zukünftige Zeichenoperationen verwendet.
void ScribbleArea::clearImage() { image.fill(qRgb(255, 255, 255)); modified = true; update(); }
Der öffentliche Slot clearImage()
löscht das im Scribble-Bereich angezeigte Bild. Wir füllen einfach das gesamte Bild mit Weiß, was dem RGB-Wert (255, 255, 255) entspricht. Wie üblich, wenn wir das Bild ändern, setzen wir modified
auf true
und planen ein neues Bild.
void ScribbleArea::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) { lastPoint = event->position().toPoint(); scribbling = true; } } void ScribbleArea::mouseMoveEvent(QMouseEvent *event) { if ((event->buttons() & Qt::LeftButton) && scribbling) drawLineTo(event->position().toPoint()); } void ScribbleArea::mouseReleaseEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton && scribbling) { drawLineTo(event->position().toPoint()); scribbling = false; } }
Für Ereignisse beim Drücken und Loslassen der Maus verwenden wir die Funktion QMouseEvent::button(), um herauszufinden, welche Taste das Ereignis ausgelöst hat. Für Mausbewegungsereignisse verwenden wir QMouseEvent::buttons(), um herauszufinden, welche Tasten gerade gedrückt sind (als ODER-Kombination).
Wenn der Benutzer die linke Maustaste drückt, speichern wir die Position des Mauszeigers in lastPoint
. Wir vermerken auch, dass der Benutzer gerade kritzelt. (Die Variable scribbling
ist notwendig, weil wir nicht davon ausgehen können, dass einem Mausbewegungs- und Mausfreigabeereignis immer ein Mausdruckereignis auf demselben Widget vorausgeht.)
Wenn der Benutzer die Maus mit gedrückter linker Taste bewegt oder die Taste loslässt, rufen wir die private Funktion drawLineTo()
auf, um zu zeichnen.
void ScribbleArea::paintEvent(QPaintEvent *event) { QPainter painter(this); QRect dirtyRect = event->rect(); painter.drawImage(dirtyRect, image, dirtyRect); }
In der Neuimplementierung der Funktion paintEvent() erstellen wir einfach eine QPainter für den Scribble-Bereich und zeichnen das Bild.
An dieser Stelle werden Sie sich vielleicht fragen, warum wir nicht einfach direkt auf das Widget zeichnen, anstatt in einem QImage zu zeichnen und das QImage auf den Bildschirm in paintEvent()
zu kopieren. Hierfür gibt es mindestens drei gute Gründe:
- Das Fenstersystem erfordert, dass wir in der Lage sind, das Widget jederzeit neu zu zeichnen. Wenn zum Beispiel das Fenster minimiert und wiederhergestellt wird, könnte das Fenstersystem den Inhalt des Widgets vergessen haben und uns ein Malereignis senden. Mit anderen Worten, wir können uns nicht darauf verlassen, dass das Fenstersystem sich unser Bild merkt.
- Qt erlaubt es uns normalerweise nicht, außerhalb von
paintEvent()
zu malen. Insbesondere können wir nicht über die Maus-Ereignishandler malen. (Dieses Verhalten kann jedoch mit dem Qt::WA_PaintOnScreen Widget-Attribut geändert werden). - Wenn QImage richtig initialisiert ist, verwendet es garantiert 8 Bit für jeden Farbkanal (Rot, Grün, Blau und Alpha), während QWidget je nach Monitorkonfiguration eine geringere Farbtiefe hat. Wenn wir also ein 24-Bit- oder 32-Bit-Bild laden und es auf ein QWidget malen und dann das QWidget wieder in ein QImage kopieren, könnten wir einige Informationen verlieren.
void ScribbleArea::resizeEvent(QResizeEvent *event) { if (width() > image.width() || height() > image.height()) { int newWidth = qMax(width() + 128, image.width()); int newHeight = qMax(height() + 128, image.height()); resizeImage(&image, QSize(newWidth, newHeight)); update(); } QWidget::resizeEvent(event); }
Wenn der Benutzer die Scribble-Anwendung startet, wird ein Größenänderungsereignis erzeugt und ein Bild erstellt und im Scribble-Bereich angezeigt. Wir machen dieses anfängliche Bild etwas größer als das Hauptfenster und den Scribble-Bereich der Anwendung, um zu vermeiden, dass die Größe des Bildes immer geändert wird, wenn der Benutzer die Größe des Hauptfensters ändert (was sehr ineffizient wäre). Wenn das Hauptfenster jedoch größer wird als diese anfängliche Größe, muss das Bild in der Größe angepasst werden.
void ScribbleArea::drawLineTo(const QPoint &endPoint) { QPainter painter(&image); painter.setPen(QPen(myPenColor, myPenWidth, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); painter.drawLine(lastPoint, endPoint); modified = true; int rad = (myPenWidth / 2) + 2; update(QRect(lastPoint, endPoint).normalized() .adjusted(-rad, -rad, +rad, +rad)); lastPoint = endPoint; }
In drawLineTo()
zeichnen wir eine Linie von dem Punkt aus, an dem sich die Maus beim letzten Mausklick oder der letzten Mausbewegung befand, setzen modified
auf true, erzeugen ein Repaint-Ereignis und aktualisieren lastPoint
, so dass wir beim nächsten Aufruf von drawLineTo()
dort weitermalen, wo wir aufgehört haben.
Wir könnten die Funktion update()
ohne Parameter aufrufen, aber als einfache Optimierung übergeben wir QRect, das das Rechteck innerhalb des Scribbles angibt, das aktualisiert werden muss, um eine komplette Neuzeichnung des Widgets zu vermeiden.
void ScribbleArea::resizeImage(QImage *image, const QSize &newSize) { if (image->size() == newSize) return; QImage newImage(newSize, QImage::Format_RGB32); newImage.fill(qRgb(255, 255, 255)); QPainter painter(&newImage); painter.drawImage(QPoint(0, 0), *image); *image = newImage; }
QImage hat keine schöne API für die Größenänderung eines Bildes. Es gibt eine QImage::copy()-Funktion, die den Trick machen könnte, aber wenn sie verwendet wird, um ein Bild zu erweitern, füllt sie die neuen Bereiche mit Schwarz, während wir Weiß wollen.
Der Trick besteht also darin, ein brandneues QImage mit der richtigen Größe zu erstellen, es mit Weiß zu füllen und das alte Bild mit QPainter darauf zu zeichnen. Das neue Bild erhält das Format QImage::Format_RGB32, was bedeutet, dass jedes Pixel als 0xffRRGGBB gespeichert wird (wobei RR, GG und BB die roten, grünen und blauen Farbkanäle sind, ff ist der hexadezimale Wert 255).
Das Drucken wird über den print()
Slot abgewickelt:
void ScribbleArea::print() { #if defined(QT_PRINTSUPPORT_LIB) && QT_CONFIG(printdialog) QPrinter printer(QPrinter::HighResolution); QPrintDialog printDialog(&printer, this);
Wir konstruieren ein hochauflösendes QPrinter-Objekt für das gewünschte Ausgabeformat und verwenden einen QPrintDialog, um den Benutzer aufzufordern, eine Seitengröße anzugeben und anzugeben, wie die Ausgabe auf der Seite formatiert werden soll.
Wenn der Dialog akzeptiert wird, wird der Druck auf dem Malgerät ausgeführt:
if (printDialog.exec() == QDialog::Accepted) { QPainter painter(&printer); QRect rect = painter.viewport(); QSize size = image.size(); size.scale(rect.size(), Qt::KeepAspectRatio); painter.setViewport(rect.x(), rect.y(), size.width(), size.height()); painter.setWindow(image.rect()); painter.drawImage(0, 0, image); } #endif // QT_CONFIG(printdialog) }
Das Drucken eines Bildes in eine Datei auf diese Weise ist einfach eine Sache des Malens auf den QPrinter. Wir skalieren das Bild so, dass es in den verfügbaren Platz auf der Seite passt, bevor wir es auf das Malgerät malen.
Definition der Klasse MainWindow
class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); protected: void closeEvent(QCloseEvent *event) override; private slots: void open(); void save(); void penColor(); void penWidth(); void about(); private: void createActions(); void createMenus(); bool maybeSave(); bool saveFile(const QByteArray &fileFormat); ScribbleArea *scribbleArea; QMenu *saveAsMenu; QMenu *fileMenu; QMenu *optionMenu; QMenu *helpMenu; QAction *openAct; QList<QAction *> saveAsActs; QAction *exitAct; QAction *penColorAct; QAction *penWidthAct; QAction *printAct; QAction *clearScreenAct; QAction *aboutAct; QAction *aboutQtAct; };
Die Klasse MainWindow
erbt von QMainWindow. Wir reimplementieren den Handler closeEvent() von QWidget. Die Slots open()
, save()
, penColor()
und penWidth()
entsprechen den Menüeinträgen. Darüber hinaus erstellen wir vier private Funktionen.
Mit der Funktion boolean maybeSave()
prüfen wir, ob es ungespeicherte Änderungen gibt. Wenn es ungespeicherte Änderungen gibt, geben wir dem Benutzer die Möglichkeit, diese Änderungen zu speichern. Die Funktion gibt false
zurück, wenn der Benutzer auf Cancel klickt. Wir verwenden die Funktion saveFile()
, um dem Benutzer die Möglichkeit zu geben, das aktuell im Scribble-Bereich angezeigte Bild zu speichern.
Implementierung der MainWindow-Klasse
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), scribbleArea(new ScribbleArea(this)) { setCentralWidget(scribbleArea); createActions(); createMenus(); setWindowTitle(tr("Scribble")); resize(500, 500); }
Im Konstruktor erstellen wir einen Scribble-Bereich, den wir zum zentralen Widget des Widgets MainWindow
machen. Dann erstellen wir die zugehörigen Aktionen und Menüs.
void MainWindow::closeEvent(QCloseEvent *event) { if (maybeSave()) event->accept(); else event->ignore(); }
Schließereignisse werden an Widgets gesendet, die der Benutzer schließen möchte, normalerweise durch Klicken auf File|Exit oder durch Klicken auf die Schaltfläche in der Titelleiste X. Durch die Neuimplementierung des Ereignis-Handlers können wir Versuche, die Anwendung zu schließen, abfangen.
In diesem Beispiel verwenden wir das Schließereignis, um den Benutzer aufzufordern, alle nicht gespeicherten Änderungen zu speichern. Die Logik dafür befindet sich in der Funktion maybeSave()
. Wenn maybeSave()
true zurückgibt, gibt es keine Änderungen oder der Benutzer hat sie erfolgreich gespeichert, und wir akzeptieren das Ereignis. Die Anwendung kann dann normal beendet werden. Wenn maybeSave()
false zurückgibt, hat der Benutzer auf Cancel geklickt, so dass wir das Ereignis "ignorieren" und die Anwendung davon unbeeinflusst bleibt.
void MainWindow::open() { if (maybeSave()) { QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"), QDir::currentPath()); if (!fileName.isEmpty()) scribbleArea->openImage(fileName); } }
Im Slot open()
geben wir dem Benutzer zunächst die Möglichkeit, etwaige Änderungen am aktuell angezeigten Bild zu speichern, bevor ein neues Bild in den Scribble-Bereich geladen wird. Dann wird der Benutzer aufgefordert, eine Datei auszuwählen, und die Datei wird in den ScribbleArea
geladen.
void MainWindow::save() { QAction *action = qobject_cast<QAction *>(sender()); QByteArray fileFormat = action->data().toByteArray(); saveFile(fileFormat); }
Der Slot save()
wird aufgerufen, wenn der Benutzer den Menüeintrag Save As wählt und dann einen Eintrag aus dem Formatmenü auswählt. Als erstes müssen wir mit QObject::sender() herausfinden, welche Aktion das Signal gesendet hat. Diese Funktion gibt den Absender als Zeiger QObject zurück. Da wir wissen, dass der Absender ein Aktionsobjekt ist, können wir QObject sicher casten. Wir hätten einen Cast im Stil von C oder C++ static_cast<>()
verwenden können, aber als defensive Programmiertechnik benutzen wir qobject_cast(). Der Vorteil ist, dass ein Null-Zeiger zurückgegeben wird, wenn das Objekt den falschen Typ hat. Abstürze aufgrund von Null-Zeigern sind viel einfacher zu diagnostizieren als Abstürze aufgrund von unsicheren Casts.
Sobald wir die Aktion haben, extrahieren wir das gewählte Format mit QAction::data(). (Wenn die Aktionen erstellt werden, verwenden wir QAction::setData(), um unsere eigenen benutzerdefinierten Daten zu setzen, die der Aktion als QVariant angehängt sind. Mehr dazu, wenn wir createActions()
besprechen).
Da wir nun das Format kennen, rufen wir die private Funktion saveFile()
auf, um das aktuell angezeigte Bild zu speichern.
void MainWindow::penColor() { QColor newColor = QColorDialog::getColor(scribbleArea->penColor()); if (newColor.isValid()) scribbleArea->setPenColor(newColor); }
Wir verwenden den penColor()
Slot, um eine neue Farbe vom Benutzer mit einem QColorDialog abzurufen. Wenn der Benutzer eine neue Farbe auswählt, machen wir sie zur Farbe des Scribble-Bereichs.
void MainWindow::penWidth() { bool ok; int newWidth = QInputDialog::getInt(this, tr("Scribble"), tr("Select pen width:"), scribbleArea->penWidth(), 1, 50, 1, &ok); if (ok) scribbleArea->setPenWidth(newWidth); }
Um eine neue Stiftbreite im Slot penWidth()
abzurufen, verwenden wir QInputDialog. Die Klasse QInputDialog bietet einen einfachen Komfortdialog, um einen einzelnen Wert vom Benutzer zu erhalten. Wir verwenden die statische Funktion QInputDialog::getInt(), die eine QLabel und eine QSpinBox kombiniert. Die QSpinBox wird mit der Stiftbreite des Scribble-Bereichs initialisiert, erlaubt einen Bereich von 1 bis 50, einen Schritt von 1 (was bedeutet, dass die Auf- und Abwärtspfeile den Wert um 1 erhöhen oder verringern).
Die boolesche Variable ok
wird auf true
gesetzt, wenn der Benutzer auf OK geklickt hat, und auf false
, wenn der Benutzer Cancel gedrückt hat.
void MainWindow::about() { QMessageBox::about(this, tr("About Scribble"), tr("<p>The <b>Scribble</b> example shows how to use QMainWindow as the " "base widget for an application, and how to reimplement some of " "QWidget's event handlers to receive the events generated for " "the application's widgets:</p><p> We reimplement the mouse event " "handlers to facilitate drawing, the paint event handler to " "update the application and the resize event handler to optimize " "the application's appearance. In addition we reimplement the " "close event handler to intercept the close events before " "terminating the application.</p><p> The example also demonstrates " "how to use QPainter to draw an image in real time, as well as " "to repaint widgets.</p>")); }
Wir implementieren den Slot about()
, um ein Nachrichtenfeld zu erstellen, das beschreibt, was das Beispiel zeigen soll.
void MainWindow::createActions() { openAct = new QAction(tr("&Open..."), this); openAct->setShortcuts(QKeySequence::Open); connect(openAct, &QAction::triggered, this, &MainWindow::open); const QList<QByteArray> imageFormats = QImageWriter::supportedImageFormats(); for (const QByteArray &format : imageFormats) { QString text = tr("%1...").arg(QString::fromLatin1(format).toUpper()); QAction *action = new QAction(text, this); action->setData(format); connect(action, &QAction::triggered, this, &MainWindow::save); saveAsActs.append(action); } printAct = new QAction(tr("&Print..."), this); connect(printAct, &QAction::triggered, scribbleArea, &ScribbleArea::print); exitAct = new QAction(tr("E&xit"), this); exitAct->setShortcuts(QKeySequence::Quit); connect(exitAct, &QAction::triggered, this, &MainWindow::close); penColorAct = new QAction(tr("&Pen Color..."), this); connect(penColorAct, &QAction::triggered, this, &MainWindow::penColor); penWidthAct = new QAction(tr("Pen &Width..."), this); connect(penWidthAct, &QAction::triggered, this, &MainWindow::penWidth); clearScreenAct = new QAction(tr("&Clear Screen"), this); clearScreenAct->setShortcut(tr("Ctrl+L")); connect(clearScreenAct, &QAction::triggered, scribbleArea, &ScribbleArea::clearImage); aboutAct = new QAction(tr("&About"), this); connect(aboutAct, &QAction::triggered, this, &MainWindow::about); aboutQtAct = new QAction(tr("About &Qt"), this); connect(aboutQtAct, &QAction::triggered, qApp, &QApplication::aboutQt); }
In der Funktion createAction()
erstellen wir die Aktionen, die die Menüeinträge darstellen, und verbinden sie mit den entsprechenden Slots. Insbesondere erstellen wir die Aktionen, die im Untermenü Save As zu finden sind. Wir verwenden QImageWriter::supportedImageFormats(), um eine Liste der unterstützten Formate zu erhalten (als QList<QByteArray>).
Dann gehen wir die Liste durch und erstellen eine Aktion für jedes Format. Wir rufen QAction::setData() mit dem Dateiformat auf, damit wir es später mit QAction::data() abrufen können. Wir hätten das Dateiformat auch aus dem Text der Aktion ableiten können, indem wir das "..." abgeschnitten hätten, aber das wäre zu unelegant gewesen.
void MainWindow::createMenus() { saveAsMenu = new QMenu(tr("&Save As"), this); for (QAction *action : std::as_const(saveAsActs)) saveAsMenu->addAction(action); fileMenu = new QMenu(tr("&File"), this); fileMenu->addAction(openAct); fileMenu->addMenu(saveAsMenu); fileMenu->addAction(printAct); fileMenu->addSeparator(); fileMenu->addAction(exitAct); optionMenu = new QMenu(tr("&Options"), this); optionMenu->addAction(penColorAct); optionMenu->addAction(penWidthAct); optionMenu->addSeparator(); optionMenu->addAction(clearScreenAct); helpMenu = new QMenu(tr("&Help"), this); helpMenu->addAction(aboutAct); helpMenu->addAction(aboutQtAct); menuBar()->addMenu(fileMenu); menuBar()->addMenu(optionMenu); menuBar()->addMenu(helpMenu); }
In der Funktion createMenu()
fügen wir die zuvor erstellten Format-Aktionen zu saveAsMenu
hinzu. Dann fügen wir die restlichen Aktionen sowie das Untermenü saveAsMenu
zu den Menüs File, Options und Help hinzu.
Die Klasse QMenu bietet ein Menü-Widget für die Verwendung in Menüleisten, Kontextmenüs und anderen Popup-Menüs. Die Klasse QMenuBar bietet eine horizontale Menüleiste mit einer Liste von Pulldown-Menüs QMenu. Am Ende werden die Menüs File und Options in die Menüleiste von MainWindow
eingefügt, die wir mit der Funktion QMainWindow::menuBar() abrufen.
bool MainWindow::maybeSave() { if (scribbleArea->isModified()) { QMessageBox::StandardButton ret; ret = QMessageBox::warning(this, tr("Scribble"), tr("The image has been modified.\n" "Do you want to save your changes?"), QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel); if (ret == QMessageBox::Save) return saveFile("png"); else if (ret == QMessageBox::Cancel) return false; } return true; }
In mayBeSave()
prüfen wir, ob es ungespeicherte Änderungen gibt. Falls dies der Fall ist, verwenden wir QMessageBox, um dem Benutzer eine Warnung zu geben, dass das Bild geändert wurde, und ihm die Möglichkeit zu geben, die Änderungen zu speichern.
Wie bei QColorDialog und QFileDialog ist es am einfachsten, eine QMessageBox zu erstellen, indem man ihre statischen Funktionen verwendet. QMessageBox bietet eine Reihe verschiedener Meldungen, die entlang zweier Achsen angeordnet sind: Schweregrad (Frage, Information, Warnung und kritisch) und Komplexität (die Anzahl der erforderlichen Antwortschaltflächen). In diesem Fall verwenden wir die Funktion warning()
, da die Meldung ziemlich wichtig ist.
Wenn sich der Benutzer für das Speichern entscheidet, rufen wir die private Funktion saveFile()
auf. Der Einfachheit halber verwenden wir PNG als Dateiformat; der Benutzer kann jederzeit Cancel drücken und die Datei in einem anderen Format speichern.
Die Funktion maybeSave()
gibt false
zurück, wenn der Benutzer auf Cancel klickt; andernfalls gibt sie true
zurück.
bool MainWindow::saveFile(const QByteArray &fileFormat) { QString initialPath = QDir::currentPath() + "/untitled." + fileFormat; QString fileName = QFileDialog::getSaveFileName(this, tr("Save As"), initialPath, tr("%1 Files (*.%2);;All Files (*)") .arg(QString::fromLatin1(fileFormat.toUpper())) .arg(QString::fromLatin1(fileFormat))); if (fileName.isEmpty()) return false; return scribbleArea->saveImage(fileName, fileFormat.constData()); }
In saveFile()
wird ein Dateidialog mit einem Vorschlag für den Dateinamen eingeblendet. Die statische Funktion QFileDialog::getSaveFileName() gibt einen vom Benutzer ausgewählten Dateinamen zurück. Die Datei muss nicht existieren.
© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.