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.