Custom widget is flickering and clipping while moving
-
I already posted this question on stackoverflow, but after thinking about it, I am probably more likely to get a useful answer from here.
I'll give you a minimal reproduceable example that was a part of the more complex widget. Here we just have a custom widget(called
MovableItem) with a simplepaintEvent. Widget is created, placed onto the central widget and moved to mouse position onMainWindow::mousePressEvent-s.When moving, it seems like the widget is getting clipped at the side it's moving towards.
mainwindow.h
#include <QMainWindow> #include <QMouseEvent> #include <QPropertyAnimation> #include "movableitem.h" class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); void mousePressEvent(QMouseEvent *event) override; QWidget* mItem; };mainwindow.cpp
#include "mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { resize(640, 480); QWidget* central_widget = new QWidget(this); mItem = new MovableItem(central_widget); mItem->move(20, 20); setCentralWidget(central_widget); } void MainWindow::mousePressEvent(QMouseEvent *event) { QPoint pos = event->pos(); QPropertyAnimation* anim = new QPropertyAnimation(mItem, "geometry"); anim->setDuration(750); anim->setStartValue(QRect(mItem->x(), mItem->y(), mItem->width(), mItem->height())); anim->setEndValue(QRect(pos.x(), pos.y(), mItem->width(), mItem->height())); anim->start(); } MainWindow::~MainWindow() {}movableitem.h
#include <QWidget> #include <QPainter> #include <QPainterPath> class MovableItem : public QWidget { Q_OBJECT public: MovableItem(QWidget *parent = nullptr); QSize sizeHint() const override; void paintEvent(QPaintEvent *event) override; };movableitem.cpp
#include "movableitem.h" MovableItem::MovableItem(QWidget *parent) : QWidget(parent) { setParent(parent); } QSize MovableItem::sizeHint() const { return QSize(150, 40); } void MovableItem::paintEvent(QPaintEvent *event) { QRect r = rect(); QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); QPainterPath path; path.addRoundedRect(r, 5, 5); QBrush brush(QColor(217, 217, 217)); painter.fillPath(path, brush); painter.drawPath(path); }Example

As you can see, movement is not fluid, but choppy. I have no idea what is happening. Am I doing something completely wrong, do I need to implement some additional functions, is double buffering needed, is this because of Qt's automatic clipping ? Should I look into QGraphicsView to solve this problem ?
-
I already posted this question on stackoverflow, but after thinking about it, I am probably more likely to get a useful answer from here.
I'll give you a minimal reproduceable example that was a part of the more complex widget. Here we just have a custom widget(called
MovableItem) with a simplepaintEvent. Widget is created, placed onto the central widget and moved to mouse position onMainWindow::mousePressEvent-s.When moving, it seems like the widget is getting clipped at the side it's moving towards.
mainwindow.h
#include <QMainWindow> #include <QMouseEvent> #include <QPropertyAnimation> #include "movableitem.h" class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); void mousePressEvent(QMouseEvent *event) override; QWidget* mItem; };mainwindow.cpp
#include "mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { resize(640, 480); QWidget* central_widget = new QWidget(this); mItem = new MovableItem(central_widget); mItem->move(20, 20); setCentralWidget(central_widget); } void MainWindow::mousePressEvent(QMouseEvent *event) { QPoint pos = event->pos(); QPropertyAnimation* anim = new QPropertyAnimation(mItem, "geometry"); anim->setDuration(750); anim->setStartValue(QRect(mItem->x(), mItem->y(), mItem->width(), mItem->height())); anim->setEndValue(QRect(pos.x(), pos.y(), mItem->width(), mItem->height())); anim->start(); } MainWindow::~MainWindow() {}movableitem.h
#include <QWidget> #include <QPainter> #include <QPainterPath> class MovableItem : public QWidget { Q_OBJECT public: MovableItem(QWidget *parent = nullptr); QSize sizeHint() const override; void paintEvent(QPaintEvent *event) override; };movableitem.cpp
#include "movableitem.h" MovableItem::MovableItem(QWidget *parent) : QWidget(parent) { setParent(parent); } QSize MovableItem::sizeHint() const { return QSize(150, 40); } void MovableItem::paintEvent(QPaintEvent *event) { QRect r = rect(); QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); QPainterPath path; path.addRoundedRect(r, 5, 5); QBrush brush(QColor(217, 217, 217)); painter.fillPath(path, brush); painter.drawPath(path); }Example

As you can see, movement is not fluid, but choppy. I have no idea what is happening. Am I doing something completely wrong, do I need to implement some additional functions, is double buffering needed, is this because of Qt's automatic clipping ? Should I look into QGraphicsView to solve this problem ?
@x86me
Hi
Ok,
I am not sure you can remove flickering into paintEvent of your widget, but maybe I'm wrong.
Because I think your flickering depends on of the parent's painter, so you could move draw to parent.A possible workaround could be:
Remove paintEvent from your widget
Add paintEvent into you MainWindow
So here you can find a workaround for you, but I am not sure is the best way to do this ;)void MainWindow::paintEvent(QPaintEvent *event)
{
setUpdatesEnabled(false);QRect r = mItem->geometry(); QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); QPainterPath path; path.addRoundedRect(r, 5, 5); QBrush brush(QColor(217, 217, 217)); painter.fillPath(path, brush); painter.drawPath(path); setUpdatesEnabled(true);}
-
When animating anything painting should be as fast as possible, so simplify if you can, e.g. use
painter.drawRoundedRect(r, 5, 5);instead of path rendering.You're using a wrong rectangle. Antialiasing is masking it a little, but if you disable it you'll see that you're cutting off the bottom and right part of the rectangle i.e. you should be using
rect().adjusted(0,0,-1,-1). If you want antialiasing enabled you need to also leave some space for it , sorect().adjusted(1,1,-1,-1).In general antialiasing and animations don't go well together, unless you use some sophisticated temporal solution, which Qt doesn't. It can result in ghosting, shimmering and all kinds of artifacts, like what you see. Consider disabling antialiasing when animation starts and turn it back on when animation finishes.
On every mouse press you're creating new animation object and never delete it. Consider deleting it automatically when animation finishes i.e.
anim->start(QAbstractAnimation::DeleteWhenStopped). -
One more note - animations like this should not have a constant time, but rather constant speed.
If you give it a constant time like here you will have fast moving object when you click far away and slow movement when you click close. That's because object needs to cover different distances in the same amount of time.
You should rather set the animation duration based on the distance the object is to travel i.e. divide your constant by the distance between start and end point. This will result in smoother feel.
Also consider using different easing curve for the animation. A linear move does not look good for object movement. It should have some easing on the ends so it looks more physical i.e. it accelerates and decelerates.
-
@x86me
Hi
Ok,
I am not sure you can remove flickering into paintEvent of your widget, but maybe I'm wrong.
Because I think your flickering depends on of the parent's painter, so you could move draw to parent.A possible workaround could be:
Remove paintEvent from your widget
Add paintEvent into you MainWindow
So here you can find a workaround for you, but I am not sure is the best way to do this ;)void MainWindow::paintEvent(QPaintEvent *event)
{
setUpdatesEnabled(false);QRect r = mItem->geometry(); QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); QPainterPath path; path.addRoundedRect(r, 5, 5); QBrush brush(QColor(217, 217, 217)); painter.fillPath(path, brush); painter.drawPath(path); setUpdatesEnabled(true);}
Thank you for your answers they helped a lot.
I ended up leaving theMovableItem'spaintEventuntouched because it turns out that just calling setUpdatesEnabled inside the MainWindow::paintEvent solves the problem.void MainWindow::paintEvent(QPaintEvent *event) { setUpdatesEnabled(false); QMainWindow::paintEvent(event); setUpdatesEnabled(true); }I know there are few imperfections in the example like handling animations and wrong rect dimensions which I noticed after posting, but even after correcting them, flickering persisted. Turning off antialiasing didn't help either(assuming
painter.setRenderHint(QPainter::Antialiasing, false)turns it off).This was originally a navigation item that you could drag and drop inside the custom layout. So after testing it, repeatedly calling code like this(not the prettiest I have to admit) also works without any flickering:
item->widget()->parentWidget()->setUpdatesEnabled(false); item->widget()->move(new_x, new_y); item->widget()->parentWidget()->setUpdatesEnabled(true);