9

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.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
user3419537
  • 4,740
  • 2
  • 24
  • 42

4 Answers4

19

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_())
three_pineapples
  • 11,579
  • 5
  • 38
  • 75
  • I guess enabling mouse tracking on the tabwidget and tabs that I tried wasn't enough. I was hoping there may have been a simpler solution than explicitly enabling tracking for every widget in the hierarchy. This works just fine though, thanks! – user3419537 Aug 19 '14 at 09:17
1

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,

Bandhit Suksiri
  • 3,390
  • 1
  • 18
  • 20
  • Have you tested this code? I have had no success when monkey-patching event handlers such as `mouseMoveEvent` in the past. The only way to override such methods AFAIK is via subclassing. – three_pineapples Aug 19 '14 at 07:16
  • I tested this code already. (Is it something wrong please tell me to fixed, thanks). It same way override this method but not inheritance directly. – Bandhit Suksiri Aug 19 '14 at 07:23
  • Hmm, very odd. I swear monkey-patching like this hasn't worked for me in the past. But your code does work (though it doesn't print coordinates when moving the mouse over the tabs!) – three_pineapples Aug 19 '14 at 07:31
  • I possibly python version or pyqt lib version ? Oh, I see. My code doesn't print coordinates when moving in tabs bar! (=[]=") – Bandhit Suksiri Aug 19 '14 at 07:35
  • OK, Fixed tab bar isn't track mouse move event. Thanks for your tested 'three_pineapples'. ;) – Bandhit Suksiri Aug 19 '14 at 07:39
  • 1
    Thanks, this works too. However, I have accepted the answer from @three_pineapples as it is a more generalised solution, and similar logic could be applied to c++ code as it does not rely on monkeypatching. – user3419537 Aug 19 '14 at 09:42
  • Your welcome, I also have new way to implement is problem too. ;) Thanks. – Bandhit Suksiri Aug 19 '14 at 09:56
1

I had the same issue and found the answer here:

self.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents)

chb
  • 1,727
  • 7
  • 25
  • 47
0

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.

Sir Digby Chicken Caesar
  • 3,063
  • 1
  • 23
  • 31