如何让无父子关系的重叠QGraphicsItem均接收hover事件?
问题
我使用两个自定义图形项:继承自QGraphicsItem的ItemImage,以及继承自QGraphicsPixmapItem的ItemDrawing。两者均在主窗口构造函数中创建并通过scene->addItem()添加到场景:
MyWindow::MyWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MyWindowClass()) , _scene(new QGraphicsScene(this)) , _item_drawing(new ItemDrawing()) , _item_image(new ItemImage()) { ... _scene->addItem(_item_drawing); _scene->addItem(_item_image); ui->view->setScene(_scene); }
两个项均在构造函数中调用setAcceptHoverEvents(true),并重写了hoverEnterEvent和hoverMoveEvent;同时我还重写了GraphicsView的mouseMoveEvent并调用了QGraphicsView::mouseMoveEvent:
/* ItemImage */ ItemImage::ItemImage(QGraphicsItem* parent) : QGraphicsItem(parent) { setAcceptHoverEvents(true); setFlags(ItemIsSelectable | ItemIsMovable); } void ItemImage::hoverMoveEvent(QGraphicsSceneHoverEvent* event) { ... QGraphicsItem::hoverMoveEvent(event); } /* ItemDrawing */ ItemDrawing::ItemDrawing(QGraphicsItem* parent) : QGraphicsPixmapItem(parent) , _temp_measure() { setZValue(1); setTransformationMode(Qt::SmoothTransformation); setShapeMode(QGraphicsPixmapItem::BoundingRectShape); setAcceptHoverEvents(true); } void ItemDrawing::hoverMoveEvent(QGraphicsSceneHoverEvent* event) { ... QGraphicsItem::hoverMoveEvent(event); } /* GraphicsView which inherits QGraphicsView */ void GraphicsView::mouseMoveEvent(QMouseEvent* e) { ... QGraphicsView::mouseMoveEvent(e); }
目前的问题是:鼠标位于重叠坐标时,只有z序最高的项(即设置了setZValue(1)的ItemDrawing)能接收hover事件;当ItemImage移出ItemDrawing区域时,ItemImage可正常接收hover事件;若将一个项设为另一个的子项,两者均可接收hover事件。
想知道:除了设置父子关系外,是否还有其他方法让重叠的无父子关系的两个项都接收hover事件?
我查看Qt的QGraphicsScenePrivate::dispatchHoverEvent实现后,发现它似乎不会将事件传递给鼠标下方的所有项,因此推测可能无法实现,但不确定是否遗漏了某些方法。
解决方案
1. 自定义场景手动分发hover事件
重写QGraphicsScene的hover事件处理函数,在事件触发时,通过items()方法获取鼠标位置下的所有堆叠项,再手动调用每个项的hover事件接口。
示例代码:
void MyGraphicsScene::hoverMoveEvent(QGraphicsSceneHoverEvent *event) { // 获取鼠标位置下所有符合条件的项,按z序从高到低排列 QList<QGraphicsItem*> itemsUnderMouse = items( event->scenePos(), Qt::IntersectsItemShape, Qt::DescendingOrder ); for (QGraphicsItem* item : itemsUnderMouse) { if (item->acceptHoverEvents()) { // 手动触发项的hoverMove事件 item->hoverMoveEvent(event); } } // 保留父类默认事件逻辑 QGraphicsScene::hoverMoveEvent(event); } void MyGraphicsScene::hoverEnterEvent(QGraphicsSceneHoverEvent *event) { QList<QGraphicsItem*> itemsUnderMouse = items( event->scenePos(), Qt::IntersectsItemShape, Qt::DescendingOrder ); for (QGraphicsItem* item : itemsUnderMouse) { if (item->acceptHoverEvents()) { item->hoverEnterEvent(event); } } QGraphicsScene::hoverEnterEvent(event); }
注意:如果需要避免原事件被修改影响后续项,可以克隆事件对象后再分发。
2. 上层项主动转发hover事件
在z序较高的ItemDrawing的hover事件处理函数中,主动查找鼠标位置下的其他目标项,并调用其hover事件方法。
示例代码:
void ItemDrawing::hoverMoveEvent(QGraphicsSceneHoverEvent* event) { // 先处理自身的hover逻辑 ... // 获取鼠标位置下的所有项,排除自身后查找目标项 QList<QGraphicsItem*> itemsUnderMouse = scene()->items(event->scenePos()); for (QGraphicsItem* item : itemsUnderMouse) { if (item != this && item->type() == ItemImage::Type && item->acceptHoverEvents()) { static_cast<ItemImage*>(item)->hoverMoveEvent(event); break; // 仅转发给特定类型项,找到后即可退出循环 } } QGraphicsItem::hoverMoveEvent(event); }
这种方法适合仅需要转发给特定类型项的场景,代码侵入性小,灵活性高。
3. 使用事件过滤器拦截分发
在主窗口或场景中安装事件过滤器,拦截hover事件后分发给所有符合条件的项,无需修改项的内部代码。
示例代码:
bool MyWindow::eventFilter(QObject *watched, QEvent *event) { if (watched == _scene && (event->type() == QEvent::GraphicsSceneHoverMove || event->type() == QEvent::GraphicsSceneHoverEnter)) { QGraphicsSceneHoverEvent* hoverEvent = static_cast<QGraphicsSceneHoverEvent*>(event); QList<QGraphicsItem*> itemsUnderMouse = _scene->items(hoverEvent->scenePos()); for (QGraphicsItem* item : itemsUnderMouse) { if (item->acceptHoverEvents()) { if (event->type() == QEvent::GraphicsSceneHoverMove) { item->hoverMoveEvent(hoverEvent); } else { item->hoverEnterEvent(hoverEvent); } } } } return QMainWindow::eventFilter(watched, event); }
在主窗口构造函数中安装过滤器:
_scene->installEventFilter(this);
内容的提问来源于stack exchange,提问作者Dean Lee

