How can I use QPainter to paint on QGraphicsView?



  • I am trying to develop an app to draw stuff like lines, rectangles, and arcs and to adjust them. I know that QGraphicsView Class and QGraphicsScene Class provides methods to generate and manage geometries. But I don't know how can I update my canvas instantly while I'm moving my mouse to draw. Since QGraphicsView Class inherits QWidget Class, I thought that QPainter can be used to do so. But here's error information:

    Starting F:\QtProj\build-scene-Desktop_Qt_5_6_3_MinGW_32bit-Debug\debug\scene.exe...
    QWidget::paintEngine: Should no longer be called
    QPainter::begin: Paint device returned engine == 0, type: 1
    QPainter::setPen: Painter not active
    

    And of course nothing was displayed on the screen.
    Here's myview.h:

    #ifndef CANVASWIDGET_H
    #define CANVASWIDGET_H
    
    #include <QWidget>
    #include <QGraphicsView>
    
    class MyView : public QGraphicsView
    {
        Q_OBJECT
    
    public:
        explicit MyView(QWidget *parent = 0);
        int drawType = 0;
        QPointF pStart, pMove;
        int started = 0;
    
    signals:
    
    private slots:
        void setCursor(int);
        void setType(int);
        void resetPos();
        qreal getLength();
    
    protected:
        void mousePressEvent(QMouseEvent *event);
        void mouseMoveEvent(QMouseEvent *event);
        void paintEvent(QPaintEvent *event);
    };
    
    #endif // CANVASWIDGET_H
    
    

    Here's myview.cpp:

    #include "myview.h"
    #include <QDebug>
    #include <QWidget>
    #include <QGraphicsView>
    #include <QMouseEvent>
    #include <QPainter>
    #include <math.h>
    
    MyView::MyView(QWidget *parent)
    {
    
    }
    void MyView::resetPos(){
        pStart.setX(0.0);  pStart.setY(0.0);
        pMove.setX(0.0);  pMove.setY(0.0);
    }
    qreal MyView::getLength(){
        return sqrt((pStart.x()-pMove.x())*(pStart.x()-pMove.x()) + (pStart.y()-pMove.y())*(pStart.y()-pMove.y()));
    }
    void MyView::setType(int type){
    //    qDebug()<<"scene:"<<type;
        drawType = type;
    }
    void MyView::setCursor(int type){
    //    qDebug()<<"view:"<<type;
    
        if(type == 1 || type == 2 || type == 3){
            viewport()->setCursor(Qt::CrossCursor);
        }
        if(type == 4){
            viewport()->setCursor(Qt::SizeAllCursor);
        }
        if(type == 5){
            viewport()->setCursor(Qt::ArrowCursor);
        }
    }
    void MyView::mousePressEvent(QMouseEvent *event){
        //  Click to draw
            if(drawType==1 || drawType==2 || drawType==3){
                if(!started){
                    pStart = event->localPos();
                    started = 1;
                    setMouseTracking(true);
                }
                if(started){
                        setMouseTracking(false);
                        this->update();
                        started = 0;
                }
            }
    }
    void MyView::mouseMoveEvent(QMouseEvent *event){
        if(drawType == 1 || drawType == 2 || drawType == 3){
            pMove = event->localPos();
        }
    }
    void MyView::paintEvent(QPaintEvent *event){
        QPainter painter(this);
        QPen pen;
        pen.setWidth(1);
        pen.setStyle(Qt::SolidLine);
        painter.setPen(pen);
    
        switch (drawType) {
            case 1: painter.drawLine(pStart, pMove);
                    break;
            case 2: painter.drawRect(QRectF(pStart, pMove));
                    break;
            case 3: pen.setStyle(Qt::DashLine);  painter.setPen(pen);
                    painter.drawLine(pStart, pMove);
                    pen.setStyle(Qt::SolidLine);  painter.setPen(pen);
                    painter.drawEllipse(pStart, getLength(), getLength());
                    break;
        }
    }
    

    Here's mainwindow.cpp:

    #include "mainwindow.h"
    #include "myitem.h"
    #include "myscene.h"
    #include "myview.h"
    #include "ui_mainwindow.h"
    #include <QGraphicsScene>
    #include <QGraphicsView>
    
    MainWindow::MainWindow(QWidget *parent) :
        QMainWindow(parent),
        ui(new Ui::MainWindow)
    {
        ui->setupUi(this);
        //New MyScene
        MyScene *s = new MyScene;
        //New View
        MyView *v = new MyView;
        v->setScene(s);
        setCentralWidget(v);
    
        QObject::connect(this, SIGNAL(drawType(int)), v, SLOT(setCursor(int)));
        QObject::connect(this, SIGNAL(drawType(int)), s, SLOT(setType(int)));
    }
    
    MainWindow::~MainWindow()
    {
        delete ui;
    }
    
    void MainWindow::on_actionDrawLine_triggered()
    {
        emit drawType(1);
        ui->statusBar->showMessage(tr("Type: Line"));
    }
    
    void MainWindow::on_actionDrawRect_triggered()
    {
        emit drawType(2);
        ui->statusBar->showMessage(tr("Type: Rect"));
    }
    
    void MainWindow::on_actionDrawArc_triggered()
    {
        emit drawType(3);
        ui->statusBar->showMessage(tr("Type: Circle"));
    }
    void MainWindow::on_actionDrag_triggered()
    {
        emit drawType(4);
        ui->statusBar->showMessage(tr("Type: Drag"));
    }
    void MainWindow::on_actionClear_triggered()
    {
        emit drawType(5);
        ui->statusBar->showMessage(tr("Type: Clear"));
    }
    

    Many thanks if you can help me!


  • Qt Champions 2017

    Hi
    Normally you would make a custom widget based on QWidget and
    use http://doc.qt.io/qt-5/qgraphicsproxywidget.html
    to have it in scene.
    Drawing directly on the view would make it impossible to have support for
    all the features of moving / zooming etc.



  • @mrjj I'd try this. I am sure it would work. Thank you so much!


  • Lifetime Qt Champion

    Hi and welcome to devnet,

    Are you looking for something like the Scribble example ?



  • hi @elmLiu and Welcome!

    First of all, if you are going to use a QGraphicsView, always use the QGraphicsScene! I draw and interact with 10000s of elements (lines, arcs, text, points, etc.) and have real-time panning, zooming, and rotation at least 20Hz (I have clocked it at 100Hz) for the moving map! Actual mouse interaction is instantaneous.

    If all you want to do is draw something yourself, derive your own QGraphicsScene override the drawForeground method. This will give you the ability draw anything you desire.

    I use drawForeground to draw things like a site scale, compass rose, vehicle, or a navigation overlay over the linework and surfaces. If you do not want to have widgets as part of your scene, the QGraphicsView accepts and works nicely with layouts (see buttons on the right of the plan screen).

    0_1533326482036_7cb0ec68-9b84-40c5-889a-8faa9fa021c3-image.png

    Don't be afraid!



  • @Buckwheat Hi, before trying to use QGraphicsView & QGraphicsScene, I customized QWidget Class as my canvas, setting it central widget, and used mousePressEvent & mouseMoveEvent & mouseReleaseEvent & paintEvent to instantly update my canvas while using mouse to draw geometries, and also, to detect certain points on geometries such as end point & mid point. So far I've made it happen, but I just don't know how to do the same using QGraphicsView & QGraphicsScene.
    Which part can the drawForeground method do? I know that all items are stored in Scene and displayed via View, but in which class or whose method shall I try to instantly update my canvas?
    I'm new to Qt, may you forgive my doubts :)



  • Hi @elmLiu

    I was new to Qt back in 2012. You will find that you will learn to use it quickly. Being older (and hopefully wizened) I find that a lot of people like to build Qt from scratch (why I do not know). I just want to use it and learn it. It makes developing fun in some respects.

    Please derive MyGraphicsScene from QGraphicsScene and override mousePressEvent, mouseMoveEvent, mouseReleaseEvent and if desired, wheelEvent. These will give you the mouse position and the last mouse position in scene coordinates in QGraphicsSceneMouseEvent. From there you can do your magic! I pan, rotate, zoom, select, etc. using

    Again, if you wish to manage your own data and paint it yourself, you can override drawBackground (always is the bottom layer) and drawForeground (always the top layer) and you will have complete control. I do a mixture of both. As I do not want to manage 10000s of items but only the overlays I need.

    Unfortunately, if you want complete control of zooming, you have to jump through hoops to disable the scrolling features. I had to do this because of the nature of my work. I consider this the only problem with QGraphicsView. I will there were two classes: QGraphicsView (no scrolling at all and acts more like a pure painter) and QGraphicsScrollView (this woudl behave like the current).

    Enjoy!



  • @Buckwheat Thanks a lot! By overriding drawForeground method I reimplemented my drawing demand! I'll dig more of QGraphicsScene! : )



  • In your MyView::paintEvent write QPainter painter(viewport()); instead of QPainter painter(this);.



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