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

Posted

技术标签:

【中文标题】QGraphicsItem 的子类仅从边界矩形接收边框上的悬停事件【英文标题】:Subclass of QGraphicsItem only receives hover event on the border from bounding rectangle 【发布时间】:2021-09-20 08:49:06 【问题描述】:

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

然后,在paint事件中,我可以根据_mouseEnter判断是否绘制圆圈。

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

我不知道我是否错过了什么?在我看来,shape()boundingRect() 会在事件发生时影响这些鼠标事件吗?我想让shape() 返回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

ma​​iwindow.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;

ma​​inwindow.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

ma​​in.cpp

#include "mainwindow.h"

#include <QApplication>

int main(int argc, char *argv[])

  QApplication a(argc, argv);
  MainWindow w;
  w.show();
  return a.exec();

【问题讨论】:

请提供minimal reproducible example @eyllanesc 嗨~,我应该在哪里以及如何附加我的最小示例?现在,我只是把它们复制到这里,如果不合适,我会删除它们。 阅读链接,同时阅读How to Ask 并查看tour 谢谢,我添加了一个最小的例子。 【参考方案1】:

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;

【讨论】:

很抱歉,由于缺乏声誉而无法投票。 @taptiptop 请查看tour【参考方案2】:

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

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));

【讨论】:

试过了,还是不行。我错过了什么吗?

以上是关于QGraphicsItem 的子类仅从边界矩形接收边框上的悬停事件的主要内容,如果未能解决你的问题,请参考以下文章

QGraphicsItem - 项目转换失败

使用 mouseMoveEvent 约束 QGraphicsItem 移动

QGraphicsItem 边框样式

QGraphicsItem:如何在 QGraphicsScene 调整大小时自动移动它?

如何在 QGraphicsItem 上接收手势事件?

包围轮廓的矩形边界 opencv