Skalierung von Bildern
Zeigt, wie Bilder asynchron heruntergeladen und skaliert werden können.
Dieses Beispiel zeigt, wie man die Klassen QFuture, QPromise und QFutureWatcher verwendet, um eine Sammlung von Bildern aus dem Netzwerk herunterzuladen und zu skalieren, ohne die Benutzeroberfläche zu blockieren.
Die Anwendung besteht aus den folgenden Schritten:
- Herunterladen von Bildern aus der vom Benutzer angegebenen Liste von URLs.
- Skalieren der Bilder.
- Anzeige der skalierten Bilder in einem Rasterlayout.
Beginnen wir mit dem Herunterladen:
QFuture<QByteArray> Images::download(const QList<QUrl> &urls) {
Die Methode download()
nimmt eine Liste von URLs entgegen und gibt ein QFuture zurück. QFuture speichert die für jedes heruntergeladene Bild empfangenen Byte-Array-Daten. Um die Daten innerhalb des QFuture zu speichern, erstellen wir ein QPromise Objekt und melden, dass es gestartet wurde, um den Beginn des Downloads anzuzeigen:
QSharedPointer<QPromise<QByteArray>> promise(new QPromise<QByteArray>()); promise->start(); ... return promise->future(); }
Der mit dem Versprechen verbundene Future wird an den Aufrufer zurückgegeben.
Ohne jetzt ins Detail zu gehen, sei darauf hingewiesen, dass das promise-Objekt in ein QSharedPointer verpackt ist. Dies wird später erklärt.
Wir verwenden QNetworkAccessManager, um Netzwerkanfragen zu senden und Daten für jede URL herunterzuladen:
for (const auto &url : urls) { QSharedPointer<QNetworkReply> reply(qnam.get(QNetworkRequest(url))); replies.push_back(reply);
Und hier beginnt der interessante Teil:
QtFuture::connect(reply.get(), &QNetworkReply::finished).then([=] { if (promise->isCanceled()) { if (!promise->future().isFinished()) promise->finish(); return; } if (reply->error() != QNetworkReply::NoError) { if (!promise->future().isFinished()) throw reply->error(); } promise->addResult(reply->readAll()); // Report finished on the last download if (promise->future().resultCount() == urls.size()) promise->finish(); }).onFailed([promise] (QNetworkReply::NetworkError error) { promise->setException(std::make_exception_ptr(error)); promise->finish(); }).onFailed([promise] { const auto ex = std::make_exception_ptr( std::runtime_error("Unknown error occurred while downloading.")); promise->setException(ex); promise->finish(); }); } ...
Anstatt sich mit der Methode QObject::connect() mit den Signalen von QNetworkReply zu verbinden, verwenden wir QtFuture::connect(). Sie funktioniert ähnlich wie QObject::connect(), gibt aber ein QFuture Objekt zurück, das verfügbar wird, sobald das QNetworkReply::finished() Signal ausgegeben wird. Dies ermöglicht es uns, Fortsetzungen und Fehlerbehandlungsroutinen anzuhängen, wie es im Beispiel gemacht wird.
In der Fortsetzung, die über .then() angehängt wird, prüfen wir, ob der Benutzer angefordert hat, den Download abzubrechen. Wenn dies der Fall ist, wird die Bearbeitung der Anfrage abgebrochen. Durch den Aufruf der Methode QPromise::finish() teilen wir dem Benutzer mit, dass die Verarbeitung abgeschlossen ist. Falls die Netzwerkanforderung mit einem Fehler beendet wurde, wird eine Ausnahme ausgelöst. Die Ausnahme wird in der Fehlerbehandlungsroutine behandelt, die mit der Methode .onFailed() verbunden ist. Beachten Sie, dass wir zwei Fehlerbehandlungsroutinen haben: Die erste erfasst die Netzwerkfehler, die zweite alle anderen während der Ausführung ausgelösten Ausnahmen. Beide Handler speichern die Ausnahme im Promise-Objekt (das vom Aufrufer der Methode download()
behandelt wird) und melden, dass die Berechnung beendet ist. Beachten Sie auch, dass wir im Falle eines Fehlers der Einfachheit halber alle anstehenden Downloads unterbrechen.
Wenn die Anfrage nicht abgebrochen wurde und kein Fehler aufgetreten ist, lesen wir die Daten aus der Netzantwort und fügen sie der Liste der Ergebnisse des Versprechensobjekts hinzu:
... promise->addResult(reply->readAll()); // Report finished on the last download if (promise->future().resultCount() == urls.size()) promise->finish(); ...
Wenn die Anzahl der im Versprechensobjekt gespeicherten Ergebnisse gleich der Anzahl der herunterzuladenden url
ist, gibt es keine weiteren Anfragen mehr zu bearbeiten, so dass wir auch melden, dass das Versprechen beendet ist.
Wie bereits erwähnt, haben wir das Versprechen in ein QSharedPointer verpackt. Da das Versprechensobjekt von Handlern, die mit jeder Netzwerkantwort verbunden sind, gemeinsam genutzt wird, müssen wir das Versprechensobjekt an mehreren Stellen gleichzeitig kopieren und verwenden. Daher wird ein QSharedPointer verwendet.
Die Methode download()
wird von der Methode Images::process
aufgerufen. Sie wird aufgerufen, wenn der Benutzer auf die Schaltfläche "URLs hinzufügen" drückt:
... connect(addUrlsButton, &QPushButton::clicked, this, &Images::process); ...
Nachdem die eventuellen Reste des vorherigen Downloads gelöscht wurden, wird ein Dialogfeld erstellt, in dem der Benutzer die URLs für die herunterzuladenden Bilder angeben kann. Auf der Grundlage der angegebenen URL-Anzahl initialisieren wir das Layout, in dem die Bilder angezeigt werden, und starten den Download. Die von der Methode download()
zurückgegebene Zukunft wird gespeichert, so dass der Benutzer den Download bei Bedarf abbrechen kann:
void Images::process() { // Clean previous state replies.clear(); addUrlsButton->setEnabled(false); if (downloadDialog->exec() == QDialog::Accepted) { const auto urls = downloadDialog->getUrls(); if (urls.empty()) return; cancelButton->setEnabled(true); initLayout(urls.size()); downloadFuture = download(urls); statusBar->showMessage(tr("Downloading...")); ...
Als Nächstes fügen wir eine Fortsetzung hinzu, um den Schritt der Skalierung zu behandeln. Dazu später mehr:
downloadFuture .then([this](auto) { cancelButton->setEnabled(false); updateStatus(tr("Scaling...")); scalingWatcher.setFuture(QtConcurrent::run(Images::scaled, downloadFuture.results())); }) ...
Danach fügen wir die Handler onCanceled() und onFailed() hinzu:
.onCanceled([this] { updateStatus(tr("Download has been canceled.")); }) .onFailed([this](QNetworkReply::NetworkError error) { updateStatus(tr("Download finished with error: %1").arg(error)); // Abort all pending requests abortDownload(); }) .onFailed([this](const std::exception &ex) { updateStatus(tr(ex.what())); }) ...
Der Handler, der mit der Methode .onCanceled() verbunden ist, wird aufgerufen, wenn der Benutzer die Schaltfläche "Abbrechen" gedrückt hat:
... connect(cancelButton, &QPushButton::clicked, this, &Images::cancel); ...
Die Methode cancel()
bricht einfach alle anstehenden Anfragen ab:
void Images::cancel() { statusBar->showMessage(tr("Canceling...")); downloadFuture.cancel(); abortDownload(); }
Die über die Methode .onFailed() angehängten Handler werden aufgerufen, wenn bei einem der vorangegangenen Schritte ein Fehler aufgetreten ist. Wenn zum Beispiel während des Download-Schrittes ein Netzwerkfehler im Versprechen gespeichert wurde, wird dieser an den Handler weitergegeben, der QNetworkReply::NetworkError als Argument erhält.
Wenn downloadFuture
nicht abgebrochen wird und keinen Fehler gemeldet hat, wird die Skalierung fortgesetzt.
Da die Skalierung rechenintensiv sein kann und wir den Hauptthread nicht blockieren wollen, verwenden wir QtConcurrent::run(), um den Skalierungsschritt in einem neuen Thread zu starten.
scalingWatcher.setFuture(QtConcurrent::run(Images::scaled, downloadFuture.results()));
Da die Skalierung in einem separaten Thread gestartet wird, kann der Benutzer möglicherweise beschließen, die Anwendung zu schließen, während der Skalierungsvorgang ausgeführt wird. Um solche Situationen angemessen zu behandeln, übergeben wir die von QtConcurrent::run() zurückgegebene QFuture an die Instanz QFutureWatcher.
Das QFutureWatcher::finished Signal des Watchers ist mit dem Images::scaleFinished
Slot verbunden:
connect(&scalingWatcher, &QFutureWatcher<QList<QImage>>::finished, this, &Images::scaleFinished);
Dieser Slot ist verantwortlich für die Anzeige der skalierten Bilder in der Benutzeroberfläche und auch für die Behandlung von Fehlern, die während der Skalierung auftreten können:
void Images::scaleFinished() { const OptionalImages result = scalingWatcher.result(); if (result.has_value()) { const auto scaled = result.value(); showImages(scaled); updateStatus(tr("Finished")); } else { updateStatus(tr("Failed to extract image data.")); } addUrlsButton->setEnabled(true); }
Die Fehlermeldung wird durch die Rückgabe eines optionalen Wertes von der Methode Images::scaled()
implementiert:
Images::OptionalImages Images::scaled(const QList<QByteArray> &data) { QList<QImage> scaled; for (const auto &imgData : data) { QImage image; image.loadFromData(imgData); if (image.isNull()) return std::nullopt; scaled.push_back(image.scaled(100, 100, Qt::KeepAspectRatio)); } return scaled; }
Der Typ Images::OptionalImages
ist hier einfach ein Typedef für std::optional
:
using OptionalImages = std::optional<QList<QImage>>;
Hinweis: Wir können die Fehler der asynchronen Skalierungsoperation nicht mit dem .onFailed()-Handler behandeln, da der Handler im Kontext des Images
-Objekts im UI-Thread ausgeführt werden muss. Wenn der Benutzer die Anwendung schließt, während die asynchrone Berechnung durchgeführt wird, wird das Images
Objekt zerstört, und der Zugriff auf seine Mitglieder aus der Fortsetzung führt zu einem Absturz. Durch die Verwendung von QFutureWatcher und seinen Signalen lässt sich dieses Problem vermeiden, da die Signale bei der Zerstörung von QFutureWatcher getrennt werden, so dass die zugehörigen Slots niemals in einem zerstörten Kontext ausgeführt werden.
Der Rest des Codes ist einfach, Sie können das Beispielprojekt für weitere Details überprüfen.
Ausführen des Beispiels
Zum Ausführen des Beispiels von Qt Creatorzu starten, öffnen Sie den Modus Welcome und wählen Sie das Beispiel unter Examples aus. Weitere Informationen finden Sie unter Erstellen und Ausführen eines Beispiels.
© 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.