我试图在一个线程上运行进度条,并在另一个线程上运行函数。以下是我的方法,并且可以正常工作,直到添加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
函数中添加QMessageBox时,我才会收到test
错误。我以为这是因为QMessagebox是在主线程(而不是我的External2()
类)上运行,我该如何解决?
答案 0 :(得分:2)
您要做的第一件事就是验证是否满足重型启动的要求(在这种情况下,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 :(得分:1)
好吧,这个单方面的功能比您要求的要多,但它的功能也要少得多-但是它确实包含了所有的点点滴滴以及它们如何互连,因此您应该可以从中推断并得到它做你想做的事。请注意,您的程序有几个问题,所以我不能原样复制它-与您的特定问题有关的问题之一是您无法在线程内运行从QWidgets继承的任何东西-我通过创建一个多进程解决了这个问题处理第二窗口的情况。仍然按照我概述线程的方式,您可以看到您不需要在线程中包含该QMessageBox,但是您可以使该线程中的某些内容在QMainWindow中启动QMessageBox-如我所指出的-您需要使用Thread来启动该QMessageBox -尽管您可能需要添加一些内容以显示Threads中正在进行的功能,但是它可以正常工作-我知道这一点,因为我已经对此进行了测试。
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_())
最后,如果您对此有任何疑问,请务必提出,但我确实尝试在代码中包含解释。此外,我还做了一些额外的跨对象调用,所以您可以看到它是如何完成的-这不是生产版本,而是更多的概念证明,可以证明其中的众多概念