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
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
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.