Reputation: 2433
The following problem happens with PyQt5, Qt 5.9.6.
I want to generate modal dialog class (Dialog
) based on another class (UserForm
). But if UserForm
already inherits from QWidget
then my script raises exception
'Dialog' object has no attribute 'exec_'
or just crashes silently or crashes with a message in console:
QDialog::exec: Recursive call detected
MRO looks exactly the same if UserForm
is inherited from QWidget or not:
(<class '__main__.Dialog'>, <class '__main__.UserForm'>, <class
'PyQt5.QtWidgets.QDialog'>, <class 'PyQt5.QtWidgets.QWidget'>, <class
'PyQt5.QtCore.QObject'>, <class 'sip.wrapper'>, <class
'PyQt5.QtGui.QPaintDevice'>, <class 'sip.simplewrapper'>, <class 'object'>)
Example:
from PyQt5 import QtWidgets
app = QtWidgets.QApplication([])
class UserForm(QtWidgets.QWidget):
pass
# class Dialog(UserForm, QtWidgets.QDialog):
# pass
Dialog = type("Dialog", (UserForm, QtWidgets.QDialog), {})
print(Dialog.__mro__)
Dialog().exec_()
I've also tried to run this code with PySide2, Qt 5.12 and it works without issues. Does it mean that there's some bug in PyQt?
Upvotes: 2
Views: 894
Reputation: 243945
On the contrary, it is a bug of PySide2 since it contradicts the docs:
Multiple Inheritance Requires QObject to Be First
If you are using multiple inheritance, moc assumes that the first inherited class is a subclass of QObject. Also, be sure that only the first inherited class is a QObject.
If the behavior described in the previous part is considered then the Dialog class should only take into account UserForm and not QDialog so Dialog should not have the exec_() method since only the classes that inherit from QDialog can have it.
In Qt you should not inherit from 2 QObject, only supports mixins (QObject + not QObject) (1)(2).
If it is a mixin then the first must be the QObject.
And that's the pattern that pyuic to inherit:
from PyQt5 import QtCore, QtGui, QtWidgets
# or
# from PySide2 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(400, 300)
self.buttonBox = QtWidgets.QDialogButtonBox(Dialog)
self.buttonBox.setGeometry(QtCore.QRect(30, 240, 341, 32))
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(
QtWidgets.QDialogButtonBox.Cancel | QtWidgets.QDialogButtonBox.Ok
)
self.buttonBox.setObjectName("buttonBox")
self.retranslateUi(Dialog)
self.buttonBox.accepted.connect(Dialog.accept)
self.buttonBox.rejected.connect(Dialog.reject)
QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog):
_translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
def init(self, parent=None):
super(self.__class__, self).__init__(parent)
self.setupUi(self)
# or
# class Dialog(QtWidgets.QDialog, Ui_Dialog):
# def __init__(self, parent=None):
# super(Dialog, self).__init__(parent)
# self.setupUi(self)
Dialog = type("Dialog", (QtWidgets.QDialog, Ui_Dialog), {"__init__": init})
if __name__ == "__main__":
app = QtWidgets.QApplication([])
Dialog().exec_()
(1) Multiple Inheritance
(2) Cooperative Multi-inheritance
Upvotes: 2