简体   繁体   English

PyQt5 QDialog在后续线程中

[英]PyQt5 QDialog in subsequent threads

I have a PyQt5 program in Python 3.3 that will start a new thread every time a button is pushed. 我在Python 3.3中有一个PyQt5程序,该程序在每次按下按钮时都会启动一个新线程。 This thread will use pop-up dialog boxes. 该线程将使用弹出对话框。 It works the first time the button is pressed, however, the second time (after the first has been completed) will crash the program. 第一次按下该按钮时,它将起作用,但是,第二次(第一次完成后)将使程序崩溃。 I can call the dialog box as many times as I want from within the thread, but the second time the thread is run the program freezes. 我可以在线程中多次调用该对话框,但是第二次运行该线程时,程序将冻结。 This code will reproduce the problem. 此代码将重现该问题。

import sys
from threading import Thread
from PyQt5 import QtWidgets, QtCore


class Ui_Dialog(object):
    def setupUi(self, Dialog):
        Dialog.setObjectName("Dialog")
        self.pushButton = QtWidgets.QPushButton(Dialog)
        self.pushButton.setGeometry(QtCore.QRect(100, 100, 100, 50))
        self.pushButton.setObjectName("pushButton")

        self.retranslateUi(Dialog)
        QtCore.QMetaObject.connectSlotsByName(Dialog)

    def retranslateUi(self, Dialog):
        _translate = QtCore.QCoreApplication.translate
        Dialog.setWindowTitle(_translate("Dialog", "Test"))
        self.pushButton.setText(_translate("Dialog", "OK"))


class Ui_MainWindow(object):
    def setupUi(self, mainWindow):
        mainWindow.setObjectName("mainWindow")
        self.pushButton = QtWidgets.QPushButton(mainWindow)
        self.pushButton.setGeometry(QtCore.QRect(30, 20, 100, 60))
        self.pushButton.setObjectName("pushButton")

        self.retranslateUi(mainWindow)
        QtCore.QMetaObject.connectSlotsByName(mainWindow)

    def retranslateUi(self, Dialog):
        _translate = QtCore.QCoreApplication.translate
        Dialog.setWindowTitle(_translate("mainWindow", "Test"))
        self.pushButton.setText(_translate("mainWindow", "Push Me!"))


class TestDialog(QtWidgets.QDialog):
    def __init__(self, parent=None):
        super(TestDialog, self).__init__(parent)
        self.ui = Ui_Dialog()
        self.ui.setupUi(self)
        # This message simply needs to go away when the button is pushed
        self.ui.pushButton.clicked.connect(self.close)

    def show_message(self):
        super(TestDialog, self).exec_()


class Main(QtWidgets.QMainWindow):
    def __init__(self):
        super(Main, self).__init__()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        self.dialog = TestDialog()

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

    def start_thread(self):
        t = Thread(target=self.show_dialog)
        t.daemon = True
        t.start()

    def show_dialog(self):
        # Do lots of background stuff here
        self.dialog.show_message()
        # The dialog can be shown multiple times within the same thread
        self.dialog.show_message()


if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    window = Main()
    window.show()
    sys.exit(app.exec_())

Remove the dialog box message and it works. 删除对话框消息,它起作用。 So why can't I call the dialog box from the second thread? 那为什么不能从第二个线程调用对话框呢? I'm not trying to run two threads simultaneously, but one after the other. 我不是试图同时运行两个线程,而是一个接一个。

I figured it out, thanks to help from sebastian. 感谢塞巴斯蒂安的帮助,我找到了答案。 I created a signal object, connected it to the show_message function. 我创建了一个信号对象,将其连接到show_message函数。 I also added a signal to tell the thread when the dialog has been accepted. 我还添加了一个信号来告知线程何时接受对话框。 Here is the working code. 这是工作代码。

import sys
from threading import Thread
from PyQt5 import QtWidgets, QtCore


class Ui_Dialog(object):
    def setupUi(self, Dialog):
        Dialog.setObjectName("Dialog")
        self.pushButton = QtWidgets.QPushButton(Dialog)
        self.pushButton.setGeometry(QtCore.QRect(100, 100, 100, 50))
        self.pushButton.setObjectName("pushButton")

        self.retranslateUi(Dialog)
        QtCore.QMetaObject.connectSlotsByName(Dialog)

    def retranslateUi(self, Dialog):
        _translate = QtCore.QCoreApplication.translate
        Dialog.setWindowTitle(_translate("Dialog", "Test"))
        self.pushButton.setText(_translate("Dialog", "OK"))


class Ui_MainWindow(object):
    def setupUi(self, mainWindow):
        mainWindow.setObjectName("mainWindow")
        self.pushButton = QtWidgets.QPushButton(mainWindow)
        self.pushButton.setGeometry(QtCore.QRect(30, 20, 100, 60))
        self.pushButton.setObjectName("pushButton")

        self.retranslateUi(mainWindow)
        QtCore.QMetaObject.connectSlotsByName(mainWindow)

    def retranslateUi(self, Dialog):
        _translate = QtCore.QCoreApplication.translate
        Dialog.setWindowTitle(_translate("mainWindow", "Test"))
        self.pushButton.setText(_translate("mainWindow", "Push Me!"))


class TestDialog(QtWidgets.QDialog):
    signal = QtCore.pyqtSignal()

    def __init__(self, parent=None):
        super(TestDialog, self).__init__(parent)
        self.ui = Ui_Dialog()
        self.ui.setupUi(self)
        # This message simply needs to go away
        self.ui.pushButton.clicked.connect(self.close)

    def show_message(self):
        # Use this to display the pop-up so the text can be altered
        super(TestDialog, self).exec_()
        self.signal.emit()


class Main(QtWidgets.QMainWindow):
    signal = QtCore.pyqtSignal()

    def __init__(self):
        super(Main, self).__init__()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        self.dialog = TestDialog()
        self.dialog_done = False

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

    def complete_dialog(self):
        self.dialog_done = True

    def wait_for_dialog(self):
        while not self.dialog_done:
            pass
        self.dialog_done = False

    def start_thread(self):
        t = Thread(target=self.show_dialog)
        t.daemon = True
        t.start()

    def show_dialog(self):
        # Do lots of background stuff here
        self.signal.emit()
        # Wait for the dialog to get closed
        self.wait_for_dialog()


if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    window = Main()
    window.show()
    dialog = TestDialog()
    window.signal.connect(dialog.show_message)
    dialog.signal.connect(window.complete_dialog)
    sys.exit(app.exec_())

Thanks to the answer posted by @Brickmastr I was able to rework this for my own use in a slightly different scenario. 感谢@Brickmastr发布的答案,我可以在稍有不同的情况下重新制作此代码以供自己使用。 In case there are others looking to do what I was, hopefully this slightly varied method can help. 如果有其他人想做我曾经做过的事情,希望这种稍微不同的方法可以有所帮助。 If you're running a program where you want to show a 'running' dialog box and then update that box once the process has completed, this is one way to achieve that. 如果您要运行的程序要显示“正在运行”对话框,然后在过程完成后更新该对话框,则这是一种实现方法。 Additionally, this is how you can use the same popup message for multiple time-consuming functions. 此外,这是将相同的弹出消息用于多个耗时功能的方法。

import PyQt5

#This below import is the python file that gets created from using QtDesigner
#And running pyuic5 to create a .py file from your .ui file - hopefully
#whomever reads this is familiar with using QtDesigner
import dialogBox as fxRun

#This is the file that would contain your primary UI, also created using QtDesigner
import mainUI
import threading

class MAIN_UI(PyQt5.QtWidgets.QMainWindow, mainUI.Ui_interface):
    startSignal = PyQt5.QtCore.pyqtSignal()
    endSignal = PyQt5.QtCore.pyqtSignal()

    def __init__(self,parent=None):
        super(MAIN_UI, self).__init__(parent)
        self.setupUi(self)
        self.buttonStartFunction1.clicked.connect(self.startFunction1)
        self.buttonStartFunction2.clicked.connect(self.startFunction2)

    def startFunction1(self):
        self.startThread(self.exampleMethod1)

   def startFunction2(self):
        self.startThread(self.exampleMethod2)

    def startThread(self,functionName):
        t = threading.Thread(target=functionName)
        t.daemon = True
        t.start()

    def exampleMethod1(self):
        #This function will show the dialog box at the beginning of the process
        # and will update the text and button once the process is complete
        FULLPROGRAM.mainUI.startSignal.emit()
        #Do lots of things here that take a long time
        FULLPROGRAM.mainUI.endSignal.emit()

    def exampleMethod2(self):
        #This can be a different function, just showing that you can send
        #whatever function into the startThread() method and it will work 
        #the same way 
        FULLPROGRAM.mainUI.startSignal.emit()
        #Do lots of things here that take a long time
        FULLPROGRAM.mainUI.endSignal.emit()

class PROCESS_BOX(PyQt5.QtWidgets.QDialog, fxRun.Ui_dialogBox):
    def __init__(self,parent=None): 
        super(PROCESS_BOX,self).__init__(parent)
        self.setupUi(self)
        self.buttonProcessCompleted.clicked.connect(self.close)

    def show_dialogbox(self): 
        self.setWindowTitle("RUNNING")
        self.labelProcessStatus.setText("PROCESSING REQUEST... \n PLEASE WAIT...")
        self.buttonProcessCompleted.setEnabled(False)
        super(PROCESS_BOX,self).exec_()

    def processComplete(self): 
        self.setWindowTitle("FINISHED")
        self.labelProcessStatus.setText("PROCESS COMPLETE! \n CLICK OK")
        self.buttonProcessCompleted.setEnabled(True)    

class FULLPROGRAM: 
    def __init__(self):
        app = PyQt5.QtWidgets.QApplication(sys.argv)
        FULLPROGRAM.fxRun = PROCESS_BOX()
        FULLPROGRAM.mainUI = MAIN_UI()
        FULLPROGRAM.mainUI.startSignal.connect(FULLPROGRAM.fxRun.show_dialogbox)
        FULLPROGRAM.mainUI.endSignal.connect(FULLPROGRAM.fxRun.processComplete)
        FULLPROGRAM.mainUI.show()
        app.exec_()

def main():
    program = FULLPROGRAM()

if __name__ == '__main__':
    main()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM