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

  • 本文关键字:循环 应用程序 PyQt5 运行 python pyqt5
  • 更新时间 :
  • 英文 :


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

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窗口的循环,这些窗口按顺序显示。当窗口不包含在循环中时,它们会正常运行,并且在循环的第一次迭代中也会运行得很好。

但是,当满足重复条件时,即使循环进行迭代(再次打印"test"(,ui和ui2窗口也不会再次显示,随后程序达到退出条件并停止。

任何关于为什么不显示窗口以及如何显示窗口的建议都将不胜感激。

一个重要前提:通常您只需要一个Q应用程序实例。

建议的解决方案

在以下示例中,我使用一个QApplication实例,并使用信号在窗口之间切换。

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

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的布局上使用setMenuBar(menuBar)将菜单栏添加到QWidget。

另一方面,QDialog更适合这些情况,因为它们提供了自己的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对象(最重要的是应用程序实例(的内存访问有关。

创建第二个QApplication时,将其分配给新的变量(app2(。在这一点上,第一个仍然存在,并且一旦用sys.exit完成该过程,它将最终被删除(通过Qt(。相反,当循环重新启动时,您将覆盖app,这通常会导致python尽快垃圾收集前一个对象
这代表了一个问题,因为Python和Qt需要做的是"他们的东西";以正确删除现有的QApplication对象和python引用。

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

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

这里有一个关于StackOverflow的相关问题,以及对其答案的一个重要评论:

原则上,我看不出有任何理由不能创建QApplication的多个实例,只要同时不存在多个实例。事实上,在单元测试中,为每个测试创建一个新的应用程序实例通常是的要求。重要的是确保每个实例都被正确删除,也许更重要的是,它在正确的时间被删除

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

apps = []
while True:
print('test')
app = QtWidgets.QApplication(sys.argv)
apps.append(app)
# ...
app2 = QtWidgets.QApplication(sys.argv)
apps.append(app2)

但是,如前所述,如果您不真的需要创建一个新的QApplication实例,则应该而不是(这几乎是从未的情况(。

正如在对该问题的评论中所指出的,您应该永远不要修改用pyuic生成的文件(也不要试图模仿它们的行为(。阅读有关使用Designer的更多信息

最新更新