Draw an item in a static location relative to the

2019-01-14 14:18发布

问题:

I would like to draw a notification that always appears in the upper right corner of my QGraphicsView. However, QGraphicsItems positions are specified in scene coordinates, so if the user panned/zoomed to view a different part of the scene, this notification would move off screen.

I have figured out that I could simulate this behavior by moving and scaling the notification any time the current view changes. But this seems terribly ineffective and not at all elegant.

It seems that QGraphicsView should support this kind of behavior. The docs mention a flag ItemIsPanel that sounds hopeful, but mentions nothing about static placement in the view. ItemIgnoresTransformations also will help with scaling/zooming, but not panning.

Is there any built-in Qt functionality that supports this behavior?

回答1:

The naive solution of having the notification be a part of the original scene is bad - it breaks the model-view separation. You can have multiple views, all showing one scene, but generally on only one of them can the notification appear as desired.

Another simple way to do it would be to overlay a QWidget notification on top of your view. The problem is that on some architectures, overlaying regular QWidgets on top of accelerated QGLWidgets will make the former disappear. Do note that a QGraphicsView's viewport may be a QGLWidget!

Thus, the only portable solution is to explicitly do the painting on top of everything else in QGraphicsSceneView's viewport().

Below is a complete example.

// main.cpp
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsItem>

qreal rnd() { return qrand() / (float)RAND_MAX; }

class OverlaidGraphicsView : public QGraphicsView
{
    Q_OBJECT
    QGraphicsScene * m_overlayScene;
public:
    explicit OverlaidGraphicsView(QWidget* parent = 0) :
        QGraphicsView(parent), m_overlayScene(NULL) {}
    explicit OverlaidGraphicsView(QGraphicsScene * scene = 0, QWidget * parent = 0) :
        QGraphicsView(scene, parent), m_overlayScene(NULL) {}
    void setOverlayScene(QGraphicsScene * scene) {
        if (scene == m_overlayScene) return;
        m_overlayScene = scene;
        connect(scene, SIGNAL(changed(QList<QRectF>)), SLOT(overlayChanged()));
        update();
    }
    QGraphicsScene * overlayScene() const { return m_overlayScene; }
    void paintEvent(QPaintEvent *ev) {
        QGraphicsView::paintEvent(ev);
        if (m_overlayScene) paintOverlay();
    }
    virtual void paintOverlay() {
        QPainter p(viewport());
        p.setRenderHints(renderHints());
        m_overlayScene->render(&p, viewport()->rect());
    }
    Q_SLOT void overlayChanged() { update(); }
};

class Window : public QWidget
{
    QGraphicsScene scene, notification;
    OverlaidGraphicsView * view;
    QGraphicsSimpleTextItem * item;
    int timerId;
    int time;
public:
    Window() :
        view(new OverlaidGraphicsView(&scene, this)),
        timerId(-1), time(0)
    {
        for (int i = 0; i < 20; ++ i) {
            qreal w = rnd()*0.3, h = rnd()*0.3;
            scene.addEllipse(rnd()*(1-w), rnd()*(1-h), w, h, QPen(Qt::red), QBrush(Qt::lightGray));
        }
        view->fitInView(0, 0, 1, 1);
        view->setResizeAnchor(QGraphicsView::AnchorViewCenter);
        view->setRenderHint(QPainter::Antialiasing);
        view->setOverlayScene(&notification);
        item = new QGraphicsSimpleTextItem();
        item->setPen(QPen(Qt::blue));
        item->setBrush(Qt::NoBrush);
        item->setPos(95, 0);
        notification.addItem(item);
        notification.addRect(0, 0, 100, 0, Qt::NoPen, Qt::NoBrush); // strut
        timerId = startTimer(1000);
        QTimerEvent ev(timerId);
        timerEvent(&ev);
    }
    void resizeEvent(QResizeEvent * ev) {
        view->resize(size());
        view->fitInView(0, 0, 1, 1, Qt::KeepAspectRatio);
        QWidget::resizeEvent(ev);
    }
    void timerEvent(QTimerEvent * ev) {
        if (ev->timerId() != timerId) return;
        item->setText(QString::number(time++));
    }
};

int main(int argc, char ** argv)
{
    QApplication a(argc, argv);

    Window window;
    window.show();
    a.exec();
}

#include "main.moc"