【问题标题】:Pyqt5: Attempting to add QLayout " "Form", which already has a layout (multiple inheritance python) [duplicate]Pyqt5:尝试添加QLayout“表单”,它已经有一个布局(多继承python)[重复]
【发布时间】:2020-07-24 15:00:58
【问题描述】:

我使用 qtDesigner 创建了一个 ui 文件 window.ui(包含一个选项卡小部件)和一个小部件文件 student(一些按钮、功能),然后进行转换使用 pyuic5 进入 py 文件。并继承在单独的文件中,例如 ma​​inWindow.pyma​​inStudent.py

我在 mainWindow.py 中添加了一个 tabWidget,我想从 tab 调用页面 student.py 。 所以我创建了一个新文件 app.py ,我首先从 ma​​inWindow.py 继承类并添加一个选项卡调用 student 并尝试从 ma​​inStudent 继承类。 py.

我的目标是如果我运行 app.py ,那么 ma​​inWindow 将与 tabwidget 一起出现,其中选项卡名称为“student”,如果我点击了学生标签,然后所有元素都将从“ma​​inStudent.py”中显示出来。

但我收到此错误: 正在尝试将 QLayout "" 添加到 studentPage "Form",它已经有一个布局 (注意:功能正常)

我不知道我在哪里做错了!请帮忙!

window.py(使用 pyuic5 从 window.ui 生成)

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(使用 pyuic5 从 window.ui 生成)

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_())

ma​​inWindow.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_())

ma​​inStudent.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_())

【问题讨论】:

    标签: python pyqt pyqt5


    【解决方案1】:

    tl;博士

    您使用了不必要的文件级别和子类化,并且您调用setupUi 的次数过多。
    发生该错误是因为您不应该多次尝试重建 GUI。

    虽然在多个文件中进行重构是一种很好的做法,但这并不意味着您应该总是这样做。
    查看您的代码,这确实没有任何优势。

    例如,mainWindow 文件是完全不需要的:只需使用相同的概念在app.py 文件中创建MainWindow 类,并将编程逻辑添加到那个 类。

    那么,stdMainWindow 类已经有了它自己的 GUI 设置,所以你应该只导入和使用 那个 类,因为另一个子类化是没有意义的。


    由于您显然对此仍然很困惑,我将尝试更详细地解释 Qt 如何处理 UI 数据。
    对于这种情况,我将使用一个简单的 QWidget 表单,它具有垂直布局和一个按钮。我还建议您仔细阅读并深入学习有关using Qt Designer 的指南,并确保您真正了解其所有内容,因为那里有很多信息必须完全理解。
    不要着急:做实验,慢慢阅读代码,并尝试自己理解会发生什么,包括如何为什么会发生。

    使用pyuic生成的代码

    单继承方法

    你从pyuic 得到的是一个非常基本的 Python object 类:就其本身而言,它没有也没有做任何事情:实际上,它没有 __init__ 方法,它主要是一个使用的“方便”类将对象“组合”在一个公共实例对象中。

    当您调用它的 setupUi 方法时,魔法就会发生一个小部件实例作为它的参数。

    这是 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"))
    

    让我们忽略retranslateUiconnectSlotsByName 部分,因为它们对我们的需求并不重要。

    如果我们遵循单继承方法,那么我们应该如何编写实际创建小部件的文件(我使用pyuic5 test.ui -o ui_test.py 生成了 ui 文件):

    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_())
    

    当你运行上面的文件时会发生这样的事情:

    • 文件是主脚本,所以进入if语句
    • 它创建一个 QApplication 实例(这是创建基于 GUI 的对象所必需的)
    • 它创建了一个MyWidget实例,其含义如下:
      • MyWidget 进入其__init__
      • 它创建一个Ui_Form实例,从ui_test.py 文件导入
      • 它以mywidgetMyWidget实例)作为参数调用setupUi

    现在,让我们看看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)
    

    结果将是布局按钮将是mywidget.ui 的属性。您可以使用self.ui.verticalLayoutself.ui.pushButton 从小部件类访问它们,或者在它之外使用mywidget.ui.verticalLayoutmywidget.ui.pushButton


    为了完整起见,让我们完成程序执行步骤:

    • mywidget 实例已创建
    • 它现在正在“显示”(但是,此时它实际上还不可见!)
    • sys.exitapp.exec_() 作为参数调用

    对 QApplication 的 exec() 调用(与它的祖先一样,QGuiApplication.exec()QCoreApplication.exec())被阻塞:它们将输入自己的 event loop,等待发生某些事情(通常,来自用户的鼠标/键盘交互,或其他一些系统事件)并且不会返回,直到某些东西使他们结束它(通常,用户关闭最后一个窗口)。因此,只要应用程序“正在运行”,sys.exit不会被实际调用。

    在应用程序可能等待的事件中,有一些实际上与 GUI 相关,并且在整个过程开始后发生:创建窗口框架(与系统就字体、分辨率等进行各种级别的通信)等),小部件的实际“绘制”(之后窗口实际上显示给用户)和许多其他的。

    最后,一旦应用程序以某种方式退出,它会将其return code 返回到sys.exit,这将最终退出您的python 程序。

    多重继承方法

    当使用多重继承时,事情并没有太大变化:不同之处在于MyWidget 继承了QWidget Ui_Form both,所以,当setupUi 被调用,“self”将是 mywidget 实例,不会有任何 self.ui 在起作用:只有 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)
    
    # ...
    

    在这种情况下,布局和按钮可以直接访问(self.verticalLayoutself.pushButton),这是因为 self Form 是同一个对象:

        def setupUi(self, Form):
            # "Form" is actually "mywidget", as "self" is
            Form.setObjectName("Form")
            Form.resize(320, 240)
            self.verticalLayout = QtWidgets.QVBoxLayout(Form)
            # ...
    

    这意味着setupUi 函数在技术上也可以以这种方式重写(假设您调用self.setupUi() 不带参数):

    
        # note that there's no argument here besides "self"
        def setupUi(self):
            self.setObjectName("Form")
            self.resize(320, 240)
            self.verticalLayout = QtWidgets.QVBoxLayout(self)
            # ...
    

    这种方法可以帮助我们更好地理解 setupUi 究竟做了什么,因为调用该函数完全如下(请注意,除了 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")
    

    现在。这是最常用的方法,因为它比另一种更简单、更直接:您将小部件视为类实例的直接“子”,因此通过“子子”对象访问它们似乎有点不必要喜欢self.ui

    不过,这样做有一个缺点:您必须注意对象命名。由于这种方法会自动使用 Designer 中使用的对象名称来设置实例属性名称,因此您必须确保这些名称不会在其他地方使用,这也是因为 Python 访问对象的方式。
    例如,如果您有一个名为 saveFile 的函数,并且您也将一个按钮命名为 saveFile(我显然是在谈论 Designer 对象名称,而不是按钮的标签),您将无法直接访问该函数不再,因为 setupUi 会覆盖它:

    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
    

    会发生以下情况:

    What is "saveFile"? <bound method MyWidget.saveFile of <__main__.MyWidget object at 0xb21cc1dc>>
    What is "saveFile"? <PyQt5.QtWidgets.QPushButton object at 0xb21cc26c>
    

    好吧,说实话;您仍然可以访问该方法,但不能直接访问:

        # here we are calling the saveFile method with the instance as its argument
        MyWidget.saveFile(self)
    

    但这不是很方便,对吧?


    使用uic.loadUi

    此方法允许您立即跳过 pyuic 步骤,因为它从使用 Designer 创建的 tue .ui 文件动态创建 UI(相对路径总是相对于加载它们)。这非常方便,因为如果您在编辑时不记得保存或重建 ui 文件,可能会导致一些错误或不一致。

    此方法的行为几乎类似于多重继承,因此您将以与上述相同的方式获得self.verticalLayoutself.pushButton 对象。

    from PyQt5 import QtWidgets, uic
    
    class MyWidget(QtWidgets.QWidget):
        def __init__(self):
            super().__init__()
            uic.loadUi('test.ui', self)
    

    在这种情况下,“self”参数的处理方式与多重继承示例中使用的 setupUi 函数完全相同:setupUi 函数中的所有 self.* 对象都创建为 @ 的属性987654405@/Form。这显然意味着前面解释的相同命名缺陷也适用于这种情况。

    不幸的是,这样做还有一个小缺点:有时布局的默认边距会被忽略并设置为 0,无论您在 ui 文件中设置什么。不过,有一个解决方法,您可以在 Size of verticalLayout is different in Qt Designer and PyQt program 上阅读更多信息。


    最后,还有一些建议:

    • 通常的约定是类名使用大写,变量和函数只使用小写;
    • 关于以上几点,也尽量遵循Style Guide for Python Code(又名PEP 8),尤其是在与他人共享您的代码时;
    • 所有这些if __name__ == '__main__' 语句都是不必要的,因为您可能不会单独运行这些文件;仅在必要时使用这种语句(在您的情况下,仅适用于app.py);
    • 如果你一开始就已经导入了import sys,则不需要在那些if语句中调用它;
    • 不要过度使用粗体样式,因为这会使您的帖子分散注意力并且难以阅读;阅读更多关于using markdown、如何format your code 以及最后how to ask 好问题(包括其相关链接)的信息;

    【讨论】:

    • 非常感谢您给了我宝贵的 cmets。这真的对我有很大帮助。所以,我决定在开始我的项目之前阅读所有文档(python、pyqt5、qtdesigner、PEP 8)。
    • @varatbhusan 不客气。我知道我可能会非常冗长,但我在这里已经有一段时间了(嗯,没有其他人那么多,但这不是重点),作为一名老师,我真的很关心人们明白我在说什么。我很高兴你要学习所有这些,它们所有都是非常重要的学科:我必须自己学习它们(主要是通过犯错误),这并不容易完全没有(但是,这也很有趣,让我们说吧),所以我很高兴知道我能够以“正确”的方式减轻其他人的“痛苦”。祝你好运! :) :y
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多