QWidgets Leaving Artifacts Of Previous Paint

1k views Asked by At

I have an application that interactively moves objects derived from QWidget around the screen. Sometimes, they leave artifacts of the previous geometries which get cleaned up when I switch focus to another application, but stay on the screen until then.

Here are some examples of the artifacts I see when re-sizing a widget interactively:

artifacts

I was able to create a fairly simple test case that replicates a similar issue automatically (although only leaving one-line thick artifacts). In this example, a single vertical line artifact is consistently left after each horizontal "flip" of the geometry:

#include <QApplication>
#include <QDialog>
#include <QPainter>
#include <QMouseEvent>
#include <QDebug>
#include <QWidget>

class TestWidget : public QWidget
{
    Q_OBJECT
public:
    explicit TestWidget(QWidget *parent = 0);

private:
    void paintEvent(QPaintEvent *e);
    QRect thisRect();
    void timerEvent(QTimerEvent *t);
};


TestWidget::TestWidget(QWidget *parent) :
    QWidget(parent)
{

    setGeometry(100,100, 100,100);
    startTimer(5);
}

QRect TestWidget::thisRect()
{
    return QRect(QPoint(),geometry().size());
}

void TestWidget::timerEvent(QTimerEvent *t)
{
    QRect geo = geometry();

    static bool growUp = false;
    static bool flipUp = false;
    static bool flipOver = false;
    static bool growOver = false;
    static int delta = 1;
    static int delta2 = 1;
    static int tick = 0;
    static int enDelta = 1;
    tick++;

    if(flipUp)
       geo.adjust(0,enDelta * delta,0,0);
    else
       geo.adjust(0,0,0,enDelta * delta);


    if(tick%3==0)
          enDelta = 0;
      else
          enDelta = 1;

    if(geo.height()>100)
    {
        if(growUp)
            delta = 1;
        else
            delta = -1;
        growUp = !growUp;
    }

    if(geo.height() == 0)
        flipUp = !flipUp;


    if(flipOver)
       geo.adjust(delta2 ,0, 0,0);
    else
       geo.adjust(0,0,delta2 ,0);


    if(geo.width()>100)
    {
        if(growOver)
            delta2 = 1;
        else
            delta2  = -1;
        growOver = !growOver;
    }

    if(geo.height() == 0)
        flipOver = !flipOver;

    setGeometry(geo);

}



void TestWidget::paintEvent(QPaintEvent *e)
{
    QBrush b(QColor(55,200,55,190));
    QPainter p(this);
    p.setBrush(b);
    //p.drawRect(QRect(QPoint,this->geometry().size()));
    //p.drawRect(QRect(QPoint,geometry().size()));
    if(thisRect().width() != 0 && thisRect().height() != 0)
    {
        p.drawRect(thisRect());
        qDebug() << "painted rect is " << thisRect();
        qDebug() << "painted geo  is " << geometry();
    }
}

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    //MainWindow w;
    //w.show();
    QDialog d;
    d.show();

    int i;

    TestWidget *tw;

    for(i=0; i<1; i++) //2000; i++)
    {
        tw = new TestWidget(&d);
        tw->show();
    }

    return a.exec();
}

Is there some sort of synchronization I am missing or is this a bug? (I started to look into the event queue and backing store algorithms, but as soon as I change focus from the test application to Qt Creator so I can single step, the artifacts go away. This makes it a little tricky to trace. Also, the fact that it is double buffered makes it harder to see exactly when items are rendered to the staging buffer, since there is no visual feedback as it happens.)

After adding adding an:

update();

(and as mentioned in the comment below), there were significant improvements, but the otherwise identical code, still produced an artifact which is shown here in the red circle to which the red arrow is pointing:

enter image description here

2

There are 2 answers

0
user3761340 On

I found what appears to be a solution. The solution was to update() (as suggested by vhancho), but specifically to update the parentWidget of the object after altering its geometry.

(This makes sense to me since a child's geometry is relative to the parentWidget, not the child itself. I also had another similar episode of confusion when updating the geometry topLeft, which, of course, will always be in the coordinates of the parent widget. drawRect, by contrast, will be in the coordinates of the object widget in which it is called.)

Here is the corrected test case:

#include <QApplication>
#include <QDialog>
#include <QPainter>
#include <QMouseEvent>
#include <QDebug>
#include <QWidget>

class TestWidget : public QWidget
{
    Q_OBJECT
public:
    explicit TestWidget(QWidget *parent = 0);

private:
    void paintEvent(QPaintEvent *e);
    QRect thisRect();
    void timerEvent(QTimerEvent *t);
};


TestWidget::TestWidget(QWidget *parent) :
    QWidget(parent)
{

    setGeometry(100,100, 100,100);
    startTimer(5);
}

QRect TestWidget::thisRect()
{
    return QRect(QPoint(),geometry().size());
}

void TestWidget::timerEvent(QTimerEvent *t)
{
    QRect geo = geometry();

    static bool growUp = false;
    static bool flipUp = false;
    static bool flipOver = false;
    static bool growOver = false;
    static int delta = 1;
    static int delta2 = 1;
    static int tick = 0;
    static int enDelta = 1;
    tick++;

    if(flipUp)
       geo.adjust(0,enDelta * delta,0,0);
    else
       geo.adjust(0,0,0,enDelta * delta);


    if(tick%3==0)
          enDelta = 0;
      else
          enDelta = 1;

    if(geo.height()>100)
    {
        if(growUp)
            delta = 1;
        else
            delta = -1;
        growUp = !growUp;
    }

    if(geo.height() == 0)
        flipUp = !flipUp;


    if(flipOver)
       geo.adjust(delta2 ,0, 0,0);
    else
       geo.adjust(0,0,delta2 ,0);


    if(geo.width()>100)
    {
        if(growOver)
            delta2 = 1;
        else
            delta2  = -1;
        growOver = !growOver;
    }

    if(geo.height() == 0)
        flipOver = !flipOver;

    setGeometry(geo);

    parentWidget()->update();   //this line removes all artifacts from this test case

}



void TestWidget::paintEvent(QPaintEvent *e)
{
    QBrush b(QColor(55,200,55,190));
    QPainter p(this);
    p.setBrush(b);
    //p.drawRect(QRect(QPoint,this->geometry().size()));
    //p.drawRect(QRect(QPoint,geometry().size()));
    if(thisRect().width() != 0 && thisRect().height() != 0)
    {
        p.drawRect(thisRect());
        qDebug() << "painted rect is " << thisRect();
        qDebug() << "painted geo  is " << geometry();
    }
}

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    //MainWindow w;
    //w.show();
    QDialog d;
    d.show();

    int i;

    TestWidget *tw;

    for(i=0; i<1; i++) //2000; i++)
    {
        tw = new TestWidget(&d);
        tw->show();
    }

    return a.exec();
}

0
user3761340 On

This is more of a comment than an answer, but since comments have size and other restrictions, and I think this information may be valuable to others, I will include it here.

I am back to thinking this might be a bug. I just now used the QRubberBand example pattern from the docs, and see the same type of artifacts if I quickly rotate the rubberband around its origin. Here is the full code of the example I created from the docs:

#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QRubberBand>
class Widget : public QWidget
{ Q_OBJECT void mousePressEvent(QMouseEvent *event); void mouseMoveEvent(QMouseEvent *event); void mouseReleaseEvent(QMouseEvent *event); public: Widget(QWidget *parent = 0); ~Widget(); private: QRubberBand rubberBand; QPoint origin; }; #endif // WIDGET_H #include <QMouseEvent> Widget::Widget(QWidget *parent) : QWidget(parent) , rubberBand(QRubberBand::Rectangle, this) { } Widget::~Widget() { }
void Widget::mousePressEvent(QMouseEvent *event)
{
origin = event->pos();
//if (!rubberBand)
// rubberBand = new QRubberBand(QRubberBand::Rectangle, this);
rubberBand.setGeometry(QRect(origin, QSize()));
rubberBand.show();
}
void Widget::mouseMoveEvent(QMouseEvent *event)
{
rubberBand.setGeometry(QRect(origin, event->pos()).normalized());
}
void Widget::mouseReleaseEvent(QMouseEvent *event)
{
rubberBand.hide();
// determine selection, for example using QRect::intersects()
// and QRect::contains().
}
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}

Again, this can be solved by adding update() just after the geometry modification, but since that is not part of the documented pattern example, I expect it was not considered to be necessary. Is that the appropriate expectation?

Here is the modification that solves the artifact problem:

void Widget::mouseMoveEvent(QMouseEvent *event)
{
rubberBand.setGeometry(QRect(origin, event->pos()).normalized());
update();
}

This is also filed as a bug here:

https://bugreports.qt-project.org/browse/QTBUG-42827