QGraphicsScene incorrect "artifacts" on scrolling "bug"?
-
I do a lot of work with
QGraphicsScene
, and claim to know what I am doing :) I have "incorrect artifact" behaviour on scrolling aQGraphicsView
. I do not understand what is happening, and want to know whether this might be a "bug" or whether anyone can explain why I see what I do. (I know all about incorrectboundingRect()
and artifacts, this is not the issue here.)I work with Qt 5.15.3 under Ubuntu 22.04, Xorg (not Wayland). Would someone be kind enough to try it with Qt5 or Qt6 on whatever platform and see if they have the same problem, please?
Reduced code to single file to reproduce. You only have to copy & paste these two files.
// main.cpp #include <QApplication> #include "main.h" int main(int argc, char *argv[]) { QApplication a(argc, argv); MyGraphicsScene scene; scene.setSceneRect(0, 0, 1000, 1000); MyGraphicsView view; view.setScene(&scene); view.setGeometry(200, 200, 500, 500); view.addFixedToViewRect(); view.show(); return a.exec(); }
// main.h #ifndef MAIN_H #define MAIN_H #include <QDebug> #include <QGraphicsRectItem> #include <QGraphicsView> #include <QTimer> class MyGraphicsScene : public QGraphicsScene { Q_OBJECT private: QGraphicsRectItem *fixedToScene; public: MyGraphicsScene(QWidget *parent = nullptr) : QGraphicsScene(parent) { fixedToScene = this->addRect(450, 450, 100, 100, QPen(), Qt::red); } protected: virtual void drawBackground(QPainter *painter, const QRectF &rect) override { qDebug() << "drawBackground()" << rect; // just fill the whole scene, doesn't matter about `rect` painter->fillRect(sceneRect(), Qt::green); } }; class MyGraphicsView : public QGraphicsView { Q_OBJECT private: QGraphicsRectItem *fixedToView; public: MyGraphicsView(QWidget *parent = nullptr) : QGraphicsView(parent) { connect(this, &MyGraphicsView::viewMoved, this, &MyGraphicsView::repositionFixedToView); } void addFixedToViewRect() { fixedToView = scene()->addRect(0, 0, 100, 100, QPen(), Qt::cyan); } protected: virtual void scrollContentsBy(int dx, int dy) override { QGraphicsView::scrollContentsBy(dx, dy); qDebug() << "scrollContentsBy()"; emit viewMoved(); // In case there is an issue doing the repositioning *during* scrolling, // you can comment out the immediate signal emission above and do it on a delayed timer // but it makes "little" difference: // I do get "redraw artifacts" less often, but they still happen after a few clicks // QTimer::singleShot(500, this, &MyGraphicsView::viewMoved); } signals: void viewMoved(); private slots: void repositionFixedToView() { QPointF point(mapToScene(200, 350)); qDebug() << "repositionFixedToView()" << point; fixedToView->setPos(point); // The "redraw artifacts" can be avoided by uncommenting the line below // but I don't believe I should have to do this...? // scene()->invalidate(scene()->sceneRect(), QGraphicsScene::BackgroundLayer); } }; #endif // MAIN_H
- There is a
QGraphicsScene
with a fixed size. - Attached is a
QGraphicsView
with fixed, smaller size. So scrollbars show and can be used to scroll around. - I add a
QGraphicsRectItem
(fixedToView
) to the scene. I wish this item to move with the scrolled view, so it stays at the same place on the view the whole time. - Just for comparison I add another one (
fixedToScene
) which just sits in a fixed place on the scene. - I override
QGraphicsView::scrollContentsBy()
to know when the view is scrolled. I emit a signal from it. - I attach a slot,
repositionFixedToView()
to the "scrolled by" signal. It movesfixedToView
, by mapping its desired, fixed view coordinates to the (new) scene coordinates and callssetPos()
to move that rectangle there. So that rectangle retains its position on the view.
I find the following behaviour:
-
If I drag the scrollbar to move the view everything is fine. The item is moved without "artifact" and retains its position on the view. This may be because the slot is called multiple times during "drag scroll", which is different from the bad state when I...
-
...If I click the scroll buttons I get really bad "artifacts" left. This happens immediately, on first clicks. For all 4 directions.
-
You can see I have commented out code to control whether the signal, and hence the slot action, happens actually inside the
scrollContentsBy()
or delayed with a timer so that it happens after the scrolling is done. In my "real" program it seems to make no difference, they both misbehave equally. In this test it appears a little better: only after about the 3rd click does the artifact appears. (Note that I am not clicking quickly: I am allowing the full time to pass so that all previous scrolling has completed.)
I also have a commented out line to call
scene()->invalidate(scene()->sceneRect(), QGraphicsScene::BackgroundLayer);
in the slot after every scroll. This does fix/clear the artifacts. But I don't think I should have to do this, should I?? When aQGraphicsItem
is moved on a scene/view Qt knows that has uncovered a background area and should calldrawBackground()
to redraw correctly.Is this a bug or have I misunderstood something, please?
- There is a
-
I do a lot of work with
QGraphicsScene
, and claim to know what I am doing :) I have "incorrect artifact" behaviour on scrolling aQGraphicsView
. I do not understand what is happening, and want to know whether this might be a "bug" or whether anyone can explain why I see what I do. (I know all about incorrectboundingRect()
and artifacts, this is not the issue here.)I work with Qt 5.15.3 under Ubuntu 22.04, Xorg (not Wayland). Would someone be kind enough to try it with Qt5 or Qt6 on whatever platform and see if they have the same problem, please?
Reduced code to single file to reproduce. You only have to copy & paste these two files.
// main.cpp #include <QApplication> #include "main.h" int main(int argc, char *argv[]) { QApplication a(argc, argv); MyGraphicsScene scene; scene.setSceneRect(0, 0, 1000, 1000); MyGraphicsView view; view.setScene(&scene); view.setGeometry(200, 200, 500, 500); view.addFixedToViewRect(); view.show(); return a.exec(); }
// main.h #ifndef MAIN_H #define MAIN_H #include <QDebug> #include <QGraphicsRectItem> #include <QGraphicsView> #include <QTimer> class MyGraphicsScene : public QGraphicsScene { Q_OBJECT private: QGraphicsRectItem *fixedToScene; public: MyGraphicsScene(QWidget *parent = nullptr) : QGraphicsScene(parent) { fixedToScene = this->addRect(450, 450, 100, 100, QPen(), Qt::red); } protected: virtual void drawBackground(QPainter *painter, const QRectF &rect) override { qDebug() << "drawBackground()" << rect; // just fill the whole scene, doesn't matter about `rect` painter->fillRect(sceneRect(), Qt::green); } }; class MyGraphicsView : public QGraphicsView { Q_OBJECT private: QGraphicsRectItem *fixedToView; public: MyGraphicsView(QWidget *parent = nullptr) : QGraphicsView(parent) { connect(this, &MyGraphicsView::viewMoved, this, &MyGraphicsView::repositionFixedToView); } void addFixedToViewRect() { fixedToView = scene()->addRect(0, 0, 100, 100, QPen(), Qt::cyan); } protected: virtual void scrollContentsBy(int dx, int dy) override { QGraphicsView::scrollContentsBy(dx, dy); qDebug() << "scrollContentsBy()"; emit viewMoved(); // In case there is an issue doing the repositioning *during* scrolling, // you can comment out the immediate signal emission above and do it on a delayed timer // but it makes "little" difference: // I do get "redraw artifacts" less often, but they still happen after a few clicks // QTimer::singleShot(500, this, &MyGraphicsView::viewMoved); } signals: void viewMoved(); private slots: void repositionFixedToView() { QPointF point(mapToScene(200, 350)); qDebug() << "repositionFixedToView()" << point; fixedToView->setPos(point); // The "redraw artifacts" can be avoided by uncommenting the line below // but I don't believe I should have to do this...? // scene()->invalidate(scene()->sceneRect(), QGraphicsScene::BackgroundLayer); } }; #endif // MAIN_H
- There is a
QGraphicsScene
with a fixed size. - Attached is a
QGraphicsView
with fixed, smaller size. So scrollbars show and can be used to scroll around. - I add a
QGraphicsRectItem
(fixedToView
) to the scene. I wish this item to move with the scrolled view, so it stays at the same place on the view the whole time. - Just for comparison I add another one (
fixedToScene
) which just sits in a fixed place on the scene. - I override
QGraphicsView::scrollContentsBy()
to know when the view is scrolled. I emit a signal from it. - I attach a slot,
repositionFixedToView()
to the "scrolled by" signal. It movesfixedToView
, by mapping its desired, fixed view coordinates to the (new) scene coordinates and callssetPos()
to move that rectangle there. So that rectangle retains its position on the view.
I find the following behaviour:
-
If I drag the scrollbar to move the view everything is fine. The item is moved without "artifact" and retains its position on the view. This may be because the slot is called multiple times during "drag scroll", which is different from the bad state when I...
-
...If I click the scroll buttons I get really bad "artifacts" left. This happens immediately, on first clicks. For all 4 directions.
-
You can see I have commented out code to control whether the signal, and hence the slot action, happens actually inside the
scrollContentsBy()
or delayed with a timer so that it happens after the scrolling is done. In my "real" program it seems to make no difference, they both misbehave equally. In this test it appears a little better: only after about the 3rd click does the artifact appears. (Note that I am not clicking quickly: I am allowing the full time to pass so that all previous scrolling has completed.)
I also have a commented out line to call
scene()->invalidate(scene()->sceneRect(), QGraphicsScene::BackgroundLayer);
in the slot after every scroll. This does fix/clear the artifacts. But I don't think I should have to do this, should I?? When aQGraphicsItem
is moved on a scene/view Qt knows that has uncovered a background area and should calldrawBackground()
to redraw correctly.Is this a bug or have I misunderstood something, please?
- There is a
-
Can't test right now, but does it make a difference when you flip these two lines?
@JonB said in QGraphicsScene incorrect "artifacts" on scrolling "bug"?:
QGraphicsView::scrollContentsBy(dx, dy); emit viewMoved();
-
Tested on:
Windows 10 x64
Qt 5.12.2
Qt 6.6.1
Visual Studio 2022Same result. Afer playing with it it gets artifacts depending on where I click on the scrollbar.
After minimizing the window and showing it again, the artifacts disapear.@ollarch
Thank you for testing! And confirming still present in Qt6 and on a different platform.Yes, it seems to depend on "how much" you scroll. Certainly happens when using the small steps of the scroll buttons. Yes, it will disappear whenever you force the background to get repainted, e.g. after minimize/restore. [Actually on my platform minimize/maximize does not remove the artifacts, I don't think it redraws, but e.g. maximize does.] That's because the artifacts left are "not really there", they just have not been wiped by a background redraw. In my real app I have the view's rubber banding switched on, if you drag the rubberband over the artifact-ed area they also disappear, because that redraws background.
The really strange one is if you comment out the emit/signal/slot which is executing within the
scrollContentBy()
and replace with theQTimer::singleShot()
call. One might say it goes wrong because I am doing the item moving whilst handling the scroll by. But with the timer we are saying "if you scroll the window and then later on move aQGraphicsItem
you get artifacts".Now it would be really helpful if a Qt expert commented on whether they think anything is incorrect in my code, this behaviour is consequential on the fact that I am filling the background....?