QGraphicsItem 的子类仅从边界矩形接收边框上的悬停事件

Subclass of QGraphicsItem only receives hover event on the border from bounding rectangle

我正在使用 Qt-5.15.1 进行开发。我想自定义QGraphicsItem,在这个自定义项中添加了一个矩形和一些环绕的圆圈。当鼠标悬停在该矩形上时,小圆圈将显示。所以我重新实现了hoverEnterEventhoverLeaveEvent函数来接收鼠标悬停事件,请参考minimal example.

然后在paint事件中我可以根据_mouseEnter判断是否画圆。

问题来了,我发现hoverEnterEvent会在鼠标进入该矩形的边界时立即触发,但是很快hoverLeaveEvent也会在鼠标穿过边界时触发,靠近矩形的中心。似乎边框是鼠标悬停事件的实体,而不是填充的矩形。所以我只能在鼠标悬停在该矩形的边界上时显示圆圈。

不知道是不是漏掉了什么?在我看来,shape()boundingRect()会在事件发生时影响这些鼠标事件?我想使 shape() 到 return 成为 QPainterPath 的填充矩形,但不知道如何做。

更新:最小示例

customizeitem.cpp

#include "customizeitem.h"
#include <QDebug>

CustomizeItem::CustomizeItem(QGraphicsItem *parent):
  QGraphicsItem(parent),
  _bbox(0,0,120, 120),_radius(7),
  _mouseEnter(false)
{
  setFlags(QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsMovable);
  setAcceptHoverEvents(true);

  _rectRect = QRect(QPoint(_radius*4, _radius*4), QPoint(_bbox.width() - _radius*4, _bbox.height() - _radius*4));

  QPointF upCenter(_bbox.width()/2, _radius);
  QPointF rCenter(_bbox.width() - _radius, _bbox.height() / 2);
  QPointF downCenter(_bbox.width()/2, _bbox.height() - _radius);
  QPoint lCenter(_radius, _bbox.height() / 2);
  _anchorRects.push_back(QRectF(upCenter, QSizeF(_radius, _radius)));
  _anchorRects.push_back(QRectF(rCenter, QSizeF(_radius, _radius)));
  _anchorRects.push_back(QRectF(downCenter, QSizeF(_radius, _radius)));
  _anchorRects.push_back(QRectF(lCenter, QSizeF(_radius, _radius)));
}

void CustomizeItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
{
  qInfo() << "mouse enter";
  _mouseEnter = true;

  update();
  QGraphicsItem::hoverEnterEvent(event);
}

void CustomizeItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
{
  qInfo() << "mouse leave";
  _mouseEnter = false;

  update();
  QGraphicsItem::hoverLeaveEvent(event);
}

QRectF CustomizeItem::boundingRect() const
{
  return shape().boundingRect();
}

void CustomizeItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
{
  painter->setRenderHint(QPainter::Antialiasing);
  drawAnchors(painter);
  painter->fillRect(_rectRect, QColor(255, 0, 0));
}

QPainterPath CustomizeItem::shape() const
{
  QPainterPath path;
  path.moveTo(_bbox.topLeft());
  path.addRect(_bbox);
  QPainterPathStroker stroker;
  stroker.setWidth(10);
  return stroker.createStroke(path);
}

void CustomizeItem::drawAnchors(QPainter *painter)
{
  if(_mouseEnter)
  {
    for(int i = 0; i < _anchorRects.size(); i++)
    {
      QPainterPath path;
      path.moveTo(_anchorRects[0].center());
      path.addEllipse(_anchorRects[i].center(), _radius, _radius);

      painter->drawPath(path);
    }
  }
}

customizeitem.h

#ifndef CUSTOMIZEITEM_H
#define CUSTOMIZEITEM_H

#include <QGraphicsItem>
#include <QObject>
#include <QPainter>

class CustomizeItem : public QObject, public QGraphicsItem
{
Q_OBJECT
public:
  enum { Type = UserType + 1 };

  explicit CustomizeItem(QGraphicsItem *parent = nullptr);
  ~CustomizeItem() = default;

protected:
  void hoverEnterEvent(QGraphicsSceneHoverEvent *event);
  void hoverLeaveEvent(QGraphicsSceneHoverEvent *event);
  QRectF boundingRect() const;
  void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *);
  QPainterPath shape() const;
private:
  void drawAnchors(QPainter *painter);

  QRect _bbox;

  float _radius; // radius for circle anchor
  QVector<QRectF> _anchorRects;

  QRect _rectRect;
  bool _mouseEnter;
};

#endif // CUSTOMIZEITEM_H

maiwindow.cpp

#include "mainwindow.h"
#include "./ui_mainwindow.h"
#include "customizeitem.h"
#include <QGraphicsView>
#include <QVBoxLayout>

MainWindow::MainWindow(QWidget *parent)
  : QMainWindow(parent)
  , ui(new Ui::MainWindow)
{
  ui->setupUi(this);

  QVBoxLayout *layout = new QVBoxLayout(centralWidget());
  layout->setContentsMargins(0,0,0,0);

  QGraphicsView *view = new QGraphicsView(this);
  view->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
  QGraphicsScene *scene = new QGraphicsScene;

  CustomizeItem *item = new CustomizeItem;
  scene->addItem(item);

  view->setScene(scene);
  layout->addWidget(view);
}

MainWindow::~MainWindow()
{
  delete ui;
}

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
  Q_OBJECT

public:
  MainWindow(QWidget *parent = nullptr);
  ~MainWindow();

private:
  Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H

main.cpp

#include "mainwindow.h"

#include <QApplication>

int main(int argc, char *argv[])
{
  QApplication a(argc, argv);
  MainWindow w;
  w.show();
  return a.exec();
}

QPainterPathStroker 在矩形周围生成一个空心区域,因此一旦该边界通过,它就已经变形了。解决方案是将该边缘与内部区域连接起来:

QPainterPath CustomizeItem::shape() const
{
    QPainterPath path;
    path.moveTo(_bbox.topLeft());
    path.addRect(_bbox);
    QPainterPathStroker stroker;
    stroker.setWidth(10);
    QPainterPath strokerPath = stroker.createStroke(path);
    strokerPath.addPath(path); // join
    return strokerPath;
}

或者只调整边界框矩形以适应额外的尺寸。

QPainterPath CustomizeItem::shape() const
{
    QPainterPath path;
    path.addRect(_bbox.adjusted(-5, -5, 5, 5));
    return path;
}

由于您的“命中”形状是矩形,您可能只需重新实现 boundingRect(),因为默认 QGraphicsItem::shape() 实际上使用 boundingRect() 结果(根据 docs).

QRectF CustomizeItem::boundingRect() const
{
    return QRectF(_bbox.adjusted(-5, -5, 5, 5));
}