使用 QMessageBox 时无法使用 QThread 设置父错误

Posted

技术标签:

【中文标题】使用 QMessageBox 时无法使用 QThread 设置父错误【英文标题】:Cannot set parent error with QThread when using QMessageBox 【发布时间】:2019-08-12 17:19:02 【问题描述】:

我试图在一个线程上运行一个进度条,在另一个线程上运行一个函数。以下是我的方法,它工作正常,直到我添加一个 QMessageBox。 我为 QThread 创建了两个新类,一个处理进度条,另一个处理我的函数。当使用 onButtonClicked 函数按下按钮时调用它们

from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QMessageBox, QLineEdit, QProgressBar, QLabel, QFileDialog, QCheckBox, QMenuBar, QStatusBar

import time

TIME_LIMIT = 100
class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(800, 600)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.msg = QMessageBox()
        self.label = QtWidgets.QLabel(self.centralwidget)
        self.label.setGeometry(QtCore.QRect(300, 60, 47, 13))
        self.label.setObjectName("label")
        self.lineEdit = QtWidgets.QLineEdit(self.centralwidget)
        self.lineEdit.setGeometry(QtCore.QRect(270, 100, 113, 20))
        self.lineEdit.setObjectName("lineEdit")
        self.pushButton = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton.setGeometry(QtCore.QRect(290, 150, 75, 23))
        self.pushButton.setObjectName("pushButton")
        self.progressBar = QtWidgets.QProgressBar(self.centralwidget)
        self.progressBar.setGeometry(QtCore.QRect(280, 210, 118, 23))
        self.progressBar.setProperty("value", 24)
        self.progressBar.setObjectName("progressBar")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 21))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)
        self.Actionlistenr()

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

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.label.setText(_translate("MainWindow", "TextLabel"))
        self.pushButton.setText(_translate("MainWindow", "PushButton"))

    def Actionlistenr(self):
        self.pushButton.clicked.connect(self.onButtonClick)


    def test(self):
        if self.lineEdit.text() == "":
            self.msg.setIcon(QMessageBox.Critical)
            self.msg.setText("Please select a document first!")
            self.msg.setWindowTitle("Error")
            return self.msg.exec()
            # If this was just a regular print statement,
            # then it would work, or any other statement that
            # does not involve a QMessageBox

    def onButtonClick(self):
        self.calc = External()        
        self.calc.countChanged.connect(self.onCountChanged)
        self.calc.start()
        self.calc2 = External2(self)
        self.calc2.start()


    def onCountChanged(self, value):
        self.progressBar.setValue(value)


class External(QThread):
    """
    Runs a counter thread.
    """
    countChanged = pyqtSignal(int)

    def run(self):
        count = 0
        while count < TIME_LIMIT:
            count +=1
            time.sleep(1)
            self.countChanged.emit(count)

class External2(QThread, object):    
    """
    Runs a counter thread.
    """
    def __init__(self, outer_instance):
        super().__init__()
        self.outer_instance = outer_instance       


    def run(self):
       self.outer_instance.test()


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_())

执行此操作时出现QObject::setParent: Cannot set parent, new parent is in a different thread 错误,仅当我在test 函数中添加QMessageBox 时。我假设这是因为 QMessagebox 在主线程上运行,而不是我的 External2() 类,我该如何解决这个问题?

【问题讨论】:

您不能也不应该在另一个线程中运行 QMessageBox,因此 Qt 指出了该错误。为什么要在另一个线程中使用 QMessageBox?我想你有一个XY problem。你的主要目标是什么? 我的主要目标是在各自的线程上运行进度条和 test 方法,以避免 GUI 冻结。但是,我的 test 方法有一些 QMessageBox 错误处理,这导致了我现在面临的问题 在我的问题中更具体一点:为什么要在测试方法中使用 QMessageBox? 因为我试图要求用户输入有关 csv 文件的一些信息,例如工作表名称和列名称,如果用户单击“运行”按钮并且任何字段为空,我想显示一个 QMessageBox 来显示他们遇到的错误。 而且我想这些信息(名称和列名)你想用来在同一个线程中执行一个任务。我说的对吗? 【参考方案1】:

您必须做的第一件事是验证是否满足要求(在这种情况下 QLineEdit 不为空)以启动繁重的任务。在这些情况下,我更喜欢使用工作线程方法。要启动繁重的任务,必须异步调用该方法,例如使用 QTimer.singleShot(),并使用 functools.partial() 传递附加参数,还必须使用 @pyqtSlot 确保任务在线程对。

另一方面,您不应该修改 Qt Designer 生成的类(1),而是创建另一个继承自小部件的类并使用第一个类来填充它。

from PyQt5 import QtCore, QtGui, QtWidgets
from functools import partial
import time

TIME_LIMIT = 100


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(800, 600)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.label = QtWidgets.QLabel(self.centralwidget)
        self.label.setGeometry(QtCore.QRect(300, 60, 47, 13))
        self.label.setObjectName("label")
        self.lineEdit = QtWidgets.QLineEdit(self.centralwidget)
        self.lineEdit.setGeometry(QtCore.QRect(270, 100, 113, 20))
        self.lineEdit.setObjectName("lineEdit")
        self.pushButton = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton.setGeometry(QtCore.QRect(290, 150, 75, 23))
        self.pushButton.setObjectName("pushButton")
        self.progressBar = QtWidgets.QProgressBar(self.centralwidget)
        self.progressBar.setGeometry(QtCore.QRect(280, 210, 118, 23))
        self.progressBar.setProperty("value", 24)
        self.progressBar.setObjectName("progressBar")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 21))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        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", "MainWindow"))
        self.label.setText(_translate("MainWindow", "TextLabel"))
        self.pushButton.setText(_translate("MainWindow", "PushButton"))


class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.setupUi(self)
        self.msg = QtWidgets.QMessageBox()

        self.Actionlistenr()

        thread = QtCore.QThread(self)
        thread.start()
        self.m_worker = Worker()
        self.m_worker.countChanged.connect(self.progressBar.setValue)
        self.m_worker.moveToThread(thread)

    def Actionlistenr(self):
        self.pushButton.clicked.connect(self.onButtonClick)

    def request_information(self):
        filename = self.lineEdit.text()
        if filename:
            wrapper = partial(self.m_worker.task, filename)
            QtCore.QTimer.singleShot(0, wrapper)
        else:
            self.msg.setIcon(QtWidgets.QMessageBox.Critical)
            self.msg.setText("Please select a document first!")
            self.msg.setWindowTitle("Error")
            self.msg.exec_()

    @QtCore.pyqtSlot()
    def onButtonClick(self):
        self.request_information()


class Worker(QtCore.QObject):
    countChanged = QtCore.pyqtSignal(int)

    @QtCore.pyqtSlot(str)
    def task(self, filename):
        # execute heavy task here
        print("start")
        print(filename)
        count = 0
        while count < TIME_LIMIT:
            count += 1
            time.sleep(1)
            self.countChanged.emit(count)
        print("finished")


if __name__ == "__main__":
    import sys

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

(1)http://pyqt.sourceforge.net/Docs/PyQt5/designer.html

【讨论】:

请问如何在您的解决方案中实现进度条? @Abdane 只需将您的代码添加到我的解决方案中,我已经更新了我的答案。 执行我的函数时,该工具仍然冻结。您可以通过在request_information 函数中运行一个永久循环来测试它。目标是在后台运行该函数并在执行时更新进度条 @Abdane request_information 是 GUI 的一部分,您不能在那里执行繁重的任务(永远循环),而是在 Worker 任务方法中执行。检查我的更新并详细分析我的代码 只是一个额外的小问题,如何在单击按钮时停止 Worker 的执行?谢谢:)【参考方案2】:

好吧,这个 per-sae 比你要求的要多一点,但它也少一点——但是它确实包括所有的点点滴滴以及它们如何相互连接,所以你应该能够从中推断并得到它做任何你想做的事。请注意,您的程序存在一些问题,因此我无法逐字复制它-与您的特定问题有关的问题之一是您无法运行从线程内继承自 QWidgets 的任何内容-我通过创建多进程解决了这个问题处理第二个窗口的情况。仍然按照我概述线程的方式,您可以看到您不需要在线程中拥有该 QMessageBox,但您可能会导致该线程中的某些东西在 QMainWindow 中启动 QMessageBox - 正如我所指出的 - 如果您需要线程来启动该 QMessageBox ——尽管您可能需要添加一些内容以显示线程中正在进行的功能,但这是可行的——我知道这一点,因为我已经测试了这方面的内容。

from sys  import exit  as sysExit
from time import sleep as tmSleep

from PyQt5.QtCore import Qt, QObject, QThread, QRunnable, pyqtSignal, pyqtSlot
# from PyQt5.QtGui import ??
#Widget Container Objects
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QDockWidget 
from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QMenuBar, QStatusBar, QLabel
#Widget Action Objects
from PyQt5.QtWidgets import QMessageBox, QFileDialog, QPushButton, QLineEdit 
from PyQt5.QtWidgets import QProgressBar, QCheckBox, QAction, QStyleFactory

# Part of Threading
# Note be very careful with Signals/Slots as they are prone to Memory Leaks
class ThreadSignals(QObject):
    ObjctSignal = pyqtSignal(object)
    IntgrSignal = pyqtSignal(int)

# Part of Threading -- if its a Class that does pretty much the same thing then should only have one
class Processor(QWidget):
    def __init__(self, Id):
        QWidget.__init__(self)
        self.ThreadActive = True
        self.RunProcess   = False
        self.Id = Id
        self.Name = '---- Threaded Process ' + str(self.Id)
        self.Msg = self.Name

    def Connect(self, sigHandle, sigFlag):
        self.QueData = queQue()
        cnt = 0
        self.Flag = sigFlag
        sigHandle.emit(self)
        tmSleep(0.005)   # 5 Milliseconds
      # This simulates a continuously running process
      # The waits are necessary to allow the OS to do stuff because
      # python IS NOT multiprocessing due to the GIL -- look it up
        self.lstData = []
        while self.ThreadActive:
            while self.RunProcess:
                cnt += 1
                if cnt % 10 == 0:
                    self.lstData.append(cnt)
                if cnt % 100 == 0:
                    self.Msg = self.Name + ' : Loop ' + str(cnt)
                    self.QueData.put(self.Msg)
                    self.QueData.put(self.lstData.copy())
                    sigFlag.emit(cnt)
                    self.lstData = []
                tmSleep(0.005)   # 5 Milliseconds
            tmSleep(0.005)       # 5 Milliseconds

    def GetData(self):
        RetData = []
        if not self.QueData.empty():
            RetData = list(self.QueData.get())
        return RetData

    def StartProcess(self):
        self.RunProcess = True
        self.Msg = self.Name + ' Started'
        self.Flag.emit(-1)

    def StopProcess(self):
        self.RunProcess = False
        self.Msg = self.Name + ' Stopped'
        self.Flag.emit(-1)

    def DisConnect(self):
        self.RunProcess = False
        self.ThreadActive = False
        self.Msg = self.Name + ' Disconnected'

# Part of Threading -- if its a Class that does pretty much the same thing then should only have one
class WorkerProcess(QRunnable):
    def __init__(self, StartrFunc, Id):
        super(WorkerProcess, self).__init__()
        self.StartrFunc = StartrFunc
          # def StarterFunc(self):
          #     self.ProcessObject = Processor(#)
          #     self.ProcessObject.Connect(sigHandle, sigFlag)
        self.setAutoDelete(False)
        self.Id = Id
        self.name = '----- WorkerProcess ' + str(Id)

      # Create Signal (aka Sender) Here
        self.signals = ThreadSignals()
        self.sigHndl = self.signals.ObjctSignal
        self.sigFlag = self.signals.IntgrSignal

    @pyqtSlot()
    def run(self):
        print('Inside ',self.name)
        self.StartrFunc(self.sigHndl, self.sigFlag)
          # def StarterFunc(self):
          #     self.ProcessObject = Processor(#)
          #     self.ProcessObject.Connect(sigHandle, sigFlag)
        print('******************************')
        print('--- Process Completed')
      # Note while this process has completed this thread is still active

    def DisConnect(self):
      # This disconnects all of its Signals
        self.signals.disconnect()

# This is your Menu and Tool Bar class it does not handle the Tool Bar
# at this time but it could be expanded to do so fairly easily just 
# keep in mind everything on a Tool Bar comes from the Menu Bar 
class MenuToolBar(QDockWidget):
    def __init__(self, parent):
        QDockWidget.__init__(self)
        self.Parent = parent
        self.MainMenu = parent.menuBar()

      # This is used to have a handle to the Menu Items
      # should you implement a Tool Bar
        self.MenuActRef = 'HelloAct':0,
                           'ResetAct':0

        # ******* Create the World Menu *******
        self.WorldMenu  = self.MainMenu.addMenu('World')

        # ******* Create World Menu Items *******
        self.HelloAct = QAction('&Hello', self)
      # In case you have or want to include an Icon
      #  self.HelloAct = QAction(QIcon('Images/hello.ico'), '&Hello', self)
        self.HelloAct.setShortcut("Ctrl+H")
        self.HelloAct.setStatusTip('Say Hello to the World')
        self.HelloAct.triggered.connect(self.SayHello)
        self.MenuActRef['HelloAct'] = self.HelloAct

        self.ResetAct = QAction('&Reset', self)
      #  self.ResetAct = QAction(QIcon('Images/reset.ico'), '&Hello', self)
        self.ResetAct.setShortcut("Ctrl+H")
        self.ResetAct.setStatusTip('Reset the Dialog')
        self.ResetAct.triggered.connect(self.ResetWorld)
        self.MenuActRef['ResetAct'] = self.ResetAct

        # ******* Setup the World Menu *******
        self.WorldMenu.addAction(self.HelloAct)
        self.WorldMenu.addSeparator()
        self.WorldMenu.addAction(self.ResetAct)

        self.InitToolBar()

    def InitToolBar(self):
      # If you create a Tool Bar initialize it here
        pass

# These are the Menu/Tool Bar Actions
    def SayHello(self):
        self.Parent.MenuSubmit()

    def ResetWorld(self):
        self.Parent.MenuReset()

# Its easiest and cleaner if you Class the Center Pane
# of your MainWindow object
class CenterPanel(QWidget):
    def __init__(self, parent):
        QWidget.__init__(self)
        self.Parent = parent
        self.Started = False
        #-----
        self.lblTextBox = QLabel()
        self.lblTextBox.setText('Text Box Label')
        #-----
        self.lneTextBox = QLineEdit()
        #-----
        self.btnPush = QPushButton()
        self.btnPush.setText('Start')
        self.btnPush.clicked.connect(self.Starter)
        #-----
        self.btnTest = QPushButton()
        self.btnTest.setText('Test')
        self.btnTest.clicked.connect(self.TestIt)
        #-----
        HBox = QHBoxLayout()
        HBox.addWidget(self.btnPush)
        HBox.addWidget(self.btnTest)
        HBox.addStretch(1)
        #-----
        self.pbrThusFar = QProgressBar()
        self.pbrThusFar.setProperty('value', 24)
        #-----
        VBox = QVBoxLayout()
        VBox.addWidget(self.lblTextBox)
        VBox.addWidget(self.lneTextBox)
        VBox.addWidget(QLabel('   ')) # just a spacer
        VBox.addLayout(HBox)
        VBox.addWidget(QLabel('   ')) # just a spacer
        VBox.addWidget(self.pbrThusFar)
        VBox.addStretch(1)
        #-----
        self.setLayout(VBox)

    def Starter(self):
        if self.Started:
            self.btnPush.setText('Start')
            self.Started = False
            self.Parent.OnStart()
        else:
            self.btnPush.setText('Reset')
            self.Started = True
            self.pbrThusFar.setProperty('value', 24)
            self.Parent.OnReset()

    def TestIt(self):
      # Note this cannot be handled within a Thread but a Thread can be 
      # designed to make a call back to the MainWindow to do so. This 
      # can be managed by having the MainWindow pass a handle to itself
      # to the Thread in question or by using a Signal/Slot call from
      # within the Thread back to the MainWindow either works
        Continue = True
        if self.lneTextBox.text() == '':
            DocMsg = QMessageBox()
            DocMsg.setIcon(QMessageBox.Critical)
            DocMsg.setWindowTitle("Error")
            DocMsg.setText("There is no Document. Do you want to Quit?")
            DocMsg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
            DocMsg.setDefaultButton(QMessageBox.No)
            DocMsg.setWindowFlags(Qt.WindowStaysOnTopHint)
            MsgReply = DocMsg.exec_()

            if MsgReply == QMessageBox.Yes:
                sysExit()

    def HandleSubmit(self):
        self.lneTextBox.setText('Center Panel Menu Submit')

# This is sort of your Main Handler for your interactive stuff as such
# it is best to restrict it to doing just that and let other classes 
# handle the other stuff -- it also helps maintain overall perspective
# of what each piece is designed for
class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()

        self.setWindowTitle('Main Window')
      # Sometimes its best to place the window where you want but just setting its size works too
      # Still I do this in two lines to make it clear what each position is
        WinLeft = 150; WinTop = 150; WinWidth = 400; WinHight = 200
        # self.setGeometry(WinLeft, WinTop, WinWidth, WinHight)
        self.resize(WinWidth, WinHight)

        self.CenterPane = CenterPanel(self)
        self.setCentralWidget(self.CenterPane)

      # The Menu and Tool Bar for your MainWindow should be classed as well
        self.MenuBar = MenuToolBar(self)

        self.SetStatusBar(self)
      # Not exactly sure what all this does yet but it does remove 
      # oddities from the window so I always include it - for now
        self.setStyle(QStyleFactory.create('Cleanlooks'))

  # Part of Threading
        self.Thread1Connected = False
        self.Thread2Connected = False

      # Create Handles for the Threads
      # I used this methodology as it was best for my program but 
      # there are other ways to do this it depends on your needs
        self.Thread1Hndl = QObject()
        self.Thread2Hndl = QObject()

      # This is used to start the Thread 1
        self.MyThread1 = WorkerProcess(self.Threader1, 1)
      # Create Slots (aka Receivers) Here
        self.MyThread1.signals.ObjctSignal.connect(self.Thread1_Hndl)
        self.MyThread1.signals.IntgrSignal.connect(self.Thread1_Flag)

      # This is used to start the Thread 2
        self.MyThread2 = WorkerProcess(self.Threader2, 2)
      # Create Slots (aka Receivers) Here
        self.MyThread2.signals.ObjctSignal.connect(self.Thread2_Hndl)
        self.MyThread2.signals.IntgrSignal.connect(self.Thread2_Flag)

        def MenuSubmit(self):
            self.CenterPane.HandleSubmit()

        def MenuReset(self):
            self.CenterPane.lineEdit.setText('Main Window Menu Reset')

    def SetStatusBar(self, parent):
        StatusMsg = ''
        parent.StatBar = parent.statusBar()

        if len(StatusMsg) < 1:
          # This verbiage will disappear when you view menu items
            StatusMsg = 'Ready'

        parent.StatBar.showMessage(StatusMsg)

    def OnStart(self):
        if self.Thread1Connected:
            self.Thread1Hndl.StartProcess()
        if self.Thread2Connected:
            self.Thread2Hndl.StartProcess()

    def OnReset(self):
        pass

  # Part of Threading
    def Thread1_Hndl(self, sigHandle):
        self.Thread1Hndl = sigHandle
        print('******************************')
        print('--- Thread 1 Handle Sent Back Validation')
        print(self.Thread1Hndl.Msg)
        self.Thread1Connected = True

    def Thread1_Flag(self, sigFlag):
        print('******************************')
        print('--- Thread 1 Loop Id Sent Back Validation')
        print('----- Current Loop : ', sigFlag)
        print(self.Thread1Hndl.Msg)
        self.DoStuffT1()
        if sigFlag > 1000:
            self.Thread1Connected = False
            self.Thread1Hndl.DisConnect()
            print(self.Thread1Hndl.Msg)

    def Thread2_Hndl(self, Handle):
        self.Thread2Hndl = Handle
        print('******************************')
        print('--- Thread 2 Handle Sent Back Validation')
        print(self.Thread2Hndl.Msg)
        self.Thread2Connected = True

    def Thread2_Flag(self, sigFlag):
        print('******************************')
        print('--- Thread 2 Loop Id Sent Back Validation')
        print('----- Current Loop : ', sigFlag)
        print(self.Thread2Hndl.Msg)
        self.DoStuffT2()
        if sigFlag > 1000:
            self.Thread2Connected = False
            self.Thread2Hndl.DisConnect()
            print(self.Thread2Hndl.Msg)

    def DoStuffT1(self):
      # Just a place holder function for demonstration purposes

      # Perhaps handle this here for one of the Threads
      #  self.CenterPane.pbrThusFar.setValue(value)
        pass

    def DoStuffT2(self):
      # Just a place holder function for demonstration purposes
        pass

  # Part of Threading
  # These Functions are being passed into completely Separate Threads
  # do not try to print from within as stdout is not available
  # Also keep in mind you cannot use anything within a Thread that 
  # inherits from QWidgets and a few from QGui as well
  # Create the entire object within the Thread allowing for complete
  # autonomy of its entire functionality from the Main GUI
    def Threader1(self, sigHandle, sigFlag):
        self.Thrdr1Obj = Processor(1)  # Create Threader 1 Object from Class
        self.Thrdr1Obj.Connect(sigHandle, sigFlag)

    def Threader2(self, sigHandle, sigFlag):
        self.Thrdr2Obj = Processor(2)  # Create Threader 2 Object from Class
        self.Thrdr2Obj.Connect(sigHandle, sigFlag)

if __name__ == "__main__":
  # It is best to keep this function to its bare minimum as its 
  # main purpose is to handle pre-processing stuff
  # 
  # Next you did not appear to be using sys.argv but if you od need 
  # to use command line arguments I strongly suggest you look into 
  # argparse its a python library and very helpful for this as such 
  # also much cleaner than dealing with them via regular means
    MainThred = QApplication([])

    MainGUI = MainWindow()
    MainGUI.show()

    sysExit(MainThred.exec_())

最后,如果您对此有任何疑问,请提出,但我确实尝试在代码中包含说明。我还做了一些额外的跨对象调用,这样你就可以看到它是如何完成的——这不是一个生产版本,而是一个概念证明,它展示了其中的许多概念

【讨论】:

以上是关于使用 QMessageBox 时无法使用 QThread 设置父错误的主要内容,如果未能解决你的问题,请参考以下文章

应用程序在后台运行时如何弹出QMessageBox?

QMessageBox::information 自定义按钮

QMessageBox::information 自定义按钮

QMessageBox 使用快捷键打开多次

在 QMessageBox 中设置 QPushButton 的背景

Qt编程 ——消息对话框(QMessageBox)的使用