8
votes

I'm trying to capture the cursor coordinates as the mouse is moved within a QWidget by reimplementing QWidget::mouseMoveEvent(). With mouse tracking enabled, mouse move events are generated as I move the cursor around the main widget. However, when the cursor is placed over a child widget the mouse move events cease to fire.

Mouse press/release events work while the cursor is over the same child widget, and move events are firing correctly if the mouse button is held. I've tried enabling mouse tracking on the children too, but it doesn't seem to make a difference. How can I trigger mouse move events when the mouse is over a child widget?

Here's a minimum working example that demonstrates the problem:

import sys
from PyQt4 import QtCore, QtGui

class MyWindow(QtGui.QWidget) :
    def __init__(self):
        QtGui.QWidget.__init__(self)
        tabs = QtGui.QTabWidget()
        tab1 = QtGui.QWidget()
        tab2 = QtGui.QWidget()
        tabs.addTab(tab1, "Tab 1")
        tabs.addTab(tab2, "Tab 2")
        layout = QtGui.QVBoxLayout()
        layout.addWidget(tabs)
        self.setLayout(layout)
        self.setMouseTracking(True)

    def mouseMoveEvent(self, event):
        print 'mouseMoveEvent: x=%d, y=%d' % (event.x(), event.y())


app = QtGui.QApplication(sys.argv)
window = MyWindow()
window.setFixedSize(640, 480)
window.show()
sys.exit(app.exec_())

When the mouse is moved outside of the QTabWidget the mouse coordinates are printed as expected. Inside of it nothing happens unless the mouse button is held.

4

4 Answers

18
votes

The problem with your code is that you need to enable mouse tracking for all widgets explicitly. You can do this by iterating over all children of your main widget, and calling setMouseTracking(True) for each of them. Here I've overridden setMouseTracking() to do just that:

import sys
from PyQt4 import QtCore, QtGui

class MyWindow(QtGui.QWidget) :
    def __init__(self):
        QtGui.QWidget.__init__(self)
        tabs = QtGui.QTabWidget()
        tab1 = QtGui.QWidget()
        tab2 = QtGui.QWidget()
        tabs.addTab(tab1, "Tab 1")
        tabs.addTab(tab2, "Tab 2")
        layout = QtGui.QVBoxLayout()
        layout.addWidget(tabs)
        self.setLayout(layout)
        self.setMouseTracking(True)

    def setMouseTracking(self, flag):
        def recursive_set(parent):
            for child in parent.findChildren(QtCore.QObject):
                try:
                    child.setMouseTracking(flag)
                except:
                    pass
                recursive_set(child)
        QtGui.QWidget.setMouseTracking(self, flag)
        recursive_set(self)

    def mouseMoveEvent(self, event):
        print 'mouseMoveEvent: x=%d, y=%d' % (event.x(), event.y())


app = QtGui.QApplication(sys.argv)
window = MyWindow()
window.setFixedSize(640, 480)
window.show()
sys.exit(app.exec_())
1
votes

LAST UPDATED 19 / 8 / 2014 14 : 37 Fixed tab bar isn't track mouse move event. (your can see in my code)


I also suggest implemented QWidget.mouseMoveEvent (self, QMouseEvent) as your do. But not only root widget only because it track area of interesting widget, so your have to set mouse move event all widget can track your in your application. So, create delegate method to connect them all and if your have any signal form mouse move event, get current point of mouse it. like this;

import sys
from PyQt4 import QtGui

class QCustomWidget (QtGui.QWidget):
    def __init__ (self, parent = None):
        super(QCustomWidget, self).__init__(parent)
        self.myQTabWidget = QtGui.QTabWidget(self)
        self.my1QWidget   = QtGui.QWidget()
        self.my2QWidget   = QtGui.QWidget()
        self.myQTabWidget.addTab(self.my1QWidget, 'Tab 1')
        self.myQTabWidget.addTab(self.my2QWidget, 'Tab 2')
        myQLayout = QtGui.QVBoxLayout()
        myQLayout.addWidget(self.myQTabWidget)
        self.setLayout(myQLayout)
        self.setMouseMoveEventDelegate(self)
        self.setMouseMoveEventDelegate(self.myQTabWidget)
        self.setMouseMoveEventDelegate(self.myQTabWidget.tabBar())
        self.setMouseMoveEventDelegate(self.my1QWidget)
        self.setMouseMoveEventDelegate(self.my2QWidget)

    def setMouseMoveEventDelegate (self, setQWidget):
        def subWidgetMouseMoveEvent (eventQMouseEvent):
            currentQPoint = self.mapFromGlobal(QtGui.QCursor.pos())
            print currentQPoint.x(), currentQPoint.y()
            QtGui.QWidget.mouseMoveEvent(setQWidget, eventQMouseEvent)
        setQWidget.setMouseTracking(True)
        setQWidget.mouseMoveEvent = subWidgetMouseMoveEvent

appQApplication = QtGui.QApplication(sys.argv)
windowQCustomWidget = QCustomWidget()
windowQCustomWidget.setFixedSize(640, 480)
windowQCustomWidget.show()
sys.exit(appQApplication.exec_())

Regards,

0
votes

I would try making your QTabWidget a logical child of MyWindow by passing self when calling the QTabWidget constructor. Also pass a parent for the children of the tab widgets but pass the tab widget variable tabs to their respective constructors. Without the child hierarchy declared like this, the events might not be forwarded properly to the containing widget as its "children" will be seen as just separate widgets drawn on top of your class from the perspective of the qt scene graph / event queue.

0
votes

I had the same issue and found the answer here:

self.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents)