Pyqt5: Attempting to add QLayout “ ”Form", which already has a layout (multiple inheritance python) [duplicate]

一笑奈何 提交于 2021-02-20 04:30:07

问题


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 the ui_test.py file
    • it calls setupUi with mywidget (the MyWidget 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 with app.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 for app.py);
  • you don't need to call import sys in those if 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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!