Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Draw an item in a static location relative to the QGraphicsView

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?

like image 622
Cory Klein Avatar asked Aug 23 '13 20:08

Cory Klein


People also ask

What is qgraphicsview and how does it work?

QGraphicsScene also provides functionality that lets you efficiently determine both the location of items, and for determining what items are visible within an arbitrary area on the scene. With the QGraphicsView widget, you can either visualize the whole scene, or zoom in and view only parts of the scene.

What is scenerect in qgraphicsview?

If unset, or if set to a null QRectF, sceneRect () will return the largest bounding rect of all items on the scene since the scene was created (i.e., a rectangle that grows when items are added to or moved in the scene, but never shrinks). See also width (), height (), and QGraphicsView::sceneRect.

What is the use of the class qgraphicsitems?

The class serves as a container for QGraphicsItems. It is used together with QGraphicsView for visualizing graphical items, such as lines, rectangles, text, or even custom items, on a 2D surface.

How does the selection work in qgraphicsscene?

The selection changes whenever an item is selected or unselected, a selection area is set, cleared or otherwise changed, if a preselected item is added to the scene, or if a selected item is removed from the scene. QGraphicsScene emits this signal only once for group selection operations.


1 Answers

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.

enter image description here

// 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"
like image 61
Kuba hasn't forgotten Monica Avatar answered Sep 28 '22 03:09

Kuba hasn't forgotten Monica