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

Python 使用QMessageBox时无法设置QThread的父错误,python,multithreading,pyqt5,Python,Multithreading,Pyqt5,我试图在一个线程上运行进度条,在另一个线程上运行函数。以下是我的方法,在我添加QMessageBox之前,它运行良好。 我为QThread创建了两个新类,一个处理进度条,另一个处理我的函数。当使用onButtonClicked功能按下按钮时,将调用它们 from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5.QtCore import QThread, pyqtSignal from PyQt5.QtWidgets import QApplic

我试图在一个线程上运行进度条,在另一个线程上运行函数。以下是我的方法,在我添加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_())
从PyQt5导入QtCore、QtGui、qtwidget
从PyQt5.QtCore导入QThread,pyqtSignal
从PyQt5.qtwidts导入QApplication、QWidget、QPushButton、QMessageBox、QLineEdit、QProgressBar、QLabel、QFileDialog、QCheckBox、QMenuBar、QStatusBar
导入时间
时限=100
类Ui_主窗口(对象):
def设置UI(自我,主窗口):
MainWindow.setObjectName(“MainWindow”)
主窗口。调整大小(800600)
self.centralwidget=qtwidts.QWidget(主窗口)
self.centralwidget.setObjectName(“centralwidget”)
self.msg=QMessageBox()
self.label=qtwidts.QLabel(self.centralwidget)
self.label.setGeometry(QtCore.QRect(300,60,47,13))
self.label.setObjectName(“标签”)
self.lineEdit=qtwidts.QLineEdit(self.centralwidget)
self.lineEdit.setGeometry(QtCore.QRect(270、100、113、20))
self.lineEdit.setObjectName(“lineEdit”)
self.pushButton=qtwidts.QPushButton(self.centralwidget)
自身按钮设置几何(QtCore.QRect(290、150、75、23))
self.butdown.setObjectName(“butdown”)
self.progressBar=qtwidts.QProgressBar(self.centralwidget)
self.progressBar.setGeometry(QtCore.QRect(280、210、118、23))
self.progressBar.setProperty(“值”,24)
self.progressBar.setObjectName(“progressBar”)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar=qtwidts.QMenuBar(主窗口)
self.menubar.setGeometry(QtCore.QRect(0,0800,21))
self.menubar.setObjectName(“menubar”)
MainWindow.setMenuBar(self.menubar)
self.statusbar=qtwidts.QStatusBar(主窗口)
self.statusbar.setObjectName(“statusbar”)
main window.setStatusBar(self.statusbar)
self.Actionlistenr()
自重传(主窗口)
QtCore.QMetaObject.connectSlotsByName(主窗口)
def重新传输(自身,主窗口):
_translate=QtCore.QCoreApplication.translate
setWindowTitle(_translate(“MainWindow”、“MainWindow”))
self.label.setText(_translate(“主窗口”、“文本标签”))
self.putton.setText(_translate(“主窗口”、“按钮”))
def Actionlistenr(自身):
self.button.clicked.connect(self.onbutton单击)
def测试(自我):
如果self.lineEdit.text()==“”:
self.msg.setIcon(QMessageBox.Critical)
self.msg.setText(“请先选择一个文档!”)
self.msg.setWindowTitle(“错误”)
返回self.msg.exec()
#如果这只是一个常规的打印声明,
#然后它会起作用,或者任何其他声明
#不涉及QMessageBox
def ON按钮单击(自身):
self.calc=外部()
self.calc.countChanged.connect(self.onCountChanged)
自计算开始()
self.calc2=External2(self)
self.calc2.start()
def onCountChanged(自身,值):
self.progressBar.setValue(值)
类外部(QThread):
"""
运行计数器线程。
"""
countChanged=pyqtSignal(int)
def运行(自):
计数=0
计数<时间限制:
计数+=1
时间。睡眠(1)
self.countChanged.emit(计数)
类External2(QThread,object):
"""
运行计数器线程。
"""
def uuu init uuuu(自身、外部实例):
super()。\uuuu init\uuuuu()
self.outer\u实例=outer\u实例
def运行(自):
self.outer_instance.test()
如果名称=“\uuuuu main\uuuuuuuu”:
导入系统
app=qtwidts.QApplication(sys.argv)
MainWindow=QtWidgets.QMainWindow()
ui=ui\u主窗口()
ui.setupUi(主窗口)
MainWindow.show()
sys.exit(app.exec_())

我得到
QObject::setParent:无法设置父级,执行此操作时新父级处于不同的线程中
错误,仅当我在
测试
函数中添加QMessageBox时。我假设发生这种情况是因为QMessagebox在主线程上运行,而不是我的
External2()
类,我如何解决这个问题?

首先要做的是验证是否满足启动重载的要求(在这种情况下,QLineEdit不是空的)。在这些情况下,我更喜欢使用辅助线程方法。若要启动繁重的任务,必须异步调用该方法,例如使用QTimer.singleShot(),若要使用functools.partial()传递附加参数,还必须使用@pyqtSlot以确保任务在正确的线程中执行

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

从PyQt5导入QtCore、QtGui、qtwidget
从functools导入部分
导入时间
时限=100
类Ui_主窗口(对象):
def设置UI(自我,主窗口):
MainWindow.setObjectName(“MainWindow”)
主窗口。调整大小(800600)
self.centralwidget=qtwidts.QWidget(主窗口)
self.centralwidget.setObjectName(“centralwidget”)
self.label=qtwidts.QLabel(self.centralwidget)
self.label.setGeometry(QtCore.QRect(300,60,47,13))
self.label.setObjectName(“标签”)
self.lineEdit=qtwidts.QLineEdit(self.centralwidget)
self.lineEdit.setGeometry(QtCore.QRect(270、100、113、20))
self.lineEdit.setObjectName(“lineEdit”)
self.pushButton=qtwidts.QPushButton(self.centralwidget)
自身按钮设置几何(QtCore.QRect(290、150、75、23))
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_())