Files:
The Image Loader example demonstrates how to offload work intensive tasks into seperate work threads, and than report show their results asynchronously.

In this example we'll learn how to use QNetworkAccessManager and QThread to download an image file asynchronously from the network and then offload the time-consuming task of converting the raw data into a QImage and scaling it down to a proper size into a separated worker thread.
The actual code for downloading and processing the images is encapsulated inside the ImageLoader class. For each image we'll have one instance of this class and all these instances are stored inside a QListDataModel to make them easily accessible to the UI.
When the application is started, a Button is shown at the center of the screen. If the user clicks on it, the Button is hidden and a ListView is shown instead which covers the complete screen. Additionally the loadImages() method is invoked on the App object, which has been exported to QML under the name '_app'.
// The button to start the loading of the images Button { horizontalAlignment: HorizontalAlignment.Center verticalAlignment: VerticalAlignment.Center text: qsTr("Load images") onClicked: { _app.loadImages() visible = false listView.visible = true } }
The 'model' property of the App object is bound against the 'dataModel' property of the ListView, so each ImageLoader from the model is represented by one item in the ListView.
// The ListView that shows the progress of loading and result images ListView { id: listView horizontalAlignment: HorizontalAlignment.Fill verticalAlignment: VerticalAlignment.Fill visible: false dataModel: _app.model
Since we want to show an ActivityIndicator while the image is downloaded and processed, an ImageView when the image is finally available or a Label that shows an error message if any occurs, we have to implement our own ListItemComponent.
We simply put the three controls into a Container and change their 'visible' property depending on the 'loading' and 'label' property of the ImageLoader.
listItemComponents: ListItemComponent { type: "" Container { preferredHeight: 500 preferredWidth: 768 layout: DockLayout {} // The ActivityIndicator that is only active and visible while the image is loading ActivityIndicator { horizontalAlignment: HorizontalAlignment.Center verticalAlignment: VerticalAlignment.Center preferredHeight: 300 visible: ListItemData.loading running: ListItemData.loading } // The ImageView that shows the loaded image after loading has finished without error ImageView { horizontalAlignment: HorizontalAlignment.Fill verticalAlignment: VerticalAlignment.Fill image: ListItemData.image visible: !ListItemData.loading && ListItemData.label == "" } // The Label that shows a possible error message after loading has finished Label { horizontalAlignment: HorizontalAlignment.Center verticalAlignment: VerticalAlignment.Center preferredWidth: 500 visible: !ListItemData.loading && !ListItemData.label == "" text: ListItemData.label multiline: true } } }
The App class is the central class in this application which loads the UI and provides the interaction between C++ and QML scope throught the 'model' property and the invokable loadImages() method.
class App : public QObject { Q_OBJECT // The model that contains the progress and image data Q_PROPERTY(bb::cascades::DataModel* model READ model CONSTANT) public: App(QObject *parent = 0); // This method is called to start the loading of all images. Q_INVOKABLE void loadImages(); private: // The accessor method for the property bb::cascades::DataModel* model() const; // The model that contains the progress and image data bb::cascades::QListDataModel<QObject*>* m_model; };
Inside the constructor the model is created and filled with 10 ImageLoader objects, each responsible to load a different image from wikimedia.org. Since we want to access the properties (e.g. 'loading' and 'label') of the ImageLoader objects in QML, we also have to register this type to QML.
At the end we load the UI from the main.qml file.
App::App(QObject *parent) : QObject(parent) , m_model(new QListDataModel<QObject*>()) { // Register custom type to QML qmlRegisterType<ImageLoader>(); m_model->setParent(this); // Fill the model with data m_model->append(new ImageLoader("http://upload.wikimedia.org/wikipedia/commons/6/62/Peace_riding_in_a_triumphal_chariot_Bosio_Carrousel_-_2012-05-28.jpg", this)); m_model->append(new ImageLoader("http://upload.wikimedia.org/wikipedia/commons/a/af/Crepuscular_rays_with_reflection_in_GGP.jpg", this)); m_model->append(new ImageLoader("http://upload.wikimedia.org/wikipedia/commons/2/2a/Anodorhynchus_hyacinthinus_-Hyacinth_Macaw_-side_of_head.jpg", this)); m_model->append(new ImageLoader("http://upload.wikimedia.org/wikipedia/commons/2/29/Bataille_Waterloo_1815_reconstitution_2011_cuirassier.jpg", this)); m_model->append(new ImageLoader("http://upload.wikimedia.org/wikipedia/commons/e/ec/Armadillo_Aerospace_Pixel_Hover.jpg", this)); m_model->append(new ImageLoader("http://upload.wikimedia.org/wikipedia/commons/f/f5/A_sculpture_at_the_entrance_to_the_palace_of_Versailles.jpg", this)); m_model->append(new ImageLoader("http://upload.wikimedia.org/wikipedia/commons/6/6d/Firehole_river_at_Upper_Geyser_Basin-2008-june.jpg", this)); m_model->append(new ImageLoader("http://upload.wikimedia.org/wikipedia/commons/7/7c/Peugeot_206_WRC.jpg", this)); m_model->append(new ImageLoader("http://upload.wikimedia.org/wikipedia/commons/9/97/Toda_Hut.JPG", this)); m_model->append(new ImageLoader("http://upload.wikimedia.org/wikipedia/commons/d/dc/Marriott_Center_1.JPG", this)); // Create the UI QmlDocument* qml = QmlDocument::create("asset:///main.qml").parent(this); qml->setContextProperty("_app", this); AbstractPane *root = qml->createRootObject<AbstractPane>(); Application::instance()->setScene(root); }
When the user clicks on the 'Load images' button in the UI, the loadImages() method is invoked. Inside this method we simply iterate over the ImageLoader objects inside the model and call their load() method to trigger the download of the image from the network.
void App::loadImages() { // Call the load() method for each ImageLoader instance inside the model for (int row = 0; row < m_model->size(); ++row) { qobject_cast<ImageLoader*>(m_model->value(row))->load(); } }
The ImageLoader class encapsulates the loading and processing of the images. The current state is made available to the UI through the 'loading' property, any error message through 'label' and the final image data through the 'image' property.
To download the image the QNetworkAccessManager class is used and afterwards the raw image data are converted and scaled inside a thread. Thread contexts are represented by the QThread class.
class ImageLoader : public QObject { Q_OBJECT Q_PROPERTY(QVariant image READ image NOTIFY imageChanged) Q_PROPERTY(QString label READ label NOTIFY labelChanged) Q_PROPERTY(bool loading READ loading NOTIFY loadingChanged) public: /* * Creates a new image loader. * * @param imageUrl The url to load the image from. */ ImageLoader(const QString &imageUrl, QObject* parent = 0); /* * Destroys the image loader. */ ~ImageLoader(); /* * Loads the image. */ void load(); Q_SIGNALS: // The change notification signals of the properties void imageChanged(); void labelChanged(); void loadingChanged(); private Q_SLOTS: /* * Response handler for the network operation. */ void onReplyFinished(); /* * Response handler for the image process operation. */ void onImageProcessingFinished(const QImage &image); private: // The accessor methods of the properties QVariant image() const; QString label() const; bool loading() const; // The property values bb::cascades::Image m_image; QString m_label; bool m_loading; // The URL of the image that should be loaded QString m_imageUrl; // The thread context that processes the image QPointer<QThread> m_thread; };
Inside the constructor only the member variables are initialized.
ImageLoader::ImageLoader(const QString &imageUrl, QObject* parent) : QObject(parent) , m_loading(false) , m_imageUrl(imageUrl) { }
Inside the destructor we check whether the worker thread still exists. If that's the case, we wait for it to finish execution.
ImageLoader::~ImageLoader() { if (m_thread) m_thread->wait(); }
The load() method is called to start the download operation. Inside this method we first change the value of the 'loading' property to signal that we have started our work. Afterwards the QNetworkAccessManager object is created and a GET request with the image URL is issued. We connect the finished() signal of the returned QNetworkReply to our custom onReplyFinished() slot, so that we get informed when the download is done.
void ImageLoader::load() { m_loading = true; emit loadingChanged(); QNetworkAccessManager* netManager = new QNetworkAccessManager(this); const QUrl url(m_imageUrl); QNetworkRequest request(url); QNetworkReply* reply = netManager->get(request); connect(reply, SIGNAL(finished()), this, SLOT(onReplyFinished())); }
Inside the onReplyFinished() slot we check whether an error occurred. If that's the case we fill the 'label' property accordingly and signal that we have finished loading.
If the download was successful, we read the raw image data from the QNetworkReply and store them temporarily. In the next step a new ImageProcessor object is created. This one will convert the raw image data into a QImage object and scale it down to a proper size. However since this operation can take a long time, we want to offload it into the worker thread.
So we create a new QThread instance and move the ImageProcessor object to the worker thread. Moving a QObject based object to a QThread means that the object is associated with this thread and further slot invocations will be executed inside this thread (see http://qt-project.org/doc/qt-4.8/threads-qobject.html)
The QThread object will emit the started() signal once the thread context is set up. We connect this signal against the start() slot of the ImageProcessor object, so that this code will be executed inside the thread context. When the QThread has finished the execution of the thread context, it emits the finished() signal. We connect it against the QThread's own deleteLater() slot, so that it is deleted automatically.
When the ImageProcessor has finished its work, it emits the finished(QImage) signal. We connect this signal against two slots. Once against our custom onImageProcessingFinished() slot, where we use the converted and scaled image, and once against the quit() slot of the QThread to trigger its termination.
In the last step we trigger the start of the QThread object.
void ImageLoader::onReplyFinished() { QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender()); QString response; if (reply) { if (reply->error() == QNetworkReply::NoError) { const int available = reply->bytesAvailable(); if (available > 0) { const QByteArray data(reply->readAll()); // Setup the image processing thread ImageProcessor *imageProcessor = new ImageProcessor(data); m_thread = new QThread(this); // Move the image processor to the worker thread imageProcessor->moveToThread(m_thread); // Invoke ImageProcessor's start() slot as soon as the worker thread has started connect(m_thread, SIGNAL(started()), imageProcessor, SLOT(start())); // Delete the worker thread automatically after it has finished connect(m_thread, SIGNAL(finished()), m_thread, SLOT(deleteLater())); /* * Invoke our onProcessingFinished slot after the processing has finished. * Since imageProcessor and 'this' are located in different threads we use 'QueuedConnection' to * allow a cross-thread boundary invocation. In this case the QImage parameter is copied in a thread-safe way * from the worker thread to the main thread. */ connect(imageProcessor, SIGNAL(finished(QImage)), this, SLOT(onImageProcessingFinished(QImage)), Qt::QueuedConnection); // Terminate the thread after the processing has finished connect(imageProcessor, SIGNAL(finished(QImage)), m_thread, SLOT(quit())); m_thread->start(); } } else { m_label = tr("Error: %1 status: %2").arg(reply->errorString(), reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toString()); emit labelChanged(); m_loading = false; emit loadingChanged(); } reply->deleteLater(); } else { m_label = tr("Download failed. Check internet connection"); emit labelChanged(); m_loading = false; emit loadingChanged(); } }
Inside the onImageProcessingFinished() slot, we convert the QImage into a bb::Image object, since the ImageView in the UI can only use the latter as input. The bb::Image object is used as value for the 'image' property.
Furthermore we clear the content of the 'label' property and change the 'loading' property to 'false' to signal that we have finished the operation.
void ImageLoader::onImageProcessingFinished(const QImage &image) { const QImage swappedImage = image.rgbSwapped(); const bb::ImageData imageData = bb::ImageData::fromPixels(swappedImage.bits(), bb::PixelFormat::RGBX, swappedImage.width(), swappedImage.height(), swappedImage.bytesPerLine()); m_image = bb::cascades::Image(imageData); emit imageChanged(); m_label.clear(); emit labelChanged(); m_loading = false; emit loadingChanged(); }
The ImageProcessor class encapsulates the conversion of the raw image data into a QImage and the following scaling. Its API is designed to be used inside a thread by providing a start() slot, which is invoked as a result of the QThread's started() signal, and a finished() signal, which is connected against the QThread's quit() slot to stop the event loop inside the thread.
The raw image data are passed in via the constructor and the scaled down QImage object is passed back as parameter of the finished() signal.
class ImageProcessor : public QObject { Q_OBJECT public: /* * Creates a new image processor. * * @param imageData The raw image data. * @param parent The parent object. */ ImageProcessor(const QByteArray &imageData, QObject *parent = 0); public Q_SLOTS: /* * Starts the actual operation. */ void start(); Q_SIGNALS: /* * This signal is emitted after the operation has finished. * * @param image The processed image. */ void finished(const QImage &image); private: // The raw image data QByteArray m_data; };
Inside the constructor we just store the raw image data in a member variable, so that we have access to it later on in the start() slot.
ImageProcessor::ImageProcessor(const QByteArray &imageData, QObject *parent) : QObject(parent) , m_data(imageData) { }
The start() slot is the part of the class where the actual work happens. We first convert the raw image data into a QImage object by using the loadFromData() convenience method. QImage supports a wide range of image types and support for custom types can be added by implementing new plugins. In our example however we only load JPEG images, which QImage supports out of the box.
In the second step we scale down the image to 768x500 pixels to make them fit inside the ListView.
In the last step we emit the finished signal to notify that we are done.
void ImageProcessor::start() { QImage image; image.loadFromData(m_data); image = image.scaled(768, 500, Qt::KeepAspectRatioByExpanding); // Image processing goes here, example could be adding water mark to the downloaded image emit finished(image); }
Note: Since signal/slot connections across thread boundaries are no direct method calls (unlike signal/slot connections in the same thread), but are based on event delivery, passing parameters in the signal is thread-safe, because a copy of the parameters is created and the copy is sent to the receiver thread.