一、简述

QGraphicsItem 分组比较简单,但在分组之后 group 中的 QGraphicsItem 无法捕获自己的相关事件(例如:鼠标事件、键盘事件),实际接受消息对象为 QGraphicsItemGroup。那么,如何处理呢?

二、处理方式

2.1 方式一

  1. void QGraphicsItem::setHandlesChildEvents(bool enabled)

如果 enabled 为 true,QGraphicsItemGroup 将处理其所有子 item 的所有事件(即,其任何子 item 的所有事件都发送到此 item),例如:鼠标点击子 item 的事件不会被子 item 自身处理;否则,如果 enabled 为 false,QGraphicsItemGroup 将只处理自己的事件,不会阻止子 item 的事件,并让子 item 处理自己的事件。

根据官方文档描述,该函数参数的默认值为 false。经过实验,重写鼠标事件、键盘事件之后,会发现依然会阻止子 item 的事件,究竟为何?难道是文档有误?

当然不会,打开 QGraphicsItemGroup 的源码,可以发现:

  1. QGraphicsItemGroup::QGraphicsItemGroup(QGraphicsItem *parent)
  2. : QGraphicsItem(*new QGraphicsItemGroupPrivate, parent)
  3. {
  4. setHandlesChildEvents(true);
  5. }

在 QGraphicsItemGroup 的构造函数中就这一行代码,也正是我们要找的答案!

所以,要让 QGraphicsItemGroup 中的 item 处理自己的事件,还需要在构造 group 后,再手动调用:

  1. QGraphicsItemGroup::setHandlesChildEvents(false);

这一行代码即可。

2.2 方式二

  1. bool QGraphicsItem::sceneEvent(QEvent *event)

该虚函数接收到此 item 的事件。重新实现这个函数,在事件被分派到专门的事件处理程序之前拦截事件 contextMenuEvent()、focusInEvent()、focusOutEvent()、hoverEnterEvent()、hoverMoveEvent()、hoverLeaveEvent()、keyPressEvent()、keyReleaseEvent()、mousePressEvent()、mouseReleaseEvent()、mouseMoveEvent()、和 mouseDoubleClickEvent()。

如果事件被识别和处理,则返回 true;否则(例如,如果事件类型未被识别),则返回 false。

event 是拦截的事件。

这样看来,sceneEvent() 接收一个 item 的所有事件,非常类似于 QWidget::event()。

既然如此,重写此函数也可以让 QGraphicsItemGroup 中的 item 处理自己的事件。

  1. #include <QEvent>
  2. #include <QGraphicsEllipseItem>
  3. #include <QGraphicsSceneMouseEvent>
  4. #include <QKeyEvent>
  5. #include <qDebug>
  6. class CustomItem : public QGraphicsEllipseItem {
  7. public:
  8. CustomItem(QGraphicsItem* parent = 0)
  9. {
  10. setFlag(QGraphicsItem::ItemIsFocusable);
  11. }
  12. protected:
  13. // 按键按下事件
  14. void keyPressEvent(QKeyEvent* event) Q_DECL_OVERRIDE
  15. {
  16. Q_UNUSED(event);
  17. qDebug() << "keyPressEvent";
  18. }
  19. // 按键释放事件
  20. void keyReleaseEvent(QKeyEvent* event) Q_DECL_OVERRIDE
  21. {
  22. Q_UNUSED(event);
  23. qDebug() << "keyReleaseEvent";
  24. }
  25. // 鼠标按下事件
  26. void mousePressEvent(QGraphicsSceneMouseEvent* event) Q_DECL_OVERRIDE
  27. {
  28. Q_UNUSED(event);
  29. qDebug() << "mousePressEvent";
  30. }
  31. // 鼠标按下事件
  32. void mouseMoveEvent(QGraphicsSceneMouseEvent* event) Q_DECL_OVERRIDE
  33. {
  34. Q_UNUSED(event);
  35. qDebug() << "mouseMoveEvent";
  36. }
  37. // 鼠标释放事件
  38. void mouseReleaseEvent(QGraphicsSceneMouseEvent* event) Q_DECL_OVERRIDE
  39. {
  40. Q_UNUSED(event);
  41. qDebug() << "mouseReleaseEvent";
  42. }
  43. // 处理上述事件
  44. bool sceneEvent(QEvent* event) Q_DECL_OVERRIDE
  45. {
  46. switch (event->type()) {
  47. case QEvent::GraphicsSceneMousePress:
  48. mousePressEvent(static_cast<QGraphicsSceneMouseEvent*>(event));
  49. break;
  50. case QEvent::GraphicsSceneMouseRelease:
  51. mouseReleaseEvent(static_cast<QGraphicsSceneMouseEvent*>(event));
  52. break;
  53. case QEvent::GraphicsSceneMouseMove:
  54. mouseMoveEvent(static_cast<QGraphicsSceneMouseEvent*>(event));
  55. break;
  56. case QEvent::KeyPress:
  57. keyPressEvent(static_cast<QKeyEvent*>(event));
  58. break;
  59. case QEvent::KeyRelease:
  60. keyReleaseEvent(static_cast<QKeyEvent*>(event));
  61. break;
  62. default:
  63. break;
  64. }
  65. event->accept();
  66. return true;
  67. }
  68. };

显然,大多数情况下,正确的姿势应该选择方式一,因为对我们来说更简单,方式二则需要为每一个自定义 item 都去实现 sceneEvent()。