问题
i have created a ui file, window.ui (consist with a tab widget) and a Widget file student(some buttons,functions) using qtDesigner and than convert into py file using pyuic5. and inherit in a separate file like mainWindow.py and mainStudent.py.
i added a tabWidget into mainWindow.py and i want to call the page student.py from the tab . so i create a new file app.py ,where i first inherit class from mainWindow.py and add a tab call student and try to inherit class from mainStudent.py.
my goal is if i run app.py , than mainWindow will appear with tabwidget where tab name is "student" and if i hit the student tab than all elements will be show from "mainStudent.py".
but i ame getting this error: Attempting to add QLayout "" to studentPage "Form", which already has a layout (Note: function is working fine )
i don't know where i did mistake! please help!
window.py (generated from window.ui using pyuic5)
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 600)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
self.verticalLayout.setObjectName("verticalLayout")
self.label = QtWidgets.QLabel(self.centralwidget)
font = QtGui.QFont()
font.setPointSize(20)
self.label.setFont(font)
self.label.setObjectName("label")
self.verticalLayout.addWidget(self.label)
self.tabWidget = QtWidgets.QTabWidget(self.centralwidget)
self.tabWidget.setObjectName("tabWidget")
self.verticalLayout.addWidget(self.tabWidget)
MainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.label.setText(_translate("MainWindow", "Main Window"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
student.py (generated from window.ui using pyuic5)
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(716, 635)
self.gridLayout_2 = QtWidgets.QGridLayout(Form)
self.gridLayout_2.setObjectName("gridLayout_2")
self.label = QtWidgets.QLabel(Form)
font = QtGui.QFont()
font.setPointSize(16)
self.label.setFont(font)
self.label.setObjectName("label")
self.gridLayout_2.addWidget(self.label, 0, 0, 1, 1)
self.tabWidget = QtWidgets.QTabWidget(Form)
self.tabWidget.setObjectName("tabWidget")
self.tab = QtWidgets.QWidget()
self.tab.setObjectName("tab")
self.gridLayout = QtWidgets.QGridLayout(self.tab)
self.gridLayout.setObjectName("gridLayout")
self.pushButton = QtWidgets.QPushButton(self.tab)
self.pushButton.setObjectName("pushButton")
self.gridLayout.addWidget(self.pushButton, 0, 0, 1, 1)
self.tabWidget.addTab(self.tab, "")
self.tab_2 = QtWidgets.QWidget()
self.tab_2.setObjectName("tab_2")
self.gridLayout_3 = QtWidgets.QGridLayout(self.tab_2)
self.gridLayout_3.setObjectName("gridLayout_3")
self.pushButton_2 = QtWidgets.QPushButton(self.tab_2)
self.pushButton_2.setObjectName("pushButton_2")
self.gridLayout_3.addWidget(self.pushButton_2, 0, 0, 1, 1)
self.tabWidget.addTab(self.tab_2, "")
self.gridLayout_2.addWidget(self.tabWidget, 1, 0, 1, 1)
self.retranslateUi(Form)
self.tabWidget.setCurrentIndex(0)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Form"))
self.label.setText(_translate("Form", "Student Page"))
self.pushButton.setText(_translate("Form", "Test Function"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("Form", "Regular"))
self.pushButton_2.setText(_translate("Form", "Test Second Function"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("Form", "Yearly"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
Form = QtWidgets.QWidget()
ui = Ui_Form()
ui.setupUi(Form)
Form.show()
sys.exit(app.exec_())
mainWindow.py
from PyQt5 import QtCore, QtGui, QtWidgets
import sys
from files.main_interfaces.window import Ui_MainWindow
class MainWindow(QtWidgets.QMainWindow,Ui_MainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setupUi(self)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
mainStudent.py
from PyQt5 import QtCore, QtGui, QtWidgets
import sys
from files.main_interfaces.student import Ui_Form
class stdMainWindow(QtWidgets.QWidget,Ui_Form):
def __init__(self, parent=None):
super(stdMainWindow, self).__init__(parent)
self.setupUi(self)
self.pushButton.clicked.connect(self.function1)
def function1(self):
print("function called")
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = stdMainWindow()
w.show()
sys.exit(app.exec_())
app.py
from PyQt5 import QtCore, QtGui, QtWidgets
from mainWindow import MainWindow
from mainStudent import stdMainWindow
class studentPage(stdMainWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setupUi(self)
class MainWindow3(MainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
# Add tab
self.studentPage = studentPage()
self.tabWidget.addTab(self.studentPage, 'Student')
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
window = MainWindow3()
window.show()
sys.exit(app.exec_())
回答1:
tl;dr
You are using unnecessary levels of files and subclassing, and you're calling setupUi
too many times.
That error happens because you're trying to rebuild the GUI more than once, which you shouldn't.
While doing refactoring within multiple files is a good practice, it doesn't mean that you should always do it.
Looking at your code, there's really no advantage in that.
For example, the mainWindow
file is completely unnecessary: just create the MainWindow
class in the app.py
file using the same concepts, and add the programming logic to that class.
Then, the stdMainWindow
class already has it's own GUI setup, so you should just import and use that class, as another subclassing is meaningless.
Since you're clearly still very confused about this, I'll try to explain how Qt deals with UI data more in detail.
For this case I'll use a simple QWidget form with a vertical layout and a single push button on it. I also suggest you to carefully read and deeply study the guide about using Qt Designer and ensure that you really understand all of its contents, as there's a lot of information there that has to be fully understood.
Don't rush it: make experiments, slowly read the code, and try to understand on your own what happens, including how and why that happens.
Using the code generated by pyuic
The single inheritance method
What you get from pyuic
is a very basic Python object
class: on its own, it has nor does nothing: in fact, there's no __init__
method in it, it's mostly a "convenience" class used to "group" objects together within a common instance object.
The magic happens when you call its setupUi
method with a widget instance as its argument.
Here's the generated output from pyuic:
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(320, 240)
self.verticalLayout = QtWidgets.QVBoxLayout(Form)
self.verticalLayout.setObjectName("verticalLayout")
self.pushButton = QtWidgets.QPushButton(Form)
self.pushButton.setObjectName("pushButton")
self.verticalLayout.addWidget(self.pushButton)
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Form"))
self.pushButton.setText(_translate("Form", "PushButton"))
Let's just ignore the retranslateUi
and connectSlotsByName
parts, as they're not that important for our needs here.
If we're following the single inheritance method, here's how we should write the file that actually creates the widget (I generated the ui file with pyuic5 test.ui -o ui_test.py
):
from PyQt5 import QtWidgets
from ui_test import Ui_Form
class MyWidget(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.ui = Ui_Form()
self.ui.setupUi(self)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
mywidget = MyWidget()
mywidget.show()
sys.exit(app.exec_())
That's what's going to happen when you run the file above:
- the file is the main script, so it enters the
if
statement - it creates a QApplication instance (which is mandatory to create GUI based objects)
- it creates an instance of
MyWidget
, which means the following:MyWidget
enters its__init__
- it creates an instance of
Ui_Form
, imported from theui_test.py
file - it calls
setupUi
withmywidget
(theMyWidget
instance) as its argument
Now, let's see what's happening inside setupUi
:
class Ui_Form(object):
def setupUi(self, Form):
# "Form" is actually "mywidget" (the instance), so it will set the
# object name and size for that instance object
Form.setObjectName("Form")
Form.resize(320, 240)
# the above is exactly the same as doing the following, **inside**
# the __init__ of MyWidget:
#
# self.setObjectName("Form")
# self.resize(320, 240)
# create a layout with the "mywidget" argument, which automatically
# sets the layout for that instance; note that the new object is created
# as an attribute of "self", which in this case is the "self.ui"
# of "mywidget"
self.verticalLayout = QtWidgets.QVBoxLayout(Form)
self.verticalLayout.setObjectName("verticalLayout")
# create a pushbutton with the "mywidget" as a parent; this is
# usually not required if you're adding the widget to a layout, as
# it will take its ownership automatically; as with the layout,
# the "pushButton" attribute is created for "self.ui"
self.pushButton = QtWidgets.QPushButton(Form)
self.pushButton.setObjectName("pushButton")
# add the button to the layout
self.verticalLayout.addWidget(self.pushButton)
The result will be that the layout and the button will be attributes of mywidget.ui
. You can access them from the widget class by using self.ui.verticalLayout
and self.ui.pushButton
, or outside of it with mywidget.ui.verticalLayout
and mywidget.ui.pushButton
.
For the sake of completeness, let's finish the program execution steps:
- the
mywidget
instance has been created - it's now being "shown" (but, at this point, it's not actually visible yet!)
sys.exit
is called withapp.exec_()
as argument
An exec() call on a QApplication (as with its ancestors, QGuiApplication.exec() and QCoreApplication.exec()) is blocking: they will enter their own event loop, waiting for something to happen (normally, mouse/keyboard interaction from the user, or some other system event) and will not return until something make them end it (usually, the user closes the last window). So, as long as the application is "running", sys.exit
will not be actually called.
Amongst the events the application might wait for, there are some that are actually GUI related and happen just after the beginning of the whole process: the creation of the window frame (with various levels of communication with the system about fonts, resolution, etc), the actual "painting" of the widgets (after which the window is actually shown to the user) and many others.
Finally, as soon as the application quits in some way, it will return its return code to sys.exit
, which will eventually quit your python program.
The multiple inheritance method
Things don't change that much when using multiple inheritance: the difference is that MyWidget
inherits both from QWidget
and Ui_Form
, so, when setupUi
is called, "self" will be the mywidget
instance and there won't be any self.ui
at play: just self
.
from PyQt5 import QtWidgets
from ui_test import Ui_Form
class MyWidget(QtWidgets.QWidget, Ui_Form):
def __init__(self):
super().__init__()
self.setupUi(self)
# ...
In this case, the layout and button are directly accessible (self.verticalLayout
and self.pushButton
) and that's because both self
and Form
are the same object:
def setupUi(self, Form):
# "Form" is actually "mywidget", as "self" is
Form.setObjectName("Form")
Form.resize(320, 240)
self.verticalLayout = QtWidgets.QVBoxLayout(Form)
# ...
This means that the setupUi
function could also technically be rewritten in this way (assuming that you call self.setupUi()
without arguments):
# note that there's no argument here besides "self"
def setupUi(self):
self.setObjectName("Form")
self.resize(320, 240)
self.verticalLayout = QtWidgets.QVBoxLayout(self)
# ...
This approach can help us to better understand what setupUi
exactly does, since calling that function is exactly as doing the following (note that there's no other inheritance than QtWidgets.QWidget
):
class MyWidget(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.setObjectName("Form")
self.resize(320, 240)
self.verticalLayout = QtWidgets.QVBoxLayout(self)
self.verticalLayout.setObjectName("verticalLayout")
self.saveFile = QtWidgets.QPushButton(self)
self.saveFile.setObjectName("saveFile")
Now. This is the most common method, as it's simpler and more straightforward than the other: you think of a widget as a direct "child" of the class instance, so it seems a bit unnecessary to access them by a "sub-child" object like self.ui
.
There is a drawback with this, though: you have to be careful with the object naming. Since this approach automatically uses the object names used in Designer to set the instance attribute names, you have to be sure that those names are not used elsewhere, and that's also because of the way Python accesses objects.
For example, if you have a function named saveFile
and you name a button saveFile
too (I'm obviously talking about the Designer object name, not the button's label), you won't be able to directly access that function anymore, since setupUi will have it overwritten:
from PyQt5 import QtWidgets
from ui_test import Ui_Form
class MyWidget(QtWidgets.QWidget, Ui_Form):
def __init__(self):
super().__init__()
print('What is "saveFile"?', self.saveFile)
self.setupUi(self)
print('What is "saveFile"?', self.saveFile)
def saveFile(self):
pass
The following will happen:
What is "saveFile"? <bound method MyWidget.saveFile of <__main__.MyWidget object at 0xb21cc1dc>>
What is "saveFile"? <PyQt5.QtWidgets.QPushButton object at 0xb21cc26c>
Ok, to be honest; you still can have access to that method, but not in a straightforward way:
# here we are calling the saveFile method with the instance as its argument
MyWidget.saveFile(self)
But that's not very convenient, right?
Using uic.loadUi
This method allows you to skip the pyuic
step at once, as it dynamically creates the UI from tue .ui
files created with Designer (relative paths are always relative to the python file that loads them). This can be very handy, as you might incur into some error or inconsistency if you don't always remember to save or rebuild an ui file whenever you edit it.
This method behaves almost as the multiple inheritance one, so you'll get your self.verticalLayout
and self.pushButton
objects in the same way as above.
from PyQt5 import QtWidgets, uic
class MyWidget(QtWidgets.QWidget):
def __init__(self):
super().__init__()
uic.loadUi('test.ui', self)
In this case, the "self" argument is treated exactly in the same way as per the setupUi
function used in the multiple inheritance example: all self.*
objects in the setupUi
function are created as attribute of self
/Form
. This obviously means that the same naming drawback explained before is valid for this situation too.
Unfortunately, there's another small drawback with this: sometimes the default margins of layouts are ignored and set to 0, no matter what you set in the ui file. There's a workaround about that, though, you can read more on Size of verticalLayout is different in Qt Designer and PyQt program.
Finally, some further suggestions:
- it's common convention to use capitalized names for classes and lower case names for variables and functions only;
- about the point above, also try to generally follow the Style Guide for Python Code (aka, PEP 8), especially when sharing your code with others;
- all those
if __name__ == '__main__'
statements are unnecessary since you're probably not going to run those files individually; use that kind of statement just when necessary (in your case, only forapp.py
); - you don't need to call
import sys
in thoseif
statement if you've already imported it at the beginning; - don't overuse bold styling too much, as it makes your posts distracting and more difficult to read; read more on using markdown, how to format your code and, finally, how to ask good questions (including its related links);
来源:https://stackoverflow.com/questions/61164402/pyqt5-attempting-to-add-qlayout-form-which-already-has-a-layout-multiple