Pyqt5:尝试添加 QLayout "“Form",已经有了布局(多继承python) [英] Pyqt5: Attempting to add QLayout " "Form", which already has a layout (multiple inheritance python)

查看:51
本文介绍了Pyqt5:尝试添加 QLayout "“Form",已经有了布局(多继承python)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我使用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"添加到已经有布局的studentPageForm"中(注意:功能工作正常)

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

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

from PyQt5 import QtCore, QtGui, QtWidgets类 Ui_MainWindow(对象):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)字体 = QtGui.QFont()font.setPointSize(20)self.label.setFont(字体)self.label.setObjectName("标签")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.translateMainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))self.label.setText(_translate("MainWindow", "Main Window"))如果 __name__ == "__main__":导入系统app = QtWidgets.QApplication(sys.argv)MainWindow = QtWidgets.QMainWindow()ui = Ui_MainWindow()ui.setupUi(主窗口)MainWindow.show()sys.exit(app.exec_())

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

from PyQt5 import QtCore, QtGui, QtWidgets类 Ui_Form(对象):def setupUi(self, Form):Form.setObjectName("表单")Form.resize(716, 635)self.gridLayout_2 = QtWidgets.QGridLayout(Form)self.gridLayout_2.setObjectName("gridLayout_2")self.label = QtWidgets.QLabel(Form)字体 = QtGui.QFont()font.setPointSize(16)self.label.setFont(字体)self.label.setObjectName("标签")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.translateForm.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"))如果 __name__ == "__main__":导入系统app = QtWidgets.QApplication(sys.argv)表单 = QtWidgets.QWidget()ui = Ui_Form()ui.setupUi(表单)表单.show()sys.exit(app.exec_())

ma​​inWindow.py

from PyQt5 import QtCore, QtGui, QtWidgets导入系统从 files.main_interfaces.window 导入 Ui_MainWindow类 MainWindow(QtWidgets.QMainWindow,Ui_MainWindow):def __init__(self, parent=None):super(MainWindow, self).__init__(parent)self.setupUi(self)如果 __name__ == "__main__":导入系统app = QtWidgets.QApplication(sys.argv)w = 主窗口()w.show()sys.exit(app.exec_())

ma​​inStudent.py

from PyQt5 import QtCore, QtGui, QtWidgets导入系统从 files.main_interfaces.student 导入 Ui_Form类 stdMainWindow(QtWidgets.QWidget,Ui_Form):def __init__(self, parent=None):super(stdMainWindow, self).__init__(parent)self.setupUi(self)self.pushButton.clicked.connect(self.function1)定义函数1(自我):打印(函数调用")如果 __name__ == "__main__":导入系统app = QtWidgets.QApplication(sys.argv)w = stdMainWindow()w.show()sys.exit(app.exec_())

app.py

from PyQt5 import QtCore, QtGui, QtWidgets从主窗口导入主窗口从 mainStudent 导入 stdMainWindow类学生页面(stdMainWindow):def __init__(self, *args, **kwargs):super().__init__(*args, **kwargs)self.setupUi(self)类 MainWindow3(MainWindow):def __init__(self):super().__init__()self.setupUi(self)# 添加标签self.studentPage = studentPage()self.tabWidget.addTab(self.studentPage, 'Student')如果 __name__ == '__main__':导入系统app = QtWidgets.QApplication(sys.argv)窗口 = MainWindow3()window.show()sys.exit(app.exec_())

解决方案

tl;dr

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

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

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

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

<小时>

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

使用pyuic生成的代码

单继承方式

你从 pyuic 得到的是一个非常基本的 Python object 类:就其本身而言,它没有也没有什么:事实上,没有 __init__ 方法,它主要是一个便利"类,用于将对象分组"在一个公共实例对象中.

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

这是从 pyuic 生成的输出:

class Ui_Form(object):def setupUi(self, Form):Form.setObjectName("表单")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.translateForm.setWindowTitle(_translate("Form", "Form"))self.pushButton.setText(_translate("Form", "PushButton"))

让我们忽略 retranslateUiconnectSlotsByName 部分,因为它们对于我们这里的需求并不那么重要.

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

from PyQt5 import QtWidgets从 ui_test 导入 Ui_Form类 MyWidget(QtWidgets.QWidget):def __init__(self):super().__init__()self.ui = Ui_Form()self.ui.setupUi(self)如果 __name__ == '__main__':导入系统app = QtWidgets.QApplication(sys.argv)mywidget = MyWidget()mywidget.show()sys.exit(app.exec_())

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

  • 文件是主脚本,所以进入if语句
  • 它创建了一个 QApplication 实例(这是创建基于 GUI 的对象所必需的)
  • 它创建了一个 MyWidget实例,这意味着:
    • MyWidget 输入它的 __init__
    • 它创建了一个Ui_Form实例,从ui_test.py文件中导入
    • 它使用 mywidget(MyWidget instance)作为参数调用 setupUi

现在,让我们看看 setupUi 内部发生了什么:

class Ui_Form(object):def setupUi(self, Form):# Form"实际上是mywidget"(实例),所以它会设置# 该实例对象的对象名称和大小Form.setObjectName("表单")Form.resize(320, 240)# 上面和下面做的完全一样,**inside**# MyWidget 的 __init__:## self.setObjectName("Form")# self.resize(320, 240)# 创建一个带有mywidget"参数的布局,它会自动# 设置该实例的布局;请注意,新对象已创建# 作为self"的一个属性,在本例中是self.ui"#mywidget"self.verticalLayout = QtWidgets.QVBoxLayout(Form)self.verticalLayout.setObjectName("verticalLayout")# 创建一个以mywidget"为父级的按钮;这是# 如果您将小部件添加到布局中,通常不需要,因为# 它将自动取得它的所有权;与布局一样,# 为self.ui"创建pushButton"属性self.pushButton = QtWidgets.QPushButton(Form)self.pushButton.setObjectName("pushButton")# 将按钮添加到布局中self.verticalLayout.addWidget(self.pushButton)

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


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

  • mywidget 实例已经创建
  • 它现在正在显示"(但是,在这一点上,它还实际上不可见!)
  • sys.exit 使用 app.exec_() 作为参数调用

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

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

最后,只要应用程序以某种方式退出,它就会返回它的 返回代码sys.exit,它最终会退出你的python程序.

多重继承方法

在使用多重继承时,事情没有太大变化:区别在于 MyWidgetQWidget 继承 both/strong> Ui_Form,因此,当调用 setupUi 时,self"将是 mywidget 实例,并且不会有任何 self.ui 在玩:只是 self.

from PyQt5 import QtWidgets从 ui_test 导入 Ui_Form类 MyWidget(QtWidgets.QWidget, Ui_Form):def __init__(self):super().__init__()self.setupUi(self)# ...

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

 def setupUi(self, Form):# "Form" 实际上是 "mywidget",因为 "self" 是Form.setObjectName("表单")Form.resize(320, 240)self.verticalLayout = QtWidgets.QVBoxLayout(Form)# ...

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

<预><代码># 注意这里除了 "self" 没有参数def setupUi(self):self.setObjectName("Form")self.resize(320, 240)self.verticalLayout = QtWidgets.QVBoxLayout(self)# ...

这种方法可以帮助我们更好地理解 setupUi 究竟做了什么,因为调用该函数完全,如下所示(注意,除了 没有其他继承>QtWidgets.QWidget):

<预><代码>类 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从 ui_test 导入 Ui_Form类 MyWidget(QtWidgets.QWidget, Ui_Form):def __init__(self):super().__init__()print('什么是saveFile"?', self.saveFile)self.setupUi(self)print('什么是saveFile"?', self.saveFile)def saveFile(self):经过

将发生以下情况:

什么是saveFile"?<在0xb21cc1dc处<__main__.MyWidget对象的绑定方法MyWidget.saveFile>>什么是保存文件"?<PyQt5.QtWidgets.QPushButton 对象在 0xb21cc26c>

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

 # 在这里我们使用实例作为参数调用 saveFile 方法MyWidget.saveFile(self)

但这不是很方便,对吧?

<小时>

使用 uic.loadUi

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

这个方法的行为几乎就像多重继承一样,所以你会得到你的 self.verticalLayoutself.pushButton 对象在和上面一样.

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

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

不幸的是,这还有一个小缺点:有时会忽略布局的默认边距并将其设置为 0,无论您在 ui 文件中设置什么.不过,有一种解决方法,您可以在 Qt Designer 和 PyQt 程序中的垂直布局大小不同上阅读更多内容.

<小时>

最后,一些进一步的建议:

  • 通常的惯例是对类使用大写名称,而对变量和函数使用小写名称;
  • 关于上述观点,也尽量遵循Python代码风格指南(又名 PEP 8),尤其是在与他人共享您的代码时;
  • 所有那些 if __name__ == '__main__' 语句都是不必要的,因为您可能不会单独运行这些文件;仅在必要时使用这种语句(在您的情况下,仅用于 app.py);
  • 如果您已经在开始时导入它,则不需要在那些 if 语句中调用 import sys
  • 不要过度使用粗体,因为这会使您的帖子分散注意力并且更难以阅读;阅读更多关于使用降价的信息,如何格式化您的代码,最后,如何提出好问题(包括其相关链接);

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

解决方案

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);

这篇关于Pyqt5:尝试添加 QLayout &quot;“Form",已经有了布局(多继承python)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆