QDeclarativeItem and update() method



  • Hello,

    I created my own QDeclarativeItem for using in qml, but unfortunately when calling the update() method, the overloaded paint() method doesn't get called.

    What have I overseen.

    Header:
    @
    class SVGWIDGETSHARED_EXPORT SvgWidget : public QDeclarativeItem {
    Q_OBJECT
    private:
    QSvgRenderer *svgRenderer;
    QGraphicsScene *graphicsScene;

    public:
    explicit SvgWidget(QDeclarativeItem *parent = 0);
    ~SvgWidget();

    public slots:
    Q_INVOKABLE void startAnimation();
    void scenceChanged(const QList<QRectF> &region);

    protected:
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0);
    };
    @

    Code:
    @
    SvgWidget::SvgWidget(QDeclarativeItem *parent) :
    QDeclarativeItem(parent)
    {
    svgRenderer = new QSvgRenderer(QString(":/images/state.svg"));
    graphicsScene = new QGraphicsScene(svgRenderer->viewBoxF(),this);
    setFlag(QGraphicsItem::ItemHasNoContents, false);

    // connect signal
    connect(graphicsScene,SIGNAL(changed(QList<QRectF>)),this,SLOT(scenceChanged(QList<QRectF>)));
    

    }

    SvgWidget::~SvgWidget()
    {
    delete graphicsScene;
    delete svgRenderer;
    }

    void SvgWidget::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
    {
    // scene empty?
    if( graphicsScene->items().count() == 0)
    {
    qDebug() << "Building scene";

        // build scene
        for(quint8 count = 1; count <= 3; count++)
        {
            QString elementName = QString("layer%1").arg(count);
            QGraphicsSvgItem *graphicItem = new QGraphicsSvgItem();
            graphicItem->setSharedRenderer(svgRenderer);
            QRectF boundingRect = svgRenderer->boundsOnElement(elementName);
            graphicItem->setElementId(elementName);
            graphicItem->setPos(boundingRect.x(),boundingRect.y());
            graphicsScene->addItem(graphicItem);
        }
    }
    
    // Render scene
    graphicsScene->render(painter);
    qDebug() << "Rendering scene";
    

    }

    void SvgWidget::startAnimation()
    {
    static bool animationStarted = false;

    if(!animationStarted)
    {
        qsrand(QDateTime::currentMSecsSinceEpoch());
        QGraphicsItem *item;
        foreach(item, graphicsScene->items())
        {
            QGraphicsSvgItem *svgItem = dynamic_cast<QGraphicsSvgItem*>(item);
            QPropertyAnimation *propertyAnimation = new QPropertyAnimation(svgItem,"opacity");
            propertyAnimation->setDuration(3000 + (qrand() % 4) * 1000 );
            propertyAnimation->setLoopCount(-1);
            propertyAnimation->setStartValue(1.0);
            propertyAnimation->setEndValue(1.0);
            propertyAnimation->setKeyValueAt(0.5,0.0);
            propertyAnimation->start();
        }
    
        animationStarted = true;
    }
    

    }

    void SvgWidget::scenceChanged(const QList<QRectF> &region)
    {
    static quint64 count = 0;
    qDebug() << "SceneChanged event " << count++;
    update();
    }
    @

    And the type registration:
    @
    Q_DECL_EXPORT int main(int argc, char *argv[])
    {
    QScopedPointer<QApplication> app(createApplication(argc, argv));

    QmlApplicationViewer viewer;
    qmlRegisterType<SvgWidget>("ApsQml",1,0,"SvgWidget");
    viewer.setOrientation(QmlApplicationViewer::ScreenOrientationAuto);
    viewer.setMainQmlFile&#40;QLatin1String("qml/SVGTestQml/main.qml"&#41;);
    viewer.showExpanded();
    
    return app->exec&#40;&#41;;
    

    }
    @

    I can see that when starting the animations, the scenceChanged() slot will be called several times a second, but a "repaint" is never done.
    If the window loses its focus a repaint is done immediately.



  • hi, little bit confused about your code,
    why do you create QGraphicsScene 'per' SvgWidget?..

    afaik, single QGraphicsScene contains 'multiple' QGraphicsItem, (i.e, SvgWidget in this case).
    QGraphicsScene is a sort of manager of QGraphicsItems.
    and usually single QGraphicsScene will suffice for simple qml viewer

    i think if multiple QGraphicsScenes are really needed,
    you'd better review your scene management,



  • Just to understand, my SvgWidget contains one QGraphicsScene and multiple QGraphicItems.
    I'm using only one instance of the SvgWidget in my QML-App.

    One SvgWidget with one QGraphicsScene with multiple elements.

    Is there a better base class for using as type for QML? Should I use QObject directly, but I think I need a paint method to overload?



  • QmlApplicationViewer already has QGraphicsScene instance
    so i don't think you need to create graphics scene by yourself.
    ,, and when you call setMainQmlFile(...),
    your qml object (i.e., instance of SvgWidget) is created and added to
    (QmlApplicationViewer's internal) graphics scene automatically.

    QGraphicsScene instance is accessible using viewer's scene() method,
    and also using QGraphicsItem's scene() method (if item was added to scene())

    I'm not hundred percent sure what you want, but how about,,,

    create all three QGraphicsSvgItems in SvgWidget's constructor.
    and make all them as child of SvgWidget (maybe setParentItem() can be used)
    and make SvgWidget's paint() do nothing.
    and remove qgrpahicsscene instance and related code (because already has one inside viewer)
    ...

    this looks okay,
    because SvgWidget seems nothing more than a container of QGraphicsSvgItem,
    and actual painting happens with QGraphicsSvgItem, QSvgRenderer thing.

    ps. for iterating QGraphicsSvgItem, SvgWidget's childItem() can be used



  • Ok, I think I will rewrite my code just using the SvgWidget to create some QDeclarativeItem childs containing my SVG layers.

    I reduced my code to some basic stuff, but it seems my initial problem is still there.

    @
    SvgWidget::SvgWidget(QDeclarativeItem *parent) :
    QDeclarativeItem(parent)
    {
    svgRenderer = new QSvgRenderer(QString(":/images/state.svg"));
    setFlag(QGraphicsItem::ItemHasNoContents, false);
    }

    SvgWidget::~SvgWidget()
    {
    delete svgRenderer;
    }

    void SvgWidget::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
    {
    painter->drawRect(0,0,100,100);
    qDebug() << "Rendering scene";
    }

    void SvgWidget::startAnimation()
    {
    static bool animationStarted = false;

    if(!animationStarted)
    {
        qsrand(QDateTime::currentMSecsSinceEpoch());
        QPropertyAnimation *propertyAnimation = new QPropertyAnimation(this,"opacity");
        propertyAnimation->setDuration(3000 + (qrand() % 4) * 1000 );
        propertyAnimation->setLoopCount(-1);
        propertyAnimation->setStartValue(1.0);
        propertyAnimation->setEndValue(1.0);
        propertyAnimation->setKeyValueAt(0.5,0.0);
        propertyAnimation->start();
    
        animationStarted = true;
    }
    

    }
    @

    When starting the animation the paint() method gets called, but no update in my qml viewer.
    If I change the size of my qmlviewer window I can see the animation.

    How can I force an update of my QDeclarativeItem???



  • Ok, I rewrote my code as follows:

    @
    SvgWidget::SvgWidget(QDeclarativeItem *parent) :
    QDeclarativeItem(parent)
    {
    svgRenderer = new QSvgRenderer(QString(":/images/state.svg"));
    //setFlag(QGraphicsItem::ItemHasNoContents, false);

    // build scene
    for(quint8 count = 1; count <= 3; count++)
    {
        QString elementName = QString("layer%1").arg(count);
        QGraphicsSvgItem *graphicItem = new QGraphicsSvgItem(this);
        graphicItem->setSharedRenderer(svgRenderer);
        QRectF boundingRect = svgRenderer->boundsOnElement(elementName);
        graphicItem->setElementId(elementName);
        graphicItem->setPos(boundingRect.x(),boundingRect.y());
    }
    

    }

    SvgWidget::~SvgWidget()
    {
    delete svgRenderer;
    }

    void SvgWidget::startAnimation()
    {
    static bool animationStarted = false;

    if(!animationStarted)
    {
        qsrand(QDateTime::currentMSecsSinceEpoch());
        QGraphicsItem *item;
        foreach(item, childItems())
        {
            QGraphicsSvgItem *svgItem = dynamic_cast<QGraphicsSvgItem*>(item);
            QPropertyAnimation *propertyAnimation = new QPropertyAnimation(svgItem,"opacity");
            propertyAnimation->setDuration(3000 + (qrand() % 4) * 1000 );
            propertyAnimation->setLoopCount(-1);
            propertyAnimation->setStartValue(1.0);
            propertyAnimation->setEndValue(1.0);
            propertyAnimation->setKeyValueAt(0.5,0.0);
            propertyAnimation->start();
        }
    
        animationStarted = true;
    }
    

    }
    @

    Hopefully last question :-):
    The QGraphicsSvgItems are now drawn in their original size an on the original coordinates.
    When resizing the qmlviewer, they size is also not changed?

    Is there a way to get the size, bounding rect of the component when getting instanciated from the qmlviewer?
    Or how can I improve my coordinate mapping?



  • afaik, calling update() is sufficient for repaint,, no more 'forcing' exists.
    and when view is exposed, resized,,,
    paint event is spontaneously delivered to graphics item, so don't have to care about.

    i don't see any big problems with given code,,
    hm, did you give a valid width, height property inside qml file ?...

    i tried belows based on what you wrote, and blinking red rect is visible.

    @
    #include <QTimer>
    #include <QtGui/QApplication>
    #include <QDeclarativeView>
    #include "svgwidget.h"
    #include "mainwindow.h"

    /* contents of a.qml

    import QtQuick 1.0
    import ApsQml 1.0

    SvgWidget {
    width: 200
    height: 200
    }

    */

    int main(int argc, char *argv[])
    {
    QApplication a(argc, argv);

    qmlRegisterType<SvgWidget>("ApsQml",1,0,"SvgWidget");
    
    QDeclarativeView view;
    view.setSource(QUrl::fromLocalFile&#40;"a.qml"&#41;&#41;;
    view.show(&#41;;
    
    QTimer::singleShot(2000, view.rootObject(&#41;, SLOT(startAnimation(&#41;));
    
    return a.exec&#40;&#41;;
    

    }
    @

    and

    @SvgWidget::SvgWidget(QDeclarativeItem *parent) :
    QDeclarativeItem(parent)
    {
    svgRenderer = new QSvgRenderer(QString("/images/state.svg"));
    setFlag(QGraphicsItem::ItemHasNoContents, false);
    }

    SvgWidget::~SvgWidget()
    {
    delete svgRenderer;
    }

    void SvgWidget::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
    {
    painter->setBrush(Qt::red);
    painter->drawRect(0, 0,100, 100);
    qDebug() << "Rendering scene";
    }

    void SvgWidget::startAnimation()
    {
    static bool animationStarted = false;

    if(!animationStarted)
    {
        qsrand(QDateTime::currentMSecsSinceEpoch());
        QPropertyAnimation *propertyAnimation = new QPropertyAnimation(this,"opacity");
        propertyAnimation->setDuration(3000 + (qrand() % 4) * 1000 );
        propertyAnimation->setLoopCount(-1);
        propertyAnimation->setStartValue(1.0);
        propertyAnimation->setEndValue(1.0);
        propertyAnimation->setKeyValueAt(0.5,0.0);
        propertyAnimation->start();
    
        animationStarted = true;
    }
    

    }

    @

    and startAnimation() is defined as Q_SLOTS

    regarding size, rect,,,

    viewer's rootObject() will return qml's root object as it's name describes,
    and you can get properly typed object by casting...
    i think you can get size, rect of component with that object's getter

    and also can check size of component in qml side,, like

    @
    SvgWidget {
    ...
    onWidthChanged: console.log("width =" + width);
    onHeightChanged: console.log("height =" + height);
    }
    @

    same pattern can be used for x, y property..

    hope this helps...



  • But are there any efficent methods for translating the SVG model coordinates to the qml model coordinates.

    E.g. my SVG file consist of 3 elements and has a resolution of 640 x 480 pixels.
    My qml component has a default size of 320 x 320 pixels.

    When creating the child items with the following code snippet:
    @
    // build scene
    for(quint8 count = 1; count <= 3; count++)
    {
    QString elementName = QString("layer%1").arg(count);
    QGraphicsSvgItem *graphicItem = new QGraphicsSvgItem(this);
    graphicItem->setSharedRenderer(svgRenderer);
    QRectF boundingRect = svgRenderer->boundsOnElement(elementName);
    graphicItem->setElementId(elementName);
    graphicItem->setPos(boundingRect.x(),boundingRect.y());
    }
    @

    The boundingRect has all the positions/coordinates from the original SVG file.
    I need somehow a mechanism to scale the child items so that they will fit into my component.

    I tried mapToParent() but with no luck.

    With my first approach using the QGraphicScene this was all done automatically when rendering the scene.

    @
    // Render scene
    graphicsScene->render(painter);
    @

    But unfortunately with the bad effect, that my animations are not displayed.



  • then, how about introducing child svg item from QDeclarativeItem
    to delegete render single element in svg

    @
    #include <QPainter>
    #include "svgwidgetchild.h"

    SvgWidgetChild::SvgWidgetChild(QSvgRenderer* r, const QString& str, QDeclarativeItem* parent) :
    QDeclarativeItem(parent)
    {
    renderer = r;
    element = str;
    setFlag(ItemHasNoContents, false);
    }

    void SvgWidgetChild::paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*)
    {
    if (!renderer || !renderer->elementExists(element))
    return;

    // full boundrect drawing
    renderer->render(painter, element);
    

    }
    @

    and SvgWidget now using SvgWidgetChild rather than QGraphicsSvgItem

    @

    SvgWidget::SvgWidget(QDeclarativeItem *parent) :
    QDeclarativeItem(parent)
    {
    svgRenderer = new QSvgRenderer(QString("SVG-logo.svg"));
    child1 = new SvgWidgetChild(svgRenderer, "SVG", this);
    child2 = new SvgWidgetChild(svgRenderer, "logo", this);

    setFlag(ItemHasNoContents, false);
    

    }

    SvgWidget::~SvgWidget()
    {
    delete svgRenderer;
    }

    @

    and in viewer, resize mode is changed to SizeRootObjectToView,
    so qml object's width/height is automatically adjusted according to view

    @
    QDeclarativeView view;
    view.setSource(QUrl::fromLocalFile("a.qml"));
    view.setResizeMode(QDeclarativeView::SizeRootObjectToView);
    view.show();
    @

    ("SVG-logo.svg" can be downloaded from wikipedia site, http://en.wikipedia.org/wiki/File:SVG-logo.svg )

    when i run above code with appropriate header,
    animation is not blocked at all.



  • Will try this tomorrow.



  • Ok, so far so good, but…
    the positions of the single elements, in your case SVG and logo are lost, means the logo image will be paint in foreground and the text in the background.
    But I want to restore the original positions and scale the whole svg childs to fit into my qml window.
    So I think I need somehow a mechanism to translate the positions to scene positions.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.