如何正确编写 PyQT5 事件函数

Posted

技术标签:

【中文标题】如何正确编写 PyQT5 事件函数【英文标题】:How to write correctly PyQT5 event function 【发布时间】:2020-09-17 16:51:10 【问题描述】:

如何正确编写事件函数,通过按回车键从一个 QLineEdit 传递到另一个 QLineEdit?

我知道该怎么做:

工作示例

import sys
from PyQt5.QtWidgets import QApplication, QWidget, QLineEdit, QFormLayout
from PyQt5.QtCore import Qt, QEvent

class working(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.show()

        self.x = QLineEdit(self)
        self.x1 = QLineEdit(self)

        layout = QFormLayout(self)
        layout.addRow("x:", self.x)
        layout.addRow("x1:", self.x1)

    def event(self, event):
        if event.type() == QEvent.KeyPress:
            if event.key() in (Qt.Key_Return, Qt.Key_Enter):
                self.focusNextPrevChild(True)
        return super().event(event)

def main():
    app = QApplication(sys.argv)
    ex = working()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

现在我想了解如何对这段代码做同样的事情(我认为问题出在 super() 和 init 但我不知道为什么)。

UNWorking 基本示例

from PyQt5 import QtCore, QtWidgets, QtGui
 
class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(193, 119)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.centralwidget)
        self.verticalLayout_2.setObjectName("verticalLayout_2")
 
        self.x = QtWidgets.QLineEdit()
        self.verticalLayout_2.addWidget(self.x)
        self.x1 = QtWidgets.QLineEdit()
        self.verticalLayout_2.addWidget(self.x1)
 
        MainWindow.setCentralWidget(self.centralwidget)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)
        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)
 
    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "0"))
 
    def event(self, event):
        if event.type() == QEvent.KeyPress:
            if event.key() in (Qt.Key_Return, Qt.Key_Enter):
                self.focusNextPrevChild(True)
        return super().event(event)
 
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_())

【问题讨论】:

你为什么要用“那个”代码来做这个?是不是因为您想使用 Designer 构建应用程序而不是手动创建整个界面? 主要用于学习,在第二个逻辑中,我可以在不关闭整个应用程序的情况下执行 MainWindow.Close()。我是初学者,我不明白如何正确使用 init super() 和 parent 【参考方案1】:

第二个示例不起作用,因为您试图覆盖 event 方法,该方法仅适用于 QWidget 子类,而您的 Ui_MainWindow 是一个简单的 python object 子类,所以它永远不会被调用。

event() 方法由 Qt 在任何 QWidget 上调用,并且由于您在第一个示例中覆盖了它,因此实际上调用了您的方法。在第二个示例中,您没有覆盖 QWidget event,而只是创建了一个永远不会被任何东西调用的函数。

理论上你可以覆盖setupUiMainWindow对象的event方法(这就是所谓的“猴子补丁”):

    def setupUi(self, MainWindow):
        # ...
        MainWindow.event = self.event

但这不起作用,因为您在该函数中所指的 self 实际上是 Ui_MainWindow 实例,而不是实际的 QMainWindow 实例。虽然您可以使用 lambda,并在 MainWindow 实例中添加参数,但我强烈建议您不要这样做,以下解释将阐明您为什么不应该这样做。

出于一个简单的原因,做你想要达到的目标是没有意义的:修改或尝试模仿 `pyuic` 生成的文件的行为是**永远**应该做的事情。

不鼓励编辑主要是因为当您需要从 Designer 更改 UI 中的某些内容时,您最终会尝试将新生成的代码(希望您同时没有覆盖文件)与现有代码集成,这通常会导致大量错误、问题、崩溃和令人头疼的问题。

不鼓励编辑或模仿,因为它很难实现简单的类操作或覆盖现有方法。此外,虽然我可以理解您有兴趣研究它是如何工作的,但除了阅读如何在 setupUi 中创建界面之外,没有什么要学习的了。 pyuic 文件仅用作“实用层”以简化 PyQt 中的编程,它们应该始终仅按照有关 using Designer 的官方指南中的建议导入和使用。

如果您想实现第一个示例的事件,您需要使用自己的子类并使用上面链接中建议的方法之一。最常见且易于使用的是多重继承方法:

import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QLineEdit, QFormLayout
from PyQt5.QtCore import Qt, QEvent
from ui_mainwindow import Ui_MainWindow

class Working(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.setupUi(self)

    def event(self, event):
        if event.type() == QEvent.KeyPress:
            if event.key() in (Qt.Key_Return, Qt.Key_Enter):
                self.focusNextPrevChild(True)
        return super().event(event)

if __name__ == "__main__":
    import sys
 
    app = QApplication(sys.argv)
    mainWindow = Working()
    mainWindow.show()
    sys.exit(app.exec_())

注意:我假设您再次使用pyuic 生成了文件,该文件名为ui_mainwindow.py;另外,请注意,如果您在设计器中使用 QMainWindow,则必须从同一类(QMainWindow,而不是 QWidget)子类化;最后,由于您已经从 QMainWindow Ui_MainWindow 继承,您必须创建Working 的实例,而不是 QMainWindow 之一,也不是 Ui_MainWindow 之一。

另一种可能是使用uic模块,它允许直接导入.ui文件,而不需要每次都重新构建整个界面。

import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QLineEdit, QFormLayout
from PyQt5.QtCore import Qt, QEvent
from PyQt5.uic import loadUi

class Working(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super().__init__()
        loadUi('mainWindow.ui', self)
        # ...

【讨论】:

我在 pastebin 中放了一个新的例子 @yalodek(请用英文写) 什么不起作用?你检查过字母大小写吗?如果Ui_MainWindow 在同一个文件中,那么您不应该导入它(但您应该将该类放在脚本中,因为应该始终导入 pyuic 文件)。 查看我的编辑。你应该只创建一个Working 的实例,而不是 QMainWindow 和 Working。 现在它工作得很好,我没有看到现在我需要用 mainWindow 调用它。谢谢 不客气。请记住,如果我的回答按预期解决了您的问题,您应该通过单击左侧的复选标记将其标记为已接受。

以上是关于如何正确编写 PyQT5 事件函数的主要内容,如果未能解决你的问题,请参考以下文章

如何杀死 PyQt5 中的 QRunnable?

PYQT5(二十四)文本框回应回车事件

如何为 Node.js 编写异步函数

pyqt5注意事项

QCoreApplication::exec: 事件循环已经在运行 - 调用另一个 PyQt5 文件

如何正确设置在 PyQt5 上缩放的 Pixmap?