How can QUiLoader load ui to "self" and trigger closeEvent
-
This is how it is used for most ppl I think:
class Mainwin():#inherit QMainWindow or not wont make any difference. The right logic here is not to I think. def __init__(self): super().__init__() self.LoadUI() self.ui.show() def LoadUI(self): loader=QUiLoader() uifile=QFile('youruifile.ui') uifile.open(QFile.ReadOnly) self.ui = loader.load(uifile,self) uifile.close()
Is there a way to use QUiLoader to load ui to "self"?
so I can access widgets by self.widget instead of self.ui.widget.Also, since it is not loading ui to self, the class where I load the ui is just a shell, defining closeEvent in it won't work.
Since self.ui is the real QMainWindow so I tried override the closeEvent of the class but still it won't work.
def closeEvent(self, event:QCloseEvent) -> None: print('closed') QWidget.closeEvent=closeEvent QMainWindow.closeEvent=closeEvent
-
@sylvalas said in How can QUiLoader load ui to "self" and trigger closeEvent:
Is there a way to use QUiLoader to load ui to "self"?
Why would you want to do so?
What is the problem using self.ui? ui is a member of your class (so "inside" self)."defining closeEvent in it won't work" - subclass QMainWindow and override closeEvent().
See https://stackoverflow.com/questions/12365202/how-do-i-catch-a-pyqt-closeevent-and-minimize-the-dialog-instead-of-exiting -
@jsulm
I changed from pyqt5 to pyside6 I dont wanna go through it all and rewrite those lol but that's not the biggest problem.The biggest problem is closeEvent, with pyside, the window you close is not actually self but self.ui so it somehow just won't trigger the closeEvent if you define it in the class. That was why I tried to override the method of the QMainWindow class, cuz that's what QUiLoader() returns
-
@sylvalas said in How can QUiLoader load ui to "self" and trigger closeEvent:
the window you close is not actually self but self.ui
No. If Mainwin is a QMainWindow subclass then you can override closeEvent() like any other.
-
@jsulm
I can't upload files so if you create a ui file and try this:from PySide6.QtWidgets import QApplication from PySide6.QtWidgets import QMainWindow from PySide6.QtUiTools import QUiLoader from PySide6.QtCore import QFile import sys class MainThread(QMainWindow): def __init__(self): super().__init__() self.LoadUI() # This gives you a blank window if you close this one, it'll trigger the closeEvent self.show() #This is the real window but won't trigger closeEvent self.ui.show() def LoadUI(self): loader=QUiLoader() uifile=QFile('testui.ui') uifile.open(QFile.ReadOnly) self.ui = loader.load(uifile,self) uifile.close() def closeEvent(self, event): super(MainThread, self).closeEvent(event) print('closed') if __name__== '__main__': app = QApplication(sys.argv) mainthread=MainThread() sys.exit(app.exec_())
-
@sylvalas said in How can QUiLoader load ui to "self" and trigger closeEvent:
I changed from pyqt5 to pyside6 I dont wanna go through it all and rewrite those lol but that's not the biggest problem.
This is the problem area. Unfortunately PyQt and PySide use different ways to implement loading UI from file, and that is why you are having trouble with
self.ui
.If you say
self.ui.show()
is what "shows the real window" then that meansself.ui
is yourQMainWindow
. And so yourdef closeEvent(self, event):
override needs to be in the class of theself.ui
, not in yourclass MainThread(QMainWindow)
.I notice https://doc.qt.io/qt-5/quiloader.html says:
In addition, you can customize or create your own user interface by deriving your own loader class.
I don't know if that is the clue to what you are supposed to do to achieve what you want.
Before you spend too long on this. Are you wedded to using
QUiLoader
on a.ui
file at runtime? When I did Python I preferred to use theuic
to generate Python code from.ui
files, just like when using C++. It gives you more design/development-time support for your widgets. It is true, however, that theuic
must be re-run every time you alter the.ui
file. I don't know whether Creator now recognises and supports this if using Python/PySide, I think it might now. If you chose that you have a Python class where you can override methods like in C++.This is all covered in Using .ui files from Designer or QtCreator with QUiLoader and pyside6-uic. I like the Option A: Generating a Python class there.
-
@sylvalas
That's not theuic
I mean. You are talking about a PyQt class of that name, andloadUi("file.ui")
at runtime. I was talking about theuic
"pre-processor command" in PySide, I think it'spyuic
in PyQt5. Which takes a different approach, no runtime.ui
file, instead it produces Python source code from it when you develop, and you get a dedicated class for your .ui
files. -
I totally get the OP as I encountered the same situation. His question is not directly answered here. I get there are 2 ways to inflate .ui files, pre-compiled to a python file using the uic or compiling it at runtime using the quiloader. Let's focus on the latter option, could someone please let me know how I can hook the closeEvent in this case?
I found a potential solution here: https://stackoverflow.com/q/14892713/1592410. But it seems to be too hacky as one have to recreate the UI loader.
-
@changyuheng
Well it is possible any solution will be a bit "hacky" for you :)You are wanting to override
closeEvent()
. Normally that means you must subclass to achieve that, at least in C++.However, untested but from my knowledge of Python: can't you "monkey-patch" to achieve what you want? In your instance can't you go
instance.showEvent = myShowEvent
or some such, e.g. https://stackoverflow.com/a/6647776/489865from SomeOtherProduct.SomeModule import SomeClass def speak(self): return "ook ook eee eee eee!" SomeClass.speak = speak
Not sure whether you can monkey-patch on an instance or only for the whole class --- ah, https://filippo.io/instance-monkey-patching-in-python/ for example shows instance monkey-patching.
If you find this too hacky I leave it for another person to propose something else acceptable to you.
-
Thanks for replying, @JonB.
Well it is possible any solution will be a bit "hacky" for you :)
Well, if I can fix an issue by calling the right API or playing with a few APIs, that's a normal solution. If I have to add customized logics that should be done in the library, that's a hack. If I have to patch QUiLoader or recreate one in order to make the built-in hook function closeEvent of QWidget work, it's obviously a hack.
You can't do the "monkey-patch" for the closeEvent if you have read the original post.
I can create a subclass of QMainWindow. But is there a way I can make QUiLoader load a .ui file onto my extended QMainWindow?
-
@changyuheng said in How can QUiLoader load ui to "self" and trigger closeEvent:
You can't do the "monkey-patch" for the closeEvent if you have read the original post.
I did read the original post. Did you read the link to "instance monkey-patching" I posted? I can't see anything there which cannot be done by replacing
self
with the instance you want patched from outside the class, which you have available inself.ui
. But perhaps you know Python better than I do.I can create a subclass of QMainWindow. But is there a way I can make QUiLoader load a .ui file onto my extended QMainWindow?
I believe you are supposed to do this via PySide6.QtUiTools.QUiLoader.registerCustomWidget(customWidgetType)
This is needed when you want to override a virtual method of some widget in the interface, since duck punching will not work with widgets created by QUiLoader based on the contents of the .ui file.
I know nothing about hitting birds with gloves. A stackoverflow post may have said problems getting this to work with PySide2, don't know if the coder got it right, don't know if it's better in Qt6.
-
I did read the original post. Did you read the link to "instance monkey-patching" I posted? I can't see anything there which cannot be done by replacing self with the instance you want patched from outside the class, which you have available in self.ui. But perhaps you know Python better than I do.
I know what monkey-patching is. Yes, replacing the closeEvent method insdie self.ui object that was generated by QUiLoader is supposed to work. But I'm telling you it's not working as I have tested it. And this is addressed by the OP at the beginning. You also pointed it out already at:
PySide6.QtUiTools.QUiLoader.registerCustomWidget(customWidgetType)
This is needed when you want to override a virtual method of some widget in the interface, since duck punching will not work with widgets created by QUiLoader based on the contents of the .ui file.So...
I believe you are supposed to do this via PySide6.QtUiTools.QUiLoader.registerCustomWidget(customWidgetType)
This is promising. I've thought of this too. I didn't try it yet because I'm currently having the UI loader run inside my own QMainWindow. But I think this is the way to go. Thank you.
-
In case anyone else runs into the same situation. To load (inflate) a .ui file by QUiLoader with a customized QMainWindow is possible, and it should be the way to go if customizing the closeEvent or any other built-in behaviors is the goal.
However, promoting QMainWindow is not allowed from Qt Designer at the moment. This has to be done by manually editing the .ui file:
- <widget class="QMainWindow" name="MainWindow"> + <widget class="QMainWindowExt" name="MainWindow">
<customwidgets> + <customwidget> + <class>QMainWindowExt</class> + <extends>QMainWindow</extends> + <header>QMainWindowExt.h</header> + </customwidget> </customwidgets>
And from the python code,
registerCustomWidget(QMainWindowExt)
is required before loading the .ui file. An example:ui_loader: QUiLoader = QUiLoader() ui_loader.registerCustomWidget(QMainWindowExt) main_window_ui: pathlib.Path with importlib.resources.path(__package__, 'main_window.ui') as main_window_ui: main_window_ui_file: QFile = QFile(str(main_window_ui)) if not main_window_ui_file.open(QIODevice.ReadOnly): raise RuntimeError(f"Cannot open {main_window_ui}: {main_window_ui_file.errorString()}") main_window: QMainWindowExt = ui_loader.load(main_window_ui_file) main_window_ui_file.close()
Of course, you'll have to create a
QMainWindowExt
:class QMainWindowExt(QMainWindow): ...