2

I have a C++ QQuickPaintedItem which I want to instanciate in QML. However the paint() method is never called.

Here's my code:

CPlotXY.h

#ifndef CPLOTXY_H
#define CPLOTXY_H

#include <QQuickPaintedItem>
#include <lib/qcustomplot-source/qcustomplot.h>
#include <QQuickView>

class CPlotXY : public QQuickPaintedItem
{
    Q_OBJECT
    public:
        CPlotXY(QQuickItem* parent = 0);
        virtual ~CPlotXY();

        void paint(QPainter* painter);

        Q_INVOKABLE void init();

    protected:
        void routeMouseEvents( QMouseEvent* event );

        virtual void mousePressEvent( QMouseEvent* event );
        virtual void mouseReleaseEvent( QMouseEvent* event );
        virtual void mouseMoveEvent( QMouseEvent* event );
        virtual void mouseDoubleClickEvent( QMouseEvent* event );

        void setupQuadraticDemo( QCustomPlot* Q_CustomPlot );

    private:
        QCustomPlot*         Q_CustomPlot;

    private slots:
        void graphClicked( QCPAbstractPlottable* plottable );
        void onCustomReplot();
        void updateCustomPlotSize();

};

#endif // CPLOTXY_H

CPlotXY.cpp

#include "CPlotXY.h"

CPlotXY::CPlotXY( QQuickItem* parent ) : QQuickPaintedItem( parent ), Q_CustomPlot( nullptr )
{
    setFlag( QQuickItem::ItemHasContents, true );
    setAcceptedMouseButtons( Qt::AllButtons );
    connect( this, &QQuickPaintedItem::widthChanged, this, &CPlotXY::updateCustomPlotSize );
    connect( this, &QQuickPaintedItem::heightChanged, this, &CPlotXY::updateCustomPlotSize );
}

CPlotXY::~CPlotXY()
{
    delete Q_CustomPlot;
    Q_CustomPlot = nullptr;
}

void CPlotXY::init()
{
    Q_CustomPlot = new QCustomPlot();

    updateCustomPlotSize();

    setupQuadraticDemo( Q_CustomPlot );

    connect( Q_CustomPlot, &QCustomPlot::afterReplot, this, &CPlotXY::onCustomReplot );

    Q_CustomPlot->replot();

}


void CPlotXY::paint(QPainter* painter)
{
    qDebug() << "@####";
    if (Q_CustomPlot)
    {
        QPixmap    Q_PixMap( boundingRect().size().toSize() );
        QCPPainter Q_CPPainter( &Q_PixMap );

        Q_CustomPlot->toPainter(&Q_CPPainter);

        painter->drawPixmap(QPoint(), Q_PixMap);
    }
}

void CPlotXY::mousePressEvent( QMouseEvent* Q_Event )
{
    qDebug() << Q_FUNC_INFO;
    routeMouseEvents( Q_Event );
}

void CPlotXY::mouseReleaseEvent( QMouseEvent* Q_Event )
{
    qDebug() << Q_FUNC_INFO;
    routeMouseEvents( Q_Event );
}

void CPlotXY::mouseMoveEvent( QMouseEvent* Q_Event )
{
    routeMouseEvents( Q_Event );
}

void CPlotXY::mouseDoubleClickEvent( QMouseEvent* Q_Event )
{
    qDebug() << Q_FUNC_INFO;
    routeMouseEvents( Q_Event );
}

void CPlotXY::graphClicked( QCPAbstractPlottable* Q_Plottable )
{
    qDebug() << Q_FUNC_INFO << QString( "Clicked on graph '%1 " ).arg( Q_Plottable->name() );
}

void CPlotXY::routeMouseEvents( QMouseEvent* Q_Event )
{
    if (Q_CustomPlot)
    {
        QMouseEvent* Q_NewEvent = new QMouseEvent( Q_Event->type(), Q_Event->localPos(), Q_Event->button(), Q_Event->buttons(), Q_Event->modifiers() );
        //QCoreApplication::sendEvent( m_CustomPlot, newEvent );
        QCoreApplication::postEvent( Q_CustomPlot, Q_NewEvent );
    }
}

void CPlotXY::updateCustomPlotSize()
{
    if (Q_CustomPlot)
    {
        Q_CustomPlot->setGeometry( 0, 0, width(), height() );
    }
}

void CPlotXY::onCustomReplot()
{
    qDebug() << Q_FUNC_INFO;
    update();
}

void CPlotXY::setupQuadraticDemo( QCustomPlot* customPlot )
{
    // make top right axes clones of bottom left axes:
    QCPAxisRect* axisRect = customPlot->axisRect();

    // generate some data:
    QVector<double> x( 101 ), y( 101 );   // initialize with entries 0..100
    QVector<double> lx( 101 ), ly( 101 ); // initialize with entries 0..100
    for (int i = 0; i < 101; ++i)
    {
        x[i] = i / 50.0 - 1;              // x goes from -1 to 1
        y[i] = x[i] * x[i];               // let's plot a quadratic function

        lx[i] = i / 50.0 - 1;             //
        ly[i] = lx[i];                    // linear
    }
    // create graph and assign data to it:
    customPlot->addGraph();
    customPlot->graph( 0 )->setPen( QPen( Qt::red ) );
    //customPlot->graph( 0 )->setSelectedPen( QPen( Qt::blue, 2 ) );
    customPlot->graph( 0 )->setData( x, y );

    customPlot->addGraph();
    customPlot->graph( 1 )->setPen( QPen( Qt::magenta ) );
    //customPlot->graph( 1 )->setSelectedPen( QPen( Qt::blue, 2 ) );
    customPlot->graph( 1 )->setData( lx, ly );

    // give the axes some labels:
    customPlot->xAxis->setLabel( "x" );
    customPlot->yAxis->setLabel( "y" );
    // set axes ranges, so we see all data:
    customPlot->xAxis->setRange( -1, 1 );
    customPlot->yAxis->setRange( -1, 1 );

    customPlot ->setInteractions( QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectPlottables );
    connect( customPlot, SIGNAL( plottableClick( QCPAbstractPlottable*, QMouseEvent* ) ), this, SLOT( graphClicked( QCPAbstractPlottable* ) ) );
}

XPlotXY.qml

import QtQuick 2.0
import CPlotXY 1.0

Item {
    CPlotXY {
        id: customPlot
        anchors.fill: parent
        Component.onCompleted: init()
    }
}

And Finally here is when I instanciate it:

Repeater {
    id: widgetRepeater
    model: pageModel
    anchors.fill: parent
    delegate: Loader {



        property int modelX: model.XPos
        property int modelY: model.YPos
        //property int modelWidth: model.width
        //property int modelHeight: model.height

        active: true;
        asynchronous: true
        //anchors.centerIn:  parent
        source: "%1.qml".arg(model.Type)
    }
}

The init() function of the CPlotXY is called and the object is created but paint() method is never called. I know the solution is obvious but i'm not comfortable with QQuickPaintedItem.

Thank you for helping.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
OTmn
  • 185
  • 1
  • 2
  • 16

1 Answers1

3

I think this minimal example would reproduce your problem:

// mypainteditem.h

#ifndef MYPAINTEDITEM_H
#define MYPAINTEDITEM_H

#include <QObject>
#include <QQuickPaintedItem>

class MyPaintedItem : public QQuickPaintedItem
{
    Q_OBJECT
public:
    MyPaintedItem(QQuickItem* parent = nullptr);


    void paint(QPainter* painter);
};

#endif // MYPAINTEDITEM_H

// mypainteditem.cpp

#include "mypainteditem.h"
#include <QDebug>

MyPaintedItem::MyPaintedItem(QQuickItem* parent)
    : QQuickPaintedItem(parent)
{

}


void MyPaintedItem::paint(QPainter *painter)
{
    qDebug() << "Paint called";
}

// main.qml (registered MyPaintedItem as PaintItem)

import QtQuick 2.7
import QtQuick.Window 2.1
import PaintItem 1.0

Window {
    id:rootWindow
    visible: true
    width: 800
    height: 600
    title: qsTr("Test")

    PaintItem {
    }
}

As you can see: The paint-method is not called. The problem is: We have no size of the PaintItem therefore it is invisible. To save ourselves from unnecessary paint-calls, we don't call it.

Changing the size will produce the paint-call:

import QtQuick 2.7
import QtQuick.Window 2.1
import PaintItem 1.0

Window {
    id:rootWindow
    visible: true
    width: 800
    height: 600
    title: qsTr("Test")

    PaintItem {
        id: pi
        height: width
    }

    Timer {
        interval: 2000 // Set some dimensions after 2 seconds will produce the paint call.
        onTriggered: pi.width = 100;
        running: true
    }
}

In case that the question might arise, why your chart is of size 0...

  • In your file XPlotXY.qml you (for some reasons) wrap your plot in an Item making the plot to fill that Item

  • In your main.qml you create a Repeater that fills the window. A Repeater however does not resize it's delegates. It only instantiates them.

  • The delegate of your Repeater is a Loader. That Loader adapts its size to it's content, if no explicit size is set. So what does it load? Probably your XPlotXY.qml which has no size set. So the Loader and the plot end up with a size of 0