1

I need to force a repaint of a QGraphicsScene inside a view - the entire view.
I am explaining below why I can't repaint specific items, and what I have tried.

I have a QGraphicsScene subclass that paints an "actual canvas rectangle" for items - but items can be placed outside this rectangle, or be larger.
The "actual canvas rectangle" can be resized, and I also move it on resize so its (0,0) is always the point of (0,0) coordinates in QGraphicsScene.
I made the resize smart enough to repaint the largest rectangle between the old and new canvas.
I did not consider a redraw on the entire QGraphicsScene or any items outside the canvas, because it is giant - and the items could be all over the place - and because the initial application was also re-centering the canvas in view.

This class is referenced from a QWidget created next to a QScrollArea promoted to the QGraphicsView subclass containing the scene.

If I place a large text item on the scene, and I resize the canvas, I can have leftover pieces of text either on the left or the right (depending if the item was larger than the old or new canvas). Because of the "move" generally increasing the canvas size leaves an artifact on the left.

Making movements on canvas, or zooming, does a repaint - but even paning the scene doesn't.

I need to force a repaint on the old item, after the resize which technically moves it.

I would be happy with a viewport update, but I can't get it: a few attempts:

    QList<QGraphicsView*> views = m_canvas->views();
    if(views.size() > 0)
    {
        m_canvas->update(views.at(0)->rect());  
        // or
        QWidget* viewport = views.at(0)->viewport();
        viewport->update();
        // or
        QRectF r = views.at(0)->mapToScene(views.at(0)->rect()).boundingRect();
        m_canvas->update(r);
    }

None of these works.

Since the coordinates have changed, I don't even know how to map the old bounding rectangle of my item to the new coordinates of the scene.
That's why my thought is, simpler to update the view.
But I must be doing something wrong since the leftover drawing is still there.

All I really want is to force a scene redraw on the entire viewport.
How can I do that ?

Edit - adding a program that reproduces the issue.

myview.h

#ifndef MYVIEW_H
#define MYVIEW_H

#include <QGraphicsView>
#include <QGraphicsScene>

class MyView : public QGraphicsView
{
public:
    MyView(QWidget *parent = 0) : QGraphicsView(parent) {
        setRenderHints(QPainter::Antialiasing |
                       QPainter::SmoothPixmapTransform);
        setCacheMode(CacheNone); // to avoid smear when panning very far
        setAutoFillBackground(true);
        setBackgroundRole(QPalette::Window);
        setViewportUpdateMode(SmartViewportUpdate);
        setTransformationAnchor(AnchorUnderMouse);
        setDragMode(ScrollHandDrag);
    }
};

class MyScene : public QGraphicsScene
{
public:
    explicit MyScene(const qreal sceneW, const qreal sceneH,
            const qreal canvasW, const qreal canvasH,
            QObject *parent = 0) : QGraphicsScene(parent) {
        m_actualCanvasRect = QRectF(0, 0, canvasW, canvasH);
        setSceneRect(0.5*(canvasW - sceneW), 0.5*(canvasH - sceneH), sceneW, sceneH);
    }

    void setCanvasSize(const qreal canvasW, const qreal canvasH) {
        setSceneRect(0.5*(canvasW - sceneRect().width()),
                     0.5*(canvasH - sceneRect().height()),
                     sceneRect().width(), sceneRect().height());
        qreal oldW = m_actualCanvasRect.width(),
              oldH = m_actualCanvasRect.height();
        m_actualCanvasRect = QRectF(0, 0, canvasW, canvasH);
        qreal updateW = qMax(oldW, canvasW);
        qreal updateH = qMax(oldH, canvasH);
        QRectF updateRect(0, 0, updateW, updateH);
        update(updateRect);
    }

protected:
    virtual void drawBackground(QPainter *painter, const QRectF &rect) {
        QGraphicsScene::drawBackground(painter, rect);
        QPen p(Qt::green);
        p.setWidth(5);   // this creates artifacts but lucky in my code i don't use a width
        painter->setPen(p);
        painter->setBrush(Qt::red);
        painter->drawRect(m_actualCanvasRect);
    }

private:
    QRectF m_actualCanvasRect;
};

#endif // MYVIEW_H

testscene.h

#ifndef TESTSCENE_H
#define TESTSCENE_H

#include <QWidget>
#include "myview.h"

namespace Ui {
class TestScene;
class MyScene;
class QGraphicsTextItem;
}

class TestScene : public QWidget
{
    Q_OBJECT

public:
    explicit TestScene(QWidget *parent = 0);
    ~TestScene();

private slots:
    void on_btnResize_clicked();

private:
    Ui::TestScene *ui;
    MyScene* s;
    QGraphicsTextItem* yyy;
};

#endif // TESTSCENE_H

main.cpp

#include "testscene.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    TestScene w;
    w.show();

    return a.exec();
}

testscene.cpp

#include "testscene.h"
#include "ui_testscene.h"

#include <QGraphicsTextItem>

TestScene::TestScene(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::TestScene)
{
    ui->setupUi(this);

    qreal w = 300, h = 200;
    s = new MyScene(1000000, 1000000, w, h);
    ui->myView->setScene(s);

    yyy = new QGraphicsTextItem("Hello Great World");
    QFont fff("Arial", 34);
    yyy->setFont(fff);

    QSizeF sz = yyy->boundingRect().size();
    qreal cx = (w - sz.width())/2;
    qreal cy = (h - sz.height())/2;
    yyy->setPos(cx, cy);

    s->addItem(yyy);
}

TestScene::~TestScene()
{
    delete ui;
}

void TestScene::on_btnResize_clicked()
{
    bool ok;
    qreal w = ui->linWidth->text().toDouble(&ok);
    if(!ok || w <= 0) return;
    qreal h = ui->linHeight->text().toDouble(&ok);
    if(!ok || h <= 0) return;

    s->setCanvasSize(w, h);

    QSizeF sz = yyy->boundingRect().size();
    qreal cx = (w - sz.width())/2;
    qreal cy = (h - sz.height())/2;
    yyy->setPos(cx, cy);
}

testscene.ui

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>TestScene</class>
 <widget class="QWidget" name="TestScene">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>649</width>
    <height>307</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>TestScene</string>
  </property>
  <widget class="QScrollArea" name="scrlArea">
   <property name="geometry">
    <rect>
     <x>60</x>
     <y>40</y>
     <width>471</width>
     <height>231</height>
    </rect>
   </property>
   <property name="lineWidth">
    <number>0</number>
   </property>
   <property name="verticalScrollBarPolicy">
    <enum>Qt::ScrollBarAlwaysOff</enum>
   </property>
   <property name="horizontalScrollBarPolicy">
    <enum>Qt::ScrollBarAlwaysOff</enum>
   </property>
   <property name="widgetResizable">
    <bool>true</bool>
   </property>
   <widget class="QWidget" name="scrollAreaWidgetContents">
    <property name="geometry">
     <rect>
      <x>0</x>
      <y>0</y>
      <width>469</width>
      <height>229</height>
     </rect>
    </property>
    <widget class="MyView" name="myView">
     <property name="geometry">
      <rect>
       <x>10</x>
       <y>10</y>
       <width>451</width>
       <height>211</height>
      </rect>
     </property>
     <property name="lineWidth">
      <number>0</number>
     </property>
     <property name="verticalScrollBarPolicy">
      <enum>Qt::ScrollBarAlwaysOff</enum>
     </property>
     <property name="horizontalScrollBarPolicy">
      <enum>Qt::ScrollBarAlwaysOff</enum>
     </property>
    </widget>
   </widget>
  </widget>
  <widget class="QPushButton" name="btnResize">
   <property name="geometry">
    <rect>
     <x>550</x>
     <y>50</y>
     <width>75</width>
     <height>23</height>
    </rect>
   </property>
   <property name="text">
    <string>Resize</string>
   </property>
  </widget>
  <widget class="QLineEdit" name="linWidth">
   <property name="geometry">
    <rect>
     <x>540</x>
     <y>90</y>
     <width>113</width>
     <height>20</height>
    </rect>
   </property>
  </widget>
  <widget class="QLineEdit" name="linHeight">
   <property name="geometry">
    <rect>
     <x>540</x>
     <y>130</y>
     <width>113</width>
     <height>20</height>
    </rect>
   </property>
  </widget>
 </widget>
 <layoutdefault spacing="6" margin="11"/>
 <customwidgets>
  <customwidget>
   <class>MyView</class>
   <extends>QGraphicsView</extends>
   <header location="global">myview.h</header>
  </customwidget>
 </customwidgets>
 <resources/>
 <connections/>
</ui>

To test - after it starts, set a size 40,30 and see artifacts. Set larger size after moving the program, artifacts will show again.

I have realized that I can do the resize after repositioning the item - and that takes care of artifacts for some odd reason (even though the update called in resize does not include the entire item bounding rectangle). That conflicts with some other operations, but I can make it work.

So - I am not desperate for a solution anymore, just understanding. Why / how can I force a redraw in the current situation - with an item setPos after the resize.

Thalia
  • 13,637
  • 22
  • 96
  • 190
  • All of this should work without you even thinking about it. That's the whole point of the graphics scene system. You shouldn't need to invoke any repaints/updates, or to worry about anything but painting one singular item in isolation. Your items somehow don't implement the `QGraphicsItem` API correctly, or you abuse them somehow. You're doing something very fundamentally wrong. You need to fix the underlying problem, but we can't help until you demonstrate a complete example (single `main.cpp`) that breaks for you. – Kuba hasn't forgotten Monica Jun 03 '16 at 14:28
  • @KubaOber - Added a full program to demonstrate the artifacts. In my experience, a `QGraphicsScene` doesn't redraw if it doesn't think parts of it have changed. My actual code does subclass `QGraphicsItem` - but that is irrelevant since an item has the same behavior (that is why I used a plain `QGraphicsTextItem` in my sample). – Thalia Jun 03 '16 at 16:04

0 Answers0