如何从 .ui 文件向 Qt 按钮添加功能?

Posted

技术标签:

【中文标题】如何从 .ui 文件向 Qt 按钮添加功能?【英文标题】:How to add functions to Qt buttons from .ui file? 【发布时间】:2020-11-02 09:40:03 【问题描述】:

我刚刚开始使用和发现 PyQt5 和 Qt。我安装了它 pip install pyqt5。然后我打开pyqt-tools自带的designer.exe,做了我的设计。

之后,我使用 pyuic design.ui > dialog.py 将其从 .ui 转换为 .py

当我运行app.py 时,它运行良好并且用户界面运行良好。但现在我的问题是:如何添加按下按钮时调用的函数?

这是我的 app.py 代码:

import sys
from PyQt5.QtWidgets import QDialog, QApplication
from dialog import Ui_Form

class AppWindow(QDialog)
        def __init__(self):
           super().__init__()
           self.ui = Ui_Form()
           self.ui.setupUi(self)
           self.show()  
                                              
app = QApplication(sys.argv)
w = AppWindow()
w.show()
sys.exit(app.exec_())

【问题讨论】:

澄清一下:您所显示的app.py 显然不是 pyuic 命令的输出。那么,也许用 pyuic 创建的文件实际上是dialog.py,对吧? @musicamante 是的,你是对的,我显示了我输入的文件而不是生成的文件,因为我虽然这并不重要 重要的是,使用错误的名称或令人困惑的引用会使您的问题不清楚,并且需要像我们遇到的那样不必要的问题和确认,这可以避免:-) 【参考方案1】:

您的方法实际上是正确的(至少是正确的方法之一):除非您真的(REALLY)知道,否则应该永远修改pyuic 生成的文件你在做什么以及为什么。此外,绝不应模仿他们的行为,因为没有理由这样做。

作为(我会说是强制性的)参考,您可以在有关 using Designer 的官方 PyQt 指南中阅读有关此主题的更多信息。

让我对这个话题做一个重要的前提。

您正在做的事情被称为单一继承方法,这意味着您正在创建一个仅从您正在使用的 QWidget 子类(在本例中为 QDialog)继承的子类,并且您正在使用所谓的 form 类 在其之上构建 ui。表单类是一个标准的 Python object 类型,它负责在调用其 setupUi 函数时在主子类实例之上创建所有子小部件。 结果是子类实例将始终在其范围内引用self,而其所有子实例都可通过self.ui 对象获得。

一种类似(并且更常见)的方法是多重继承方法。通过这种方式,您可以从 both 继承 QWidget 子类 (QDialog) 和 ui 对象。 UI 结果将是相同的,但使用 self.objectName 而不是 self.ui.objectName 可以更直接地使用小部件。

是否使用其中一个是一个选择问题,请记住,每当您使用多重继承方法时,setupUi 可能会覆盖任何先前设置的实例属性,并且如果您为已经存在的创建新属性对象名称,该对象将不再(直接)可访问。

最后,让我给你另一个建议:虽然没有完全错,但另一个答案中的 link posted 不仅给出了一个非常误导性的建议,而且甚至没有深入挖掘为什么它所指的以前的帖子是错误的。最重要的是,pyuic 创建的文件永远不应该被修改(也不应该尝试模仿他们的行为),原因有很多:不仅在您需要更改的情况下您的 ui 您需要将已经修改的代码与新的代码合并(希望您没有使用 pyuic 覆盖它),但这也会导致对整个对象结构的误解。


因此,在您的情况下(使用单一继承方法),在 Designer 中创建的所有对象实际上都可以使用 self.ui.objectName 访问,而 objectName 是 Designer 的对象检查器中显示的内容。

假设您创建了一个带有单个按钮的 UI,并且您希望在按下该按钮时打印一些内容。如果你没有对你的 ui 做太多的测试和修改,那个按钮的 object name 可能是“pushButton”。

然后,您的代码可能会如下所示:

import sys
from PyQt5.QtWidgets import QDialog, QApplication
from dialog import Ui_Form

class AppWindow(QDialog)
    def __init__(self):
        super().__init__()
        self.ui = Ui_Form()
        self.ui.setupUi(self)

        self.ui.pushButton.clicked.connect(self.buttonClicked)

    def buttonClicked(self):
        print('button clicked!')

                                              
app = QApplication(sys.argv)
w = AppWindow()
w.show()
sys.exit(app.exec_())

请注意,Qt 对象名称不是唯一的。理论上,您可以为多个 QObject(或 QWidget)设置相同的对象名称,但这样做没有任何用处。从 python 的角度考虑:每个变量都应该有自己的名字,如果你覆盖了一个已经存在的变量名,前一个变量将变得不可访问(如果甚至没有被垃圾回收)。 设计器足够聪明(不是它需要这么多......)以防止创建共享相同对象名称的对象,这就是为什么如果您添加另一个按钮,它将被命名为pushButton_2pushButton_3 等。PyQt 需要无论您使用的是pyuic 文件还是uic.loadUi(),都可以通过为所有对象名称创建唯一的实例属性来从中受益。

为了完整起见,以下是 多重继承 方法的外观,同时表现如上例:


import sys
from PyQt5.QtWidgets import QDialog, QApplication
from dialog import Ui_Form

class AppWindow(QDialog, Ui_Form)
    def __init__(self):
        super().__init__()
        self.setupUi(self)

        self.pushButton.clicked.connect(self.buttonClicked)

    def buttonClicked(self):
        print('button clicked!')

                                              
app = QApplication(sys.argv)
w = AppWindow()
w.show()
sys.exit(app.exec_())

根据上面的解释,考虑属性名问题:如果你在调用self.setupUi之前创建了一个名为self.pushButton的属性,那么之后调用self.pushButton将是对按钮的引用和之前设置的值会迷路;如果您在调用self.setupUi 之后创建一个名为self.pushButton 的新属性,您将无法再访问QPushButton 实例。


现在,虽然对于正常使用,它们的行为方式通常相同,但使用pyuic 生成的文件或uic.loadUiType() 与更直接的uic.loadUi 之间存在细微但仍然重要的差异,不幸的是这些差异没有正式记录(坦率地说,我不记得所有这些)。

eyllanesc 之前在问题Size of verticalLayout is different in Qt Designer and PyQt program 中报告了一个示例。

考虑到这些是非常具体的例外情况(甚至可能在 PyQt 的未来版本中得到解决,尤其是现在 Qt6 即将推出)并且它们通常不会对标准编程造成重大问题.

最后(终于!),虽然设计师通常很聪明,但并不完美。例如,作为 QAction 分隔符的“技术上不重要”的对象被认为是“匿名的”,并且设计者甚至不应用它们的对象名称:如果您将分隔符添加到菜单,则无法直接访问它,即使您主动命名它。

【讨论】:

【参考方案2】:

我建议您不要在转换后的 pythonscript 中编程。只是因为如果您想更改布局并再次转换它,您会覆盖那里编程的所有内容。

将此转换后的 ui 导入新的 pythonscript。有很多这样的介绍(谷歌他们) https://nitratine.net/blog/post/how-to-import-a-pyqt5-ui-file-in-a-python-gui/

然后你应该阅读槽和信号(你也可以在 qt 设计器中添加它们)或在 pythonscript 中添加函数: https://www.tutorialspoint.com/pyqt/pyqt_signals_and_slots.htm

这是按照教程了解它们的最简单方法...

【讨论】:

是的,谢谢,我查看了您提供的链接并按照指南进行操作,谢谢。 @user123 实际上,OP 在代码示例中显示的是 not 从 pyuic 转换的文件,并且在使用 uic.loadUi 时非常好(我通常更喜欢方法,实际上),它没有正确回答问题,即如何使用 pyuic 生成的文件实现功能。 @musicamante 我实际上使用导入的 .ui 和生成的 pyuic 进行了测试,它们都可以工作,我缺少的主要部分是 self.findChild(QtWidgets.QPushButton, 'printButton').clicked.connect(self.function),它适用于您导入 ui 的任何方法,只需输入我刚才在self.show() 之前提到的那条线,它在这两种情况下都适用。 两种方法都得到几乎相同的结果,但我将在另一个答案中解决一些细微(但有时很重要)的差异。除此之外,该指南给出了一个非常bad 的建议,即使用findChild 来获取对象。这不仅具有误导性,而且完全没有必要,因为 pyuic 构建文件和 uic 模块都已经提供了对在 Designer 中创建的对象的直接访问。对于您的最后一条评论,您的“printButton”已经可以通过self.printButton(或self.ui.printButton,如果您使用单继承)访问。 @musicamante 我将如何实现单继承?既然您说它具有误导性,那么 findChild 的替代方法是什么?

以上是关于如何从 .ui 文件向 Qt 按钮添加功能?的主要内容,如果未能解决你的问题,请参考以下文章

QT如何获得对话框中控件的指针?

qt creator 中的"提升为..."功能简介

Qt自定义主窗口的放大,缩小,关闭功能

QT做一个按钮Qpushbutton 倒计时功能的小小程序

如何使用 Qt 在同一个 UI 中添加菜单栏和按钮?

如何从另一个 QT 应用程序访问 Qt UI 的 QObject?