QWidgets 留下以前油漆的伪影

Posted

技术标签:

【中文标题】QWidgets 留下以前油漆的伪影【英文标题】:QWidgets Leaving Artifacts Of Previous Paint 【发布时间】:2014-11-21 09:34:54 【问题描述】:

我有一个应用程序可以交互式地在屏幕上移动从 QWidget 派生的对象。有时,当我将焦点切换到另一个应用程序时,它们会留下先前几何图形的伪影,但会一直留在屏幕上。

以下是我在交互式调整小部件大小时看到的一些工件示例:

我能够创建一个相当简单的测试用例,自动复制类似的问题(尽管只留下一行粗的工件)。在此示例中,几何图形的每次水平“翻转”后始终会留下一条垂直线伪影:

#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();

我是否缺少某种同步,或者这是一个错误? (我开始研究事件队列和后备存储算法,但是一旦我将焦点从测试应用程序转移到 Qt Creator 以便我可以单步执行,工件就消失了。这使得跟踪有点棘手。另外,由于它是双缓冲的,因此很难准确查看项目何时渲染到暂存缓冲区,因为它发生时没有视觉反馈。)

添加后添加:

update();

(正如下面的评论中提到的),有显着的改进,但其他方面相同的代码仍然产生了一个工件,这里显示在红色箭头所指的红色圆圈中:

【问题讨论】:

如果您尝试在此行之后调用update() 怎么办:setGeometry(geo); 这绝对有帮助。谢谢你。我还有一个小神器。我在上面添加了剩余神器的图片。 我找到了解决方案。解决方案是在更改子对象的几何形状后更新() parentWidget。 (这对我来说很有意义,因为孩子的几何形状是相对于 parentWidget,而不是孩子本身。我现在认为这很可能不是一个错误,但如果有一个很好的参考,我也想更好地理解渲染内部可用,甚至是几句智慧的话。) 【参考方案1】:

我找到了似乎是一个解决方案。解决方案是 update() (如 vhancho 所建议的那样),但特别是在更改对象的几何形状后更新对象的 parentWidget。

(这对我来说很有意义,因为孩子的几何是相对于 parentWidget,而不是孩子本身。在更新几何 topLeft 时,我也有另一个类似的困惑,当然,它总是在坐标父小部件。相比之下,drawRect 将位于调用它的对象小部件的坐标中。)

这是修正后的测试用例:

#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();

【讨论】:

【参考方案2】:

这更像是一个评论而不是一个答案,但由于 cmets 有大小和其他限制,我认为这些信息可能对其他人有价值,所以我将其包含在此处。

我又开始认为这可能是一个错误。我刚刚使用了文档中的 QRubberBand 示例模式,如果我围绕其原点快速旋转橡皮筋,则会看到相同类型的工件。这是我从文档中创建的示例的完整代码:

#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();

同样,这可以通过在几何修改之后添加 update() 来解决,但由于这不是记录的模式示例的一部分,我认为它不是必需的。这是适当的期望吗?

这里是解决神器问题的修改:

void Widget::mouseMoveEvent(QMouseEvent *event)

rubberBand.setGeometry(QRect(origin, event->pos()).normalized());
update();

这也作为一个错误在这里提交:

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

【讨论】:

以上是关于QWidgets 留下以前油漆的伪影的主要内容,如果未能解决你的问题,请参考以下文章

从 numpy 数组中删除类似方波的伪影

使用 Accelerate 缩放 Ycbcr (420f) 时的伪影

快速的音量变化会导致 AVAudioPlayer 中的伪影

IOSurfaces - 视频中的伪影,无法抓取视频表面

如何摆脱使用 SkiaSharp 创建的 ImageSource 中的伪影

使用 ggplot2 绘制 shapefile 的伪影