3

As most people familiar with QML know, there is no builtin "refresh" functionality in QML Image.

I would like to create a new QML type, say RefreshableImage to alleviate this problem without resorting to changing the source, which I feel is an ugly hack, as it bleeds into all layers of the Model-View relationship and this switching behaviour is unnatural. Additionally, setting a different source on an Image breaks any binding that may have been set (which is really the core of the problem: I want an updateable image that maintains its binding, and is isolated to QML). I understand I'll need to call some signal to actually refresh the image, that's fine.

I have trouble finding documentation on a way to extend Qt's own Image so that I can force it to reload its source. I would like to avoid writing a complete component that mostly badly replicates Image to add one function. Is there a way to extend a builtin component like I have in mind?

Minor notes:

  • due to external circumstances, I'm limited to Qt 5.5.
  • We use as source a UUID of the underlying image object which is used by an QQuickImageProvider to get the actual QImage. Hence I don't want to change this when updating an image.
rubenvb
  • 74,642
  • 33
  • 187
  • 332
  • Why you need to update `Image` with same `source` etc.? Isn't that the same image? – folibis Dec 15 '16 at 11:16
  • @folibis technically yes, but the underlying data has changed. We use QML ImageProviders to supply the Qimages to QML from our data structures. The id we use is the uuid of the image in our system, so we can retrieve the actual object elsewhere from that uuid. We're just reusing this uuid for simplicity's sake. – rubenvb Dec 15 '16 at 11:17
  • Ah ok, you should to specify this in the question. I guess that's important. – folibis Dec 15 '16 at 11:19
  • May be you should use UUID/version instead of just UUID? I think that would've been logical since image => imageid is a one to one link. It's not good if same ID relates to different image / image data. Btw, how the image data changes? – folibis Dec 15 '16 at 11:30
  • Well. It relates to the same (conceptual) image, it's just that the image data has changed. I really want to avoid adding version fluff all over if I can write a component for it... – rubenvb Dec 15 '16 at 11:32
  • I quite like the idea of @folibis to represent the underlying data change by a change in the ID, like a hash changing when the data changes. Otherwise it is not that difficult to create a custom item that shows an image, doesn't even have to go through an image provider then. – Kevin Krammer Dec 15 '16 at 12:57
  • @Kevin Dropping the image provider is impossible because the images aren't just image files somewhere on disk. Sometimes even the QImages are generated on demand. – rubenvb Dec 15 '16 at 14:54
  • Wouldn't just refreshing by setting a blank source and rebinding it with `Qt.binding` would be enough ? – GrecKo Dec 15 '16 at 16:12
  • @GrecKo maybe yes, but is there a way to encapsulate that behaviour in a QML Component so that the user can just do `source: ` and have it work? – rubenvb Dec 15 '16 at 16:24
  • Yes if you name the property something else than `source` or if you wrap the `Image` in an outer `Item`, naming it something else will be easier – GrecKo Dec 15 '16 at 16:27
  • @rubenvb if you have a custom item that takes a `QImage` your data source object or model can just provide the QImage directly. The image provider is only needed when you are working with the QtQuick `Image` element – Kevin Krammer Dec 19 '16 at 10:25
  • @Kevin how do I show the image then in QML? Paint it myself? That's something I'd like to avoid, frankly, because, well, that's Image's responsibility... – rubenvb Dec 19 '16 at 10:26
  • @rubenvb yes, e.g. with a simple `QQuickPaintedItem` based item. That would simplify your setup considerably, as you can just let the model return the image from `data()`, signal changes as usual with `dataChanged()` and with only changing the name of the QtQuick element being used – Kevin Krammer Dec 19 '16 at 10:34

2 Answers2

3

You could create a RefreshableImage type that hides the ugly source changing from you.

There's a simple way of doing it by introducing a new property for the source :

import QtQuick 2.0

Image {
    id: root
    property string refreshableSource
    source: refreshableSource
    function refresh() {
        source = "";
        source = Qt.binding(function() { return refreshableSource });
    }
}

You have to use it like that : RefreshableImage { refreshableSource: "image.jpg" }.

If you want to still use source as a property, you could do it with some aliases shenanigans. Since aliases are only activated once a component has been fully initialized, you can overwrite the source property of Image but still be able to access the underlying one.

import QtQuick 2.0

Image {
    id: root
    property alias actualUnderlyingSource: root.source //this refers to Image.source and not the newly created source alias
    property alias source: root.refreshableSource
    property string refreshableSource
    actualUnderlyingSource: refreshableSource
    function refresh() {
        actualUnderlyingSource = "";
        actualUnderlyingSource = Qt.binding(function() { return refreshableSource });
    }
}

You could then use it like so RefreshableImage { source: "image.jpg" }, this will in fact modify the refreshableSource property

GrecKo
  • 6,615
  • 19
  • 23
  • Is there a way I can add some underlying C++ to connect it to the actual image object? As in, can I extend QML Image with C++? I guess composition of an `ImageRefresher` C++/QML component/object would make this possible? I would like it to listen to/observe the object attached to the uuid in question. Thanks! – rubenvb Dec 16 '16 at 11:26
0

A rough skeleton for directly using QImage from a model with a custom item

class DirectImage : public QQuickPaintedItem
{
    Q_OBJECT
    Q_PROPERTY(QImage image READ image WRITE setImage NOTIFY imageChanged)

public:
    void paint(QPainter *painer);
    void setImage(const QImage &image);
};

void DirectImage::paint(QPainter *painter)
{
    painter->drawImage(m_image.scaled(width(), height()):
}

void DirectImage::setImage(const QImage &image)
{
    m_image = image;
    emit imageChanged();
    setImplicitWidth(image.width());
    setImplicitHeight(image.height());
    update();
}

Registered via

qmlRegisterType<DirectImage>("MyElements", 1, 0, "RefreshableImage");

Use via

import MyElements 1.0

// ...

RefreshableImage {
    image: model.image
}

The model just returns the QImage when asked for the image role, emits the dataChanged() signal with the image role whenever the image changes.

If the image needs to be generated on demand, the model can first return an empty image or a placeholder image and emit the dataChanged() signal when the actual content is available.

Kevin Krammer
  • 5,159
  • 2
  • 9
  • 22