Search code examples
qthoverqgraphicsitem

Drawing extra item in QGraphicsItem during hover event


I would like to create coordinate points on a QGraphicsView. When the mouse hovers over the point, the coordinate will be shown.

I draw the coordinates by QGraphicsEllipseItem. In order to enable the hover event, I need to re-implement the QGraphicsEllipseItem. However, because the size of the QGraphicsEllipseItem is fixed when it was constructed, the hover text is not shown properly. How can I deal with this?

Here is my code:

The MainWindow:

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    view = new QGraphicsView(this);
    view->setRenderHint(QPainter::Antialiasing);

    scene = new QGraphicsScene(this);
    view->fitInView(scene->sceneRect(), Qt::KeepAspectRatio);
    view->setScene(scene);
    setCentralWidget(view);

    for (int y = 0; y < 900; y += 100)
        for(int x = 0; x < 1400; x += 100)
            drawPoint(x, y);
}

void MainWindow::drawPoint(int x, int y)
{
    CoordinatePoint* point = new CoordinatePoint();
    point->setRect(QRect(x, y, 3, 3));
    point->setPen(QPen(Qt::red, 3, Qt::SolidLine));
    point->setBrush(Qt::red);
    scene->addItem(point);
}

The re-implement QGraphicsEllipseItem:

CoordinatePoint::CoordinatePoint(QGraphicsItem* parent)
    :QGraphicsEllipseItem(parent)
{
    setAcceptHoverEvents(true);
}


void CoordinatePoint::hoverEnterEvent(QGraphicsSceneHoverEvent* event)
{
    hover = true;
    mx = event->pos().x();
    my = event->pos().y();
    update();
}

void CoordinatePoint::hoverLeaveEvent(QGraphicsSceneHoverEvent* event)
{
    hover = false;
    update();
}

void CoordinatePoint::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
{
    QGraphicsEllipseItem::paint(painter, option, widget);
    if (hover)
    {
        painter->save();
        painter->setPen(Qt::black);
        painter->setBrush(Qt::black);
        painter->drawText(mx + 2, my + 2,
                          "(" + QString::number(mx) + "," +
                          QString::number(my) + ")");
        painter->restore();
    }
}

Solution

  • I think that using a separate child item for the text will make your life a lot easier:

    #include <QtWidgets>
    
    class CoordinatePoint : public QGraphicsEllipseItem
    {
    public:
        CoordinatePoint(QGraphicsItem* parent = Q_NULLPTR) :
            QGraphicsEllipseItem(parent),
            coordinateText(Q_NULLPTR)
        {
            setAcceptHoverEvents(true);
        }
    
        void hoverEnterEvent(QGraphicsSceneHoverEvent*)
        {
            if (!coordinateText) {
                coordinateText = new QGraphicsTextItem(this);
                coordinateText->setPlainText("(" + QString::number(x())
                    + "," + QString::number(y()) + ")");
                coordinateText->setX(2);
                coordinateText->setY(2);
            }
    
            coordinateText->setVisible(true);
        }
    
        void hoverLeaveEvent(QGraphicsSceneHoverEvent*)
        {
            if (coordinateText) {
                coordinateText->setVisible(false);
            }
        }
    
    private:
        QGraphicsTextItem *coordinateText;
    };
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
    
        QMainWindow window;
    
        QGraphicsView *view = new QGraphicsView(&window);
        view->setRenderHint(QPainter::Antialiasing);
    
        QGraphicsScene *scene = new QGraphicsScene(&window);
        view->fitInView(scene->sceneRect(), Qt::KeepAspectRatio);
        view->setScene(scene);
        window.setCentralWidget(view);
    
        for (int y = 0; y < 900; y += 100) {
            for(int x = 0; x < 1400; x += 100) {
                CoordinatePoint* point = new CoordinatePoint();
                point->setRect(QRect(0, 0, 3, 3));
                point->setX(x);
                point->setY(y);
                point->setPen(QPen(Qt::red, 3, Qt::SolidLine));
                point->setBrush(Qt::red);
                scene->addItem(point);
            }
        }
    
        window.show();
    
        return a.exec();
    }
    

    If having an extra QGraphicsTextItem for each coordinate worries you, you could construct one of them and just share it amongst them all, reparenting it as each one is hovered. This should work fine, as there can only ever be one coordinate hovered at a time.

    If you try to draw the text as part of the ellipse item, you'd have to:

    • Increase the size of the item so that it's large enough to contain the text, which means overriding boundingRect().
    • Only draw the ellipse within a certain part of that area.
    • In hoverEnterEvent(), check that the mouse cursor is within the ellipse's area.
    • A bunch of other stuff, probably.