Home About

How to access QGraphicsItem under the mouse cursor



Consider you have a QGraphicsView with a few QGraphicsLineItems, and you want it so that when hovering a line, it changes its color.

That means you need to track mouse movement, and then, somehow, get the QGraphicsLineItem under the mouse cursor. If we try to fetch it based on where we might think it is, we will get a null item; that means the position we used is wrong. In short, the whole thing depends on us using the correct coordinates.



Since QGraphicsItems live inside QGraphicsScene, QGraphicsView::mapToScene is used to convert the mouse event position from the QGraphicsView::viewport coordinates to that of the scene's.

This is mentioned in QGraphicsView::mapToScene's documentation:
QPointF QGraphicsView::mapToScene(const QPoint &point) const
Returns the viewport coordinate point mapped to scene coordinates.

Minimal demonstration:


#include <QApplication>
#include <QtWidgets>

class EventFilter : public QObject
{
protected:
    bool eventFilter(QObject *obj, QEvent *event) override
    {
        if (event->type() == QEvent::MouseMove)
        {
            QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
            QGraphicsView *view = qobject_cast<QGraphicsView*>(obj->parent());

            //this is the adjustment needed, the items live in the scene, so we need to map the cursor's position to it
            QPointF currentPos = view->mapToScene(mouseEvent->position().toPoint());

            QGraphicsLineItem* item = static_cast<QGraphicsLineItem*>(view->scene()->itemAt(currentPos, view->transform()));
            if(item)
            {
                //visual debugging
                item->setPen(QPen(Qt::yellow));
                QTimer::singleShot(100, [item]() { item->setPen(QPen(Qt::black)); } );
            }
        }

        return QObject::eventFilter(obj, event);
    }
};

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QGraphicsScene scene;
    scene.setBackgroundBrush(Qt::darkRed);
    QGraphicsView view(&scene);
    //mouse tracking to get mouse move events
    view.setMouseTracking(true);

    scene.addLine(0, 0, 100, 100);
    scene.addLine(0, 100, 100, 0);

    EventFilter eventFilter;
    //QGraphicsView is a QAbstractScrollArea, so it's a container with a contained widget, called "viewport"
    //the viewport is the one that receives mouse events because it's on top
    //hence why we installl the event filter on it
    view.viewport()->installEventFilter(&eventFilter);

    view.show();

    return app.exec();
}

Expand the GIF Two lines in a graphics view turn their color to yellow when hovered



Another solution

musicamante said:

[…] graphics item [sic] natively support hover events (as long as setAcceptHoverEvents() is used), […]
QGraphicsItem::setAcceptHoverEvents documentation says:
If enabled is true, this item will accept hover events; otherwise, it will ignore them. By default, items do not accept hover events.
And to use those hover events, we could use QGraphicsItem::installSceneEventFilter:
Installs an event filter for this item on filterItem, causing all events for this item to first pass through filterItem's sceneEventFilter() function.
Here's a minimal reproducible example demonstrating the implementation of this approach:
    
#include <QApplication>
#include <QtWidgets>

//QGraphicsItem is an abstract class, so one of its subclasses must be used instead
class SceneEventFilter : public QGraphicsLineItem
{
protected:
    bool sceneEventFilter(QGraphicsItem *watched, QEvent *event) override
    {
        if (event->type() == QEvent::GraphicsSceneHoverEnter)
        {
            QGraphicsLineItem* item = static_cast<QGraphicsLineItem*>(watched);
            if(item)
            {
                item->setPen(QPen(Qt::yellow, 5));
            }
        }
        if (event->type() == QEvent::GraphicsSceneHoverLeave)
        {
            QGraphicsLineItem* item = static_cast<QGraphicsLineItem*>(watched);
            if(item)
            {
                item->setPen(QPen(Qt::black, 5));
            }
        }

        return QGraphicsLineItem::sceneEventFilter(watched, event);
    }
};

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QGraphicsScene scene;
    scene.setBackgroundBrush(Qt::darkRed);
    QGraphicsView view(&scene);

    SceneEventFilter sceneEventFilter;

    //the item acting as a filter must be added to the same scene
    //see QGraphicsItem::installSceneEventFilter docs
    scene.addItem(&sceneEventFilter);
    scene.addLine(0, 0, 100, 100, QPen(Qt::black, 5));
    scene.addLine(0, 100, 100, 0, QPen(Qt::black, 5));

    scene.items()[0]->setAcceptHoverEvents(true);
    scene.items()[1]->setAcceptHoverEvents(true);

    scene.items()[0]->installSceneEventFilter(&sceneEventFilter);
    scene.items()[1]->installSceneEventFilter(&sceneEventFilter);

    view.show();

    return app.exec();
}
Expand the GIF Two lines in a graphics view turn their color to yellow when hovered

Note: notice that the line's width has increased, because the default width does not enable hover enter/leave events due to the small surface the cursor crosses. Keep that in mind.