c++qtqgraphicsviewqgraphicssceneqt6

Qt QGraphicsView mouseMoveEvent works nonstop


For some reason using addWidget function in QGraphicsScene class makes the related QGraphicsView widget receive mouseMoveEvents without mousebutton pressed and mouse tracking off.

MyView.h :

#pragma once

#include <qgraphicsview>
#include <qevent>
#include <qgraphicsscene>
class MyView : public QGraphicsView // subclassing QGraphicsView
{
    Q_OBJECT
protected:
    void mouseMoveEvent(QMouseEvent* event);


public:
    MyView(QGraphicsScene* scene, QWidget *parent);
    ~MyView();
};

MyView.cpp:

#include "MyView.h"

MyView::MyView(QGraphicsScene* scene, QWidget* parent)
    : QGraphicsView(scene, parent)
{}

void MyView::mouseMoveEvent(QMouseEvent* event)
{
    qDebug() << this->hasMouseTracking(); // indicating that the event goes on 
                                              // even though I dont't press any buttons
                                              // and mouse tracking is disabled by default
}

MyView::~MyView()
{}

QMainWindow.cpp :

QVBoxLayout* layout = new QVBoxLayout();
ui.centralWidget->setLayout(layout);
QGraphicsScene* scene = new QGraphicsScene();
scene->addWidget(new QWidget());
MyView* v = new MyView(scene, ui.centralWidget);
layout->addWidget(v);

This happens only after adding QWidget or its subclass in the scene. Qt version is 6.7.0


Solution

  • This happens only after adding QWidget or its subclass in the scene

    This is the very reason for which it happens.
    Mouse tracking is automatically enabled whenever the items in the scene require it, which is implicit for widgets added through QGraphicsProxyWidgets.

    By default, QWidget does not set the mouseTracking property, which is normally false (disabled) and doesn't provide any notify signal for its change.

    In reality, many widgets set that property to true: for instance to change the mouse cursor in a QLineEdit with the clear button enabled, or a QLabel showing a hyperlink, or to display the hovered item in a view.

    Proper mouse movement tracking is also necessary in order to properly propagate Enter and Leave events to widgets through their proxies.

    The assumption QGraphicsScene makes, therefore, is that whenever any QGraphicsProxyWidget is added to it, its widget may receive mouse move events, even if no mouse button is pressed, so it automatically sets the mouseTracking property to true for all views that are showing it. If a scene is added to a view, and it already contains QGraphicsProxyWidgets, the property is similarly set automatically.

    The proxy item will eventually decide if the event shall be actually sent to the widget based on its mouseTracking property.

    Note that the widget may be a complex one (eg. a container, or even a "window"), making it pointless to have a notifier signal that would be difficult to propagate and manage every time its child tree changes.

    So, the conclusion is that whenever any QGraphicsProxyWidget is added to a scene (including using scene::addWidget()), the views connected to that scene will always use mouse tracking (even if those items are removed and after the scene is cleared), and will always cause mouseMoveEvent() triggering from that moment on, unless setMouseTracking(false) is manually called.

    This calls for a proper implementation of mouseMoveEvent() in the view: depending on your needs (which you didn't clarify, btw), you may need to call the base implementation first, which would relay the event to the scene, possibly accepting/ignoring the event if a widget contained in a QGraphicsProxyWidget did it in the first place, and eventually check the event::isAccepted() in that case.
    Alternatively, you may want to handle the event only after mapping it to the scene and checking if that scene position has a topmost QGraphicsProxyWidget item.

    In any case, the behavior you're facing is not only expected, but, fundamentally, required.