Solved 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!
-
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