Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Modified mousePressEvent() function applied to top level widgets running multiple times per click



  • Hello all,

    I’m new to Python, Qt, and programming in general.

    I am working on a module that involves finding the top level widgets in a window and changing their mousePressEvent functions, so the module can be used without the user (probably just me) having to manually change the top level mousePressEvent functions. However, I do not want it to override any modified mousePressEvent if someone has modified the original for other purposes. As an example, if this is the top level widget that contains 3 instances of my widget ‘MyWidget’ from my module, with a custom mousePressEvent added:

    class WIND(QWidget):
        def __init__(self, parent=None):
            super(WIND, self).__init__(parent)
            self.layout = QVBoxLayout()
            self.setLayout(self.layout)
            self.someWidget1 = MyWidget()
            self.someWidget2 = MyWidget()
            self.someWidget3 = MyWidget()
            self.layout.addWidget(self.someWidget1)
            self.layout.addWidget(self.someWidget2)
            self.layout.addWidget(self.someWidget3)
    
        def mousePressEvent(self, event): #I DON’T want this to be overwritten if someone (probably just me) uses my module
            super(WIND, self).mousePressEvent(event)
            print(“Modified mousePressEvent has been triggered”)
    

    Within MyWidget (a child of another class in the module, see my next post below) in the module, I find the top-level widgets with

    top_widgets = QApplication.topLevelWidgets()
    

    I then iterate through them, replacing their mousePressEvent with ‘newMousePressEvent’ from within my module:

    for tw in top_widgets:
                original_MPE = tw.mousePressEvent 
                MPE = partial(self.newMousePressEvent, tw, original_MPE)
                tw.mousePressEvent = MPE
    

    And here’s the function referenced in the partial function above, to act as the top level widget's mousePressEvent function while still triggering the other mousePressEvent:

    def newMousePressEvent(self, top_widget, original_mousePressEvent, event):
        """Overrides a class's mouse press event while still triggering the original mouse press event"""
        print(top_widget) 
        original_mousePressEvent(event)
    
        #do stuff here
    

    At least in my limited test, this code does work, activating both the modified WIND.mousePressEvent and the MyWidget.newMousePressEvent function. However, when I run the program and click somewhere in the window, the print lines reveal that the function does successfully run the modified WIND.mousePressEvent function once, but also runs the print(top_widget) line 3 times per click, outputting the exact same Widget object (WIND), corresponding to the 3 instances of MyWidget within WIND (the number of prints changed depending on how many instances I put in). This is what it prints:

    <__main__.WIND object at 0x7ff0687c2230>
    <__main__.WIND object at 0x7ff0687c2230>
    <__main__.WIND object at 0x7ff0687c2230>
    Modified mousePressEvent has been triggered
    

    Essentially, I technically got this to work, but I’m not sure why it’s running certain things in the newMousePressEvent function multiple times (except for the original_mousePressEvent function??). It would be great to get the function to only run once for the topmost widget.

    Instead of doing this, I also tried putting super(type(parent), parent).mousePressEvent(event) within the newMousePressEvent function, but this did not trigger the modified WIND mousePressEvent function.

    Thank you for any input into what is happening!


  • Banned

    okay you need to provide an MRE (Minimal Reproducible Example) otherwise we are going to be wildly guessing to the exact nature of you problem -- or have to do a lot of extra work on our side to figure out how to help you with YOUR problem -- thus causing an answer to be longer in coming or not at all



  • Sorry, I was trying to make it as simple as possible for the sake of responders. Ironically, I left out one part that now seems to me to be relevant to the issue: the fact that 'MyWidget' is a child of a parent 'ClickEdit'. I'm using inheritance because each child will be for handling how different built in QWidgets, such as QSpinBox, QTimeEdit, etc., respond to clicks within the window:

    class MyWidget(ClickEdit):
        #I have multiple widgets as children of ClickEdit, each for a different QWidget class
        def __init__(self):
            ClickEdit.__init__(self, QSpinBox, parent=None)
    

    Here's the entire code, hopefully as bare bones as possible:

    import sys
    from functools import partial
    from PySide2.QtWidgets import QApplication, QWidget, QLabel, QHBoxLayout, QVBoxLayout, QSpinBox
    
    class WIND(QWidget):
        def __init__(self, parent=None):
            super(WIND, self).__init__(parent)
            self.layout = QVBoxLayout()
            self.setLayout(self.layout)
            self.number = MyWidget()
            self.number2 = MyWidget()
            self.number3 = MyWidget()
    
            self.layout.addWidget(self.number)
            self.layout.addWidget(self.number2)
            self.layout.addWidget(self.number3)
    
        def mousePressEvent(self, event):
            super(WIND, self).mousePressEvent(event)
            print("Modified mousePressEvent has been triggered")
    
    class ClickEdit(QWidget):
        def __init__(self, type_of_edit_field, parent=None):
            super(ClickEdit, self).__init__(parent)
            self.QWidgettype = type_of_edit_field
            self.layout = QHBoxLayout()
            self.setLayout(self.layout)
    
            top_widgets = QApplication.topLevelWidgets()
            
            self.layout.addWidget(self.QWidgettype())
            
            for tw in top_widgets:
                original_MPE = tw.mousePressEvent 
                MPE = partial(self.newMousePressEvent, tw, original_MPE)
                tw.mousePressEvent = MPE
    
        def newMousePressEvent(self, top_widget, original_mousePressEvent, event):
            """Overrides a class's mouse press event while still triggering the original mouse press event"""
            print(top_widget) 
            original_mousePressEvent(event)
    
            #do stuff here: if the click is within the window but outside of the bounds of self.QWidgettype, self.QWidgettype will be changed.
    
    class MyWidget(ClickEdit):
        #I have multiple widgets as children of ClickEdit, each for a different QWidget class
        def __init__(self):
            ClickEdit.__init__(self, QSpinBox, parent=None)
    
    if __name__ == '__main__':
    
        app = QApplication(sys.argv)
    
        window = WIND()
        window.show()
    
        sys.exit(app.exec_())
    


  • I figured it out. It was something I definitely should have realized.

    Since I changed the top widget mouse press events in the init function of the ClickEdit class, it was assigning new mouse press events to the same top level widget again and again for every instance of ClickEdit in the window. I'm still ignorant of the mechanics of why it runs multiple times from being reassigned more than once, but I at least fixed the issue by creating a list called top_widgets_modified shared between all ClickEdit instances that consists of any top widgets already assigned a new mousePressEvent. If a top level widget has already been assigned a new mouse press event, it skips it in the next ClickEdit init.

    Alternatively, It might work to have it iterate through the top widgets once per window instead of for every ClickEdit init, though I'll have to make sure it won't miss anything by doing this.

    class ClickEdit(QWidget):
        top_widgets_modified = []
        
        def __init__(self, type_of_edit_field, parent=None):
            super(ClickEdit, self).__init__(parent)
            self.QWidgettype = type_of_edit_field
            self.layout = QHBoxLayout()
            self.setLayout(self.layout)
    
            top_widgets = QApplication.topLevelWidgets()
            
            self.layout.addWidget(self.QWidgettype())
    
            for tw in top_widgets:
                if tw in self.top_widgets_modified:
                     continue
                self.top_widgets_modified.append(tw)
                
                original_MPE = tw.mousePressEvent 
                MPE = partial(self.newMousePressEvent, tw, original_MPE)
                tw.mousePressEvent = MPE
    

Log in to reply