Python Forum
[PyQt] PyQt trigger leaveEvent in parent
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[PyQt] PyQt trigger leaveEvent in parent
#1
Hi,

I'm working on a project where I need to change the painting of a widget when the mouse is hovering over it. The issue I'm having is I can't get the leave event to trigger for a widget when the mouse hovers over one of its children.

[Image: 4wM4P1T.png]

So here, when the mouse enters One, I want it's enterEvent to be triggered, however when the mouse enters Two I want One to have its leaveEvent triggered. Keep in mind this could be a complicated nest of layouts and widgets that can be dragged and dropped.

I've also tried installing an eventFilter and using HoverLeave and HoverEnter, with the WA_Hover attribute set, however this gives me the same behavior.

Minimal Example:
import sys
from PyQt5 import QtWidgets


class TestWindow(QtWidgets.QDialog):
    def __init__(self, parent=None):
        super(TestWindow, self).__init__(parent=parent)
        self.resize(640, 480)

        layout = QtWidgets.QVBoxLayout()
        self.setLayout(layout)

        widget1 = CustomWidget("One")
        widget2 = CustomWidget("Two")

        widget1.layout().addWidget(widget2)
        layout.addWidget(widget1)


class CustomWidget(QtWidgets.QFrame):
    def __init__(self, name, parent=None):
        super(CustomWidget, self).__init__(parent=parent)
        self.setObjectName(name)
        self.setFrameStyle(QtWidgets.QFrame.StyledPanel)
        self.setLayout(QtWidgets.QVBoxLayout())
        self.layout().addWidget(QtWidgets.QLabel(name, parent=self))

    def enterEvent(self, event):
        print("Enter:", self.objectName())

    def leaveEvent(self, event):
        print("Leave:", self.objectName())

if __name__ == "__main__":
    qApp = QtWidgets.QApplication(sys.argv)
    tw = TestWindow()
    tw.show()
    sys.exit(qApp.exec_())
Thanks for the help!
Reply
#2
Okay first I tweaked your code to be more pythonic/pyqtish in form removing some of the dangerous items and replacing PyQt4 references with PyQt5 references if you have any questions as to why do ask so that I can explain the whys of it -- so without further ado here she be
from PyQt5.QtWidgets import QApplication, QDialog, QVBoxLayout
from PyQt5.QtWidgets import QFrame, QLabel
  
class CustomWidget(QFrame):
    def __init__(self, Name):
        QFrame.__init__(self)
        self.setFrameStyle(QFrame.StyledPanel)

        self.WdgtName = Name
        self.lblName = QLabel(self.WdgtName)

        VBox = QVBoxLayout()
        VBox.addWidget(self.lblName)

        self.setLayout(VBox)
 
    def enterEvent(self, event):
        print('Entering ', self.WdgtName)
 
    def leaveEvent(self, event):
        print('Leaving ', self.WdgtName)

class TestWindow(QDialog):
    def __init__(self):
        QDialog.__init__(self)

        self.resize(640, 480)

        self.Widget1 = CustomWidget('One')
        self.Widget2 = CustomWidget('Two')
 
        self.Widget1.layout().addWidget(self.Widget2)

        MainLayout = QVBoxLayout()
        MainLayout.addWidget(self.Widget1)

        self.setLayout(MainLayout)
 
 
if __name__ == '__main__':
    MainEventThread = QApplication([])

    MainApp = TestWindow()
    MainApp.show()

    MainEventThread.exec()
Now when executing this upon Entering One -- It denotes this upon Entering Two it denotes this BUT it does not denote we have left One because we have not. This because Two is contained within One. If I go back to One then it denotes I have left Two but if while in Two I move the mouse outside the entire window then it denotes that I have left Two and One -- in that order
Reply
#3
Thanks for the response.

So to solve my problem I would need to install an event filter on any child widgets and monitor their enter and leave events to trigger some method in the parent widget.

Yes if you could explain why the changes you made are important that would be great, one thing I've struggled to find information on is best practices for PyQt. A side note, while in this example I used PyQt5, I mainly use the Qt.py module and everything executes inside AutoDesk Maya (i.e. Python 2.7) in case that makes any difference.

Also for my layouts, I've recently started writing it as:
layout = QVBoxLayout(widget)
Rather than calling the setLayout() method. Is this fine?
Reply
#4
Okay first you will need to upgrade to Python3 as Python2.7 will be deprecated in January of next year either move now or be forced to move later.

Next since you have the full version above I am just going to dissect the pieces I changed if I miss something just let me know
layout = QVBoxLayout(widget)
Yes while you can write the above in that manner it actually goes back to the old school process of cramming as much as one could on a single line because you had to which btw turned out to be a very bad idea once you could do it without issue in a more verbose easier to comprehend at a glance method. So why go back to the way it had to be done that proved to be a bad coding style once it was no longer required. I mean what are you saving? One additional line of code for more complexity? Big rule and best rule of programming is K.I.S.S. (Keep It Simple and Smart) which means you always ask yourself do I need to add more complexity or is the simple approach just as effective -- further what is more clear about what I am doing this method or that method. The following 2 liner of the above 1 liner is both less complex and more straight forward as in its obvious what you are doing.
vbxDescShrtNam = QVBoxLayout()
vbxDescShrtNam.addWidget(MyWidget)

from PyQt5.QtWidgets import QApplication, QDialog, QVBoxLayout
from PyQt5.QtWidgets import QFrame, QLabel
Next when importing libraries ONLY import what you need the from PyQt import QtWidgets imports a HUGE library that you most likely are only going to use a fraction of further importing QDialog (for example) imports all its dependencies anyway
class CustomWidget(QFrame):
    def __init__(self, Name):
        QFrame.__init__(self)
Next do not use the Super( ) function unless you are using it for the rare and specific case it was designed for because using it for any other purpose brings in various other potential issues and you are thus adding more potential issues than you are solving when you use it. Again K.I.S.S. do not add more complexity unless its necessary and using Super() does just that adds extra unnecessary complexity
        self.WdgtName = Name
I did this instead of setting the .ObjectName() because calling that method to set a parent variable is slightly more complex when all you need it for is to handle a local class variable
        VBox = QVBoxLayout()
        VBox.addWidget(self.lblName)
Again the above is cleaner less complex than cramming all within one line of code further what if you want to and multiple Widgets to that VBox?

Another element of my changes was to group like sections of code together rather sprinkle them throughout as this facilitates being able to see it all at a glance. Also arrange them top-down in order of usage for instance you need to define and set all the widgets that you plan to put into a BoxLayout prior to even creating that BoxLayout so do those first then do the BoxLayout adding each of its pre-defined Widgets/Layouts
if __name__ == '__main__':
    MainEventThread = QApplication([])

    MainApp = TestWindow()
    MainApp.show()

    MainEventThread.exec()
Okay this is a sort of packaged deal -- first QApplication([]) is what creates the Main Event Thread in Qt -- so name it as such to remind yourself and to let anyone else, at glance, know what its purpose is. Next you do not use sys.argv in the code so do not include it as that is adding unused code to your project -- further if you were going to use Command Line arguments then do not use sys.argv anyway as the Python argparser library handles it much cleaner and already exists

Next MainEventThread.exec() is the PyQt5 version of this line using what you did was the old PyQt4 way of doing it and is only supported for backwards compatibility which means eventually it will not be supported so do not add it to new code unless you absolutely need it which imho is going to be extremely unlikely

Lastly these 5 lines of code are always a copy/paste/minor-tweak for me with every program I write using Python-PyQt and the only thing I ever tweak (change) is the class name being created (aka in this case TestWindow())
Reply
#5
Why does widget1 have to be a child of widget2 ?
That makes everything much more complicated.
Reply
#6
@Axel_Erfurt I believe it has to be a child in this case because the OP was putting Widget2 inside Widget1 and yes while more complex in nature it does serve a particular purpose that I will assume the OP is trying to implement
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  [Tkinter] What causes this pop-up to trigger? Oshadha 2 1,749 Dec-17-2020, 08:37 AM
Last Post: Oshadha
  Ho can I get the first parent of a class? panoss 2 3,576 Jan-10-2017, 08:10 AM
Last Post: panoss

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020