QQuickImageProvider - get image from the network
-
Hi,
I need to show images downloaded from the network, but I can't just set the image URL to Image element, because the image needs to be requested with certain cookies from the server. So it seems that I have to subclass QQuickImageProvider to fetch image the way the server requires it. The problem is that I can't return the image data immediately from my reimplementedQQuickImageProvider::requestImage()
because data is returned in a slot connected toQNetworkRequest::finished()
. InrequestImage()
I can only send the HTTP request and connectQNetworkRequest::finished()
to a slot. I don't have the image data at the time whenrequestImage()
returns.In other words, QQuickImageProvider's API is synchronous, but Qt network API is asynchronous. Any ideas?
-
Hi,
You can use something like:
QEventLoop loop; connect(reply, SIGNAL(finished()), &loop, SLOT(quit())); loop.exec(); // process request and return image
WARNING This currently doesn't take into account that there might be timeout or other network problems so you have to adapt that code for these situations.
Hope it helps
-
Hello SGaist,
thanks for your reply. It works! Unfortunately, it crashes my application. Here is my code:
QImage EbookImageProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize) { QNetworkRequest request{QUrl(id)}; request.setRawHeader("Cookie", QString("Csob=" + CServerCommunicator::sessionID()).toUtf8()); QNetworkReply *reply = CServerCommunicator::netManager()->get(request); QEventLoop loop; QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); loop.exec(); if(reply->error() != QNetworkReply::NoError) { reply->deleteLater(); *size = m_defaultImage.size(); return m_defaultImage; } QByteArray data = reply->readAll(); reply->deleteLater(); reply = nullptr; QImage returnImage; if(returnImage.loadFromData(data)) { *size = returnImage.size(); return returnImage; } *size = m_defaultImage.size(); return m_defaultImage; }
I'm saving a default image to
m_defaultImage
in EbookImageProvider's constructor. I get the following output at the beginning (can be unrelated to image provider):
QEventLoop: Cannot be used without QApplication
QDBusConnection: system D-Bus connection created before QCoreApplication. Application may misbehave.
And when images are shown:
QObject: Cannot create children for a parent that is in a different thread.
(Parent is QNetworkAccessManager(0xc76c50), parent's thread is QThread(0xc761a0), current thread is QQuickPixmapReader(0x25a5520)
When I scroll the list view with images and reach the end of the list, application crashes. This happens on Desktop 64-bit and Android, in Qt 5.4.2. It's interesting that Images in listview are shown all at once, not one by one.
Any ideas?UPDATE: I modified the code to create QNetworkAccessManager directly in requestImage(). This eliminated "different thread" warnings, but the application still crashes. But maybe less frequently.
-
Hi,
We had a similar problem. We solved it as follows:
- Make worker thread to do the fetching.
- In requestImage() use a QSemaphore to block the main thread (yes I know)
- When the fetching thread finishes, unlock the QSemaphore and processing continues as normal
Hope this helps,
-
@t3685
Thanks for the suggestion. Does this mean that the application UI will freeze while main thread is blocked? -
Yes, it will block. But I believe the solution using the event loop will also block. If you don't want to UI to block, you can give an option to QImageProvider to use a separate thread
-
-
@SGaist I'm creating a QApplication in main() - I left this code as generated by Qt Creator.
But I think I found a better solution altogether - I'm going to ditch QQuickImageProvider and subclass QQuickPaintedItem instead. I will call
QQuickPaintedItem::update()
when network reply finishes. -
There is an altogether different solution too to your original objective ie,
"because the image needs to be requested with certain cookies from the server. So it seems that I have to subclass QQuickImageProvider ....."
in involves using QQmlNetworkAccessManagerFactory . I was able to solve a similar
problem that involved using QNetworkDiskCache . Initially i had tried the provider
approach but the subclassing was much simpler and stable ( read no more application
crashes)i am posting a relevant part of the code.
#include "customnamfactory.h" #include <QNetworkDiskCache> #include <QStandardPaths> CustomNAMFactory::CustomNAMFactory() { } QNetworkAccessManager *CustomNAMFactory::create(QObject *parent) { QNetworkAccessManager *nam = new QNetworkAccessManager(parent); QNetworkDiskCache *diskCache = new QNetworkDiskCache ; QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation); diskCache->setCacheDirectory(cacheDir); nam->setCache(diskCache); return nam; }
engine_ptr->setNetworkAccessManagerFactory(new CustomNAMFactory);
checkout http://doc.qt.io/qt-5/qtqml-networkaccessmanagerfactory-example.html
for a complete example.regds
mallah.