如何在循环中打开(和关闭)PyQt5应用程序,并使该循环多次运行

2024-05-01 07:27:24 发布

您现在位置:Python中文网/ 问答频道 /正文

以下是我创建的一个循环:

import mainui
import loginui
from PyQt5 import QtWidgets
import sys

while True:
    print('test')

    app = QtWidgets.QApplication(sys.argv)
    ui = loginui.Ui_MainWindow()
    ui.setupUi()
    ui.MainWindow.show()
    app.exec_()

    username=ui.username

    app2 = QtWidgets.QApplication(sys.argv)
    ui2 = mainui.Ui_MainWindow(username)
    ui2.setupUi()
    ui2.MainWindow.show()
    app2.exec_()

    if ui2.exitFlag=='repeat':#Repeat Condition  
        continue
    else:                     #Exit Condition
        sys.exit()

这是一个包含两个PyQt5窗口的循环,这些窗口按顺序显示。当窗口不包含在循环中时,它们可以正常运行,并且在循环的第一次迭代中也可以很好地运行

但是,当满足重复条件时,即使循环确实进行了迭代(再次打印“测试”),ui和ui2窗口也不会再次显示,随后程序点击退出条件并停止

如果您能就为什么不显示窗口以及如何显示窗口提出任何建议,我们将不胜感激


Tags: importappuisysusernamepyqt5argvqapplication
1条回答
网友
1楼 · 发布于 2024-05-01 07:27:24

一个重要的前提是:通常您只需要一个QApplication实例

提议的解决办法

在下面的示例中,我使用单个QApplication实例,并使用信号在窗口之间切换

由于您可能需要等待窗口以某种方式关闭,因此您可能更喜欢使用QDialog而不是QMainWindow,但如果出于某种原因您需要QMainWindow提供的功能(菜单、dockbar等),这是一种可能的解决方案:

class First(QtWidgets.QMainWindow):
    closed = QtCore.pyqtSignal()
    def __init__(self):
        super().__init__()
        central = QtWidgets.QWidget()
        self.setCentralWidget(central)
        layout = QtWidgets.QHBoxLayout(central)
        button = QtWidgets.QPushButton('Continue')
        layout.addWidget(button)
        button.clicked.connect(self.close)

    def closeEvent(self, event):
        self.closed.emit()


class Last(QtWidgets.QMainWindow):
    shouldRestart = QtCore.pyqtSignal()
    def __init__(self):
        super().__init__()
        central = QtWidgets.QWidget()
        self.setCentralWidget(central)
        layout = QtWidgets.QHBoxLayout(central)
        restartButton = QtWidgets.QPushButton('Restart')
        layout.addWidget(restartButton)
        closeButton = QtWidgets.QPushButton('Quit')
        layout.addWidget(closeButton)
        restartButton.clicked.connect(self.restart)
        closeButton.clicked.connect(self.close)

    def restart(self):
        self.exitFlag = True
        self.close()

    def showEvent(self, event):
        # ensure that the flag is always false as soon as the window is shown
        self.exitFlag = False

    def closeEvent(self, event):
        if self.exitFlag:
            self.shouldRestart.emit()


app = QtWidgets.QApplication(sys.argv)
first = First()
last = Last()
first.closed.connect(last.show)
last.shouldRestart.connect(first.show)
first.show()
sys.exit(app.exec_())

注意,您也可以通过在QWidget的布局上使用^{}将菜单栏添加到QWidget中

另一方面,QDialogs更适合于这些情况,因为它们提供了exec_()方法,该方法有自己的事件循环,并在对话框关闭之前阻止所有其他内容

class First(QtWidgets.QDialog):
    def __init__(self):
        super().__init__()
        layout = QtWidgets.QHBoxLayout(self)
        button = QtWidgets.QPushButton('Continue')
        layout.addWidget(button)
        button.clicked.connect(self.accept)

class Last(QtWidgets.QDialog):
    def __init__(self):
        super().__init__()
        layout = QtWidgets.QHBoxLayout(self)
        restartButton = QtWidgets.QPushButton('Restart')
        layout.addWidget(restartButton)
        closeButton = QtWidgets.QPushButton('Quit')
        layout.addWidget(closeButton)
        restartButton.clicked.connect(self.accept)
        closeButton.clicked.connect(self.reject)

def start():
    QtCore.QTimer.singleShot(0, first.exec_)

app = QtWidgets.QApplication(sys.argv)
app.setQuitOnLastWindowClosed(False)
first = First()
last = Last()
first.finished.connect(last.exec_)
last.accepted.connect(start)
last.rejected.connect(app.quit)
start()
sys.exit(app.exec_())

注意,在本例中,我必须使用QTimer启动第一个对话框。这是因为在正常情况下,信号在将控制权返回到发射器(对话框)之前等待其插槽完成。由于我们不断回忆同一个对话框,这导致了递归:

  • 首先是执行
  • 第一个开关关闭,发出finished信号,这导致以下情况:
    • 二是执行
    • 此时finished信号尚未返回
    • 第二个被接受,发出accepted信号,导致:
      • First尚未返回其exec_(),但我们正在尝试再次执行它
      • Qt崩溃显示错误StdErr: QDialog::exec: Recursive call detected

使用QTimer.singleShot可确保信号立即返回,避免exec_()的任何递归

好吧,但是为什么不起作用呢

如前所述,每个进程通常只应存在一个Q[*]应用程序实例。这实际上并不妨碍随后创建更多的实例:事实上,代码在循环的第一个周期中工作

这个问题与Python垃圾收集有关,以及PyQt和Qt如何处理对C++ QT对象的内存访问,最重要的是应用实例。p>

创建第二个QApplication时,将其分配给一个变量(app2)。在这一点上,第一个仍然存在,并将在进程完成后立即(通过Qt)删除。 相反,当循环重新启动时,您正在覆盖app,这通常会导致python尽快对上一个对象进行垃圾收集
这代表了一个问题,因为Python和Qt需要做“他们的工作”来正确删除现有的QApplication对象和Python引用

如果将以下行放在开头,您将看到第一次正确返回实例,而第二次返回None

    app = QtWidgets.QApplication(sys.argv)
    print('Instance: ', QtWidgets.QApplication.instance())

这里有一个关于StackOverflow的related question以及对其answer的重要评论:

In principle, I don't see any reason why multiple instances of QApplication cannot be created, so long as no more than one exists at the same time. In fact, it may often be a requirement in unit-testing that a new application instance is created for each test. The important thing is to ensure that each instance gets deleted properly, and, perhaps more importantly, that it gets deleted at the right time.

避免垃圾回收的一个解决方法是向应用程序添加持久引用:

apps = []
while True:
    print('test')

    app = QtWidgets.QApplication(sys.argv)
    apps.append(app)

    # ...

    app2 = QtWidgets.QApplication(sys.argv)
    apps.append(app2)

但是,正如前面所说的,如果你不真正需要一个新的QApplication实例,那么你应该而不是创建一个新的QApplication实例(这几乎是从未过的情况)

如问题注释中所述,您应该永远不要修改使用pyuic生成的文件(也不要尝试模仿它们的行为)。阅读有关using Designer的更多信息。

相关问题 更多 >