Why are my custom graphical items constantly repai

2019-07-12 09:20发布

问题:

My application has a QMdiArea, inside which subwindows are shown which contain instances of QGraphicsView-derived views (GfxInteractiveView), which in turn visualize scenes containing custom QGraphicsItem-derived items.

/// An image item which is displayed as the background of a scene.
class GfxImageItem : public QObject, public QGraphicsPixmapItem
{
    Q_OBJECT

protected:
    GfxInteractiveImgView *view_;
    QPixmap pixmap_;

    QList<GfxPointItem *> pointItems_;

public:
    GfxImageItem(GfxInteractiveImgView *view);

    // set the pixmap to the image loaded from this path
    bool load(const QString &path);

    // normally not overriden, here just for tracing 
    virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
    {
        QLOG("Painting image"); // homebrew macro for tracing
        QGraphicsPixmapItem::paint(painter, option, widget);
    }
};

/// A generated image drawn at the foreground of a scene. 
class GfxMapItem : public QGraphicsPixmapItem
{

public:
    GfxMapItem(QGraphicsItem *item);

    void regenerateMap();

    // same as GfxMapItem, here just for tracing
    virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
};

So far so good, but I also have an another item GfxPointItem which is fully custom with a paint() that actually does something, and when I add it to the scene, CPU usage jumps to full on one of the cores as all the items in the hierarchy enter a repaint loop which continues for as long the window is visible or the custom item is present in the scene. A glimpse at the stack when this happens shows that none of my function is responsible for calling paint(), the event is generated on the event loop. Here's the code for GfxPointItem:

/// A draggable item representing an analysis point on the map, drawn on top of the map.
class GfxPointItem : public QGraphicsObject
{
    Q_OBJECT

protected:
    GfxImageItem *imgParent_;
    GfxInteractiveImgView *view_;
    int size_, fontSize_;
    QColor color_, oldColor_;
    qreal paintScale_;
    QRectF boundingRect_;
    bool active_, static_;
    QStaticText pointText_, valueText_;

public:
    GfxPointItem(GfxImageItem *parent, GfxInteractiveImgView *view, const QPointF &pos);

    void setActive(bool arg);
    void setStatic(bool arg);
    void setColor(const QColor &color) { color_ = color; update(); }

    virtual QRectF boundingRect() const { return boundingRect_; }
    virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);

signals:
    void changedPos(int index, QPointF newpos);

public slots:
    void setPaintScale(qreal value);
    void setPointText(const QString &text);
    void setValueText(const QString &text);

protected:
    void updateBoundRect();
    void updatePointText();
    QPoint valueTextPos() const;
    QPoint pointTextPos() const;
};


GfxPointItem::GfxPointItem(GfxImageItem *parent, GfxInteractiveImgView *view, const QPointF &pos, int index) : QGraphicsObject(parent),
    imgParent_(parent),
    view_(view),
    index_(index), size_(8), fontSize_(8), 
    color_(Qt::black),
    paintScale_(view->invscale()),
    drawLabel_(true), active_(false), static_(false), floatPrec_(false)
{
    QLOGX("Creating new at " << pos.x() << "," << pos.y() << ", index: " << index);
    setPos(pos);
    updatePointText();
    connect(view, SIGNAL(scaleChanged(qreal)), this, SLOT(setPaintScale(qreal)));
}

/// An inactive point wil not respond to hover events and will not be movable.
void GfxPointItem::setActive(bool arg)
{
    QLOGX("Setting active state: " << arg);
    active_ = arg;
    setAcceptHoverEvents(arg);
    setFlag(QGraphicsItem::ItemIsMovable, arg);
}

/// Set or disable static mode on point. In static mode, the point text is not updated when changing position, so it can retain a constant label.
void GfxPointItem::setStatic(bool arg)
{
    QLOGX("Setting static mode: " << arg);
    static_ = arg;
}

void GfxPointItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    QLOGX("Painting point");
    static const int margin = 2;
    setScale(paintScale_);
    QPen pen;
    pen.setWidth(1);
    pen.setColor(color_);
    painter->setPen(pen);

    // paint the centerpoint marker (two crossed lines)
    painter->drawLine(QPointF(-size_, 0), QPointF( size_, 0));
    painter->drawLine(QPointF(0, -size_), QPointF(0,  size_));

    // the label box and the two static text lines inside
    pen.setWidth(0);
    painter->setPen(pen);

    QFont font;
    font.setPointSize(fontSize_);
    painter->setFont(font);

    QBrush brush(Qt::SolidPattern);
    brush.setColor(QColor(255, 255, 127)); // sand yellow
    painter->setBrush(brush);

    // point text size, value text size
    QSizeF pts = pointText_.size(), 
           vts = valueText_.size();

    // point text position, value text position
    QPoint vtp = valueTextPos(),
           ptp = pointTextPos();

    // point id and position label and value indicator label in a rectangular box
    int shift = (valueText_.text().isEmpty()) ? 0 : vts.height();
    QRectF rect(ptp.x()-margin, ptp.y(), std::max(pts.width(), vts.width())+margin, pts.height() + shift);
    painter->drawRect(rect);
    painter->drawStaticText(ptp, pointText_);
    painter->drawStaticText(vtp, valueText_);
}

void GfxPointItem::setPaintScale(qreal value) 
{ 
    QLOGX("Updating scale: " << value);
    paintScale_ = value; 
    update(); 
}

void GfxPointItem::setPointText(const QString &text)
{
    QLOGX("Setting text: " << text);
    pointText_.setText(text);
    updateBoundRect(); 
    update();
}

void GfxPointItem::setValueText(const QString &text) 
{ 
    QLOGX("Setting value text: " << text);
    valueText_.setText(text); 
    updateBoundRect(); 
    update();
}

void GfxPointItem::updateBoundRect()
{
    QLOGX("Updating bounding rect");
    boundingRect_.setRect(- size_, pointTextPos().y(), 
        (2 * size_) + std::max(pointText_.size().width(), valueText_.size().width()), 
        (2 * size_) + pointText_.size().height() + valueText_.size().height());
}

void GfxPointItem::updatePointText()
{
    QLOGX("Updating point text");
    pointText_.setText("P" + QString::number(index_ + 1) + " (" 
        + (floatPrec_ ? QString::number(pos().x()) : QString::number(std::floor(pos().x()))) + "," 
        + (floatPrec_ ? QString::number(pos().y()) : QString::number(std::floor(pos().y()))) + ")");

    updateBoundRect();
    update();
}

void GfxPointItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
    QLOGX("Mouse move");
    QPointF p = pos();
    QPointF ep = event->pos();

    QGraphicsItem::mouseMoveEvent(event);

    if (!static_) updatePointText();
    emit changedPos(index_, pos());
}

I've no idea why the event loop keeps repainting the items. I wouldn't even notice this but I hit a problem with showing a standard QFileDialog::getExistingDirectory() which wouldn't even manage to get painted while a window with the graphical items was visible, because the repaints stole all execution time on the main thread away from it, resulting in a freeze. After that, I added tracing statements to the paint functions and came up with tens of thousands of entries in the logfile after a couple seconds while the application was seemingly doing nothing. In task manager, the CPU usage is around 25% when the window is visible (on a 4-core processor) and it drops to 0 when I close it.

None of my code forces these repaints, so what is? How can I debug the event loop to find the source of this behaviour, which slows down my application and causes freezes?

Thanks!

Qt version is the newest 5.0.2 binary bundle and the application is compiled with Visual C++ 2012 for x64.

回答1:

It would seem to me that when calling setScale in paint it connects to setPaintScale, which then calls the QWidget's update function, which then invalidates the area and when it gets back to the main event loop causes a repaint.

So you end up getting 1 paint required event every time you call the paint method.