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

Time:09-22

I'm developing with Qt-5.15.1. I want to customize QGraphicsItem, and in this customized item one rectangle and some surrounding circles added. The little circles will show when the mouse is hovering on that rectangle. So I reimplement the hoverEnterEvent and hoverLeaveEvent function to receive mouse hover event, please refer to minimal example.

Then, in paint event I can determine whether draw circles or not based on _mouseEnter.

Here comes the problem, I found the hoverEnterEvent will triggered as soon as mouse enter the border of that rectangle, however quickly hoverLeaveEvent is also triggered as mouse go through the border, being near the center of rectangle. Seems the border is the entity of mouse hover event, not the filled rectangle. So I can only show circles on when mouse is hovering on the border of that rectangle.

I don't know if I miss something? In my opinion, shape() and boundingRect() will affect these mouse events on when event happens? I want to make shape() to return a filled rectangle of QPainterPath, but don't know how to.

Update: minimal example

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

CodePudding user response:

QPainterPathStroker generates a hollow region around the rectangle so as soon as that border passes it is already out of the shape. The solution is to join that edge with the inner region:

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

CodePudding user response:

Or just adjust the bounding box rectangle to accommodate the extra size.

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

Since your "hit" shape is rectangular, you can probably just re-implement boundingRect(), since the default QGraphicsItem::shape() actually uses boundingRect() result (according to docs).

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