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