当以前不知道对话框将在什么时候打开时,在 PyQt 的第二个线程中打开子对话框的正确方法是什么?
What is the proper way of opening a child dialog in a second thread in PyQt when it is previously onknown at what point the dialog will be opened?
我有一个应用程序,其中 运行 在第二个线程中进行一些处理。在此过程中的某些时候,如果满足特定条件,则会打开另一个对话框 window,该对话框会暂停该过程,直到您确认某些内容。这会导致以下错误消息:
QObject: Cannot create children for a parent that is in a different thread.
(Parent is QApplication(0x1f9c82383d0), parent's thread is QThread(0x1f9c7ade2a0), current thread is QThread(0x1f9c8358800)
有趣的是,如果您在进程 运行ning 时也将光标移到 MainWindow
上,并且在弹出新对话框之前,它还会多次产生此错误消息:
QBasicTimer::stop: Failed. Possibly trying to stop from a different thread
很奇怪。因为如果您将光标移动到 MainWindow
.
上,它 只会发生
现在,在我的应用程序中,我实际上为使用 PyQt5.uic.loadUi
弹出的新对话框加载了一个界面,这没有造成任何问题。但是,当我为此 post 创建示例时,出现了另一个问题,因为我在初始化期间设置了新对话框的布局:
QObject::setParent: Cannot set parent, new parent is in a different thread
导致应用程序崩溃:
Process finished with exit code -1073741819 (0xC0000005)
关于我猜的线程,我显然在这里做错了什么,但我不知道是什么。我特别困惑的是我无法在初始化期间设置新对话框的布局,而使用 loadUi
完全没问题。这是我的示例代码:
import sys
import time
import numpy as np
from PyQt5.QtCore import QObject, pyqtSignal, QThread
from PyQt5.QtWidgets import (
QDialog, QApplication, QPushButton, QGridLayout, QProgressBar, QLabel
)
class SpecialDialog(QDialog):
def __init__(self):
super().__init__()
btn = QPushButton('pass variable')
btn.clicked.connect(self.accept)
layout = QGridLayout()
layout.addWidget(btn)
# self.setLayout(layout)
self.variable = np.random.randint(0, 100)
class Handler(QObject):
progress = pyqtSignal(int)
finished = pyqtSignal(int)
def __init__(self):
super().__init__()
self._isRunning = True
self._success = False
def run(self):
result = None
i = 0
while i < 100 and self._isRunning:
if i == np.random.randint(0, 100):
dialog = SpecialDialog()
dialog.exec_()
result = dialog.variable
time.sleep(0.01)
i += 1
self.progress.emit(i)
if i == 100:
self._success = True
self.finished.emit(result)
def stop(self):
self._isRunning = False
class MainWindow(QDialog):
def __init__(self):
super().__init__()
btn = QPushButton('test')
btn.clicked.connect(self.run_test)
self.pbar = QProgressBar()
self.resultLabel = QLabel('Result:')
layout = QGridLayout(self)
layout.addWidget(btn)
layout.addWidget(self.pbar)
layout.addWidget(self.resultLabel)
self.setLayout(layout)
self.handler = None
self.handler_thread = QThread()
self.result = None
def run_test(self):
self.handler = Handler()
self.handler.moveToThread(self.handler_thread)
self.handler.progress.connect(self.progress)
self.handler.finished.connect(self.finisher)
self.handler_thread.started.connect(self.handler.run)
self.handler_thread.start()
def progress(self, val):
self.pbar.setValue(val)
def finisher(self, result):
self.result = result
self.resultLabel.setText(f'Result: {result}')
self.pbar.setValue(0)
self.handler.stop()
self.handler.progress.disconnect(self.progress)
self.handler.finished.disconnect(self.finisher)
self.handler_thread.started.disconnect(self.handler.run)
self.handler_thread.terminate()
self.handler = None
if __name__ == '__main__':
app = QApplication(sys.argv)
GUI = MainWindow()
GUI.show()
sys.exit(app.exec_())
编辑
我忘了说我已经找到了this post,这可能与我的问题有关,但是,我并不理解顶部答案中解决方案的推理,更重要的是,我不'说出我认为是 C++ 的东西。
如果我们分析相等之前的情况,除非初始进度不同,否则相等发生之后的情况是相同的,这表明当相等发生时,必须向 GUI 发送信号以请求信息,当您获得该信息时,启动相同的任务,但初始进度与信号发出前的进度相同。
from functools import partial
import sys
import time
import numpy as np
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, QThread, QTimer
from PyQt5.QtWidgets import (
QDialog,
QApplication,
QPushButton,
QGridLayout,
QProgressBar,
QLabel,
)
class SpecialDialog(QDialog):
def __init__(self):
super().__init__()
btn = QPushButton("pass variable")
btn.clicked.connect(self.accept)
layout = QGridLayout()
layout.addWidget(btn)
# self.setLayout(layout)
self.variable = np.random.randint(0, 100)
class Handler(QObject):
progress = pyqtSignal(int)
finished = pyqtSignal()
resultChanged = pyqtSignal(int)
requestSignal = pyqtSignal()
def __init__(self):
super().__init__()
self._isRunning = True
self._success = False
self.iter = 0
self.result = None
@pyqtSlot()
def start(self):
self._isRunning = True
self._success = False
self.iter = 0
self.result = None
self.task()
@pyqtSlot()
@pyqtSlot(int)
def task(self, value=None):
if value is not None:
self.result = value
while self.iter < 100 and self._isRunning:
if self.iter == np.random.randint(0, 100):
self.requestSignal.emit()
return
time.sleep(0.01)
self.iter += 1
self.progress.emit(self.iter)
if self.iter == 100:
self._success = True
if self.result:
self.resultChanged.emit(self.result)
self.finished.emit()
def stop(self):
self._isRunning = False
class MainWindow(QDialog):
def __init__(self):
super().__init__()
btn = QPushButton("test")
btn.clicked.connect(self.run_test)
self.pbar = QProgressBar()
self.resultLabel = QLabel("Result:")
layout = QGridLayout(self)
layout.addWidget(btn)
layout.addWidget(self.pbar)
layout.addWidget(self.resultLabel)
self.setLayout(layout)
self.handler = None
self.handler_thread = QThread()
self.result = None
def run_test(self):
self.handler = Handler()
self.handler.moveToThread(self.handler_thread)
self.handler.progress.connect(self.progress)
self.handler.resultChanged.connect(self.on_result_changed)
self.handler.finished.connect(self.on_finished)
self.handler.requestSignal.connect(self.onRequestSignal)
self.handler_thread.started.connect(self.handler.start)
self.handler_thread.start()
def progress(self, val):
self.pbar.setValue(val)
def onRequestSignal(self):
dialog = SpecialDialog()
dialog.exec_()
wrapper = partial(self.handler.task, dialog.variable)
QTimer.singleShot(0, wrapper)
@pyqtSlot(int)
def on_result_changed(self, result):
self.result = result
self.resultLabel.setText(f"Result: {result}")
@pyqtSlot()
def on_finished(self):
self.pbar.setValue(0)
self.handler.stop()
self.handler_thread.quit()
self.handler_thread.wait()
if __name__ == "__main__":
app = QApplication(sys.argv)
GUI = MainWindow()
GUI.show()
sys.exit(app.exec_())
我有一个应用程序,其中 运行 在第二个线程中进行一些处理。在此过程中的某些时候,如果满足特定条件,则会打开另一个对话框 window,该对话框会暂停该过程,直到您确认某些内容。这会导致以下错误消息:
QObject: Cannot create children for a parent that is in a different thread.
(Parent is QApplication(0x1f9c82383d0), parent's thread is QThread(0x1f9c7ade2a0), current thread is QThread(0x1f9c8358800)
有趣的是,如果您在进程 运行ning 时也将光标移到 MainWindow
上,并且在弹出新对话框之前,它还会多次产生此错误消息:
QBasicTimer::stop: Failed. Possibly trying to stop from a different thread
很奇怪。因为如果您将光标移动到 MainWindow
.
现在,在我的应用程序中,我实际上为使用 PyQt5.uic.loadUi
弹出的新对话框加载了一个界面,这没有造成任何问题。但是,当我为此 post 创建示例时,出现了另一个问题,因为我在初始化期间设置了新对话框的布局:
QObject::setParent: Cannot set parent, new parent is in a different thread
导致应用程序崩溃:
Process finished with exit code -1073741819 (0xC0000005)
关于我猜的线程,我显然在这里做错了什么,但我不知道是什么。我特别困惑的是我无法在初始化期间设置新对话框的布局,而使用 loadUi
完全没问题。这是我的示例代码:
import sys
import time
import numpy as np
from PyQt5.QtCore import QObject, pyqtSignal, QThread
from PyQt5.QtWidgets import (
QDialog, QApplication, QPushButton, QGridLayout, QProgressBar, QLabel
)
class SpecialDialog(QDialog):
def __init__(self):
super().__init__()
btn = QPushButton('pass variable')
btn.clicked.connect(self.accept)
layout = QGridLayout()
layout.addWidget(btn)
# self.setLayout(layout)
self.variable = np.random.randint(0, 100)
class Handler(QObject):
progress = pyqtSignal(int)
finished = pyqtSignal(int)
def __init__(self):
super().__init__()
self._isRunning = True
self._success = False
def run(self):
result = None
i = 0
while i < 100 and self._isRunning:
if i == np.random.randint(0, 100):
dialog = SpecialDialog()
dialog.exec_()
result = dialog.variable
time.sleep(0.01)
i += 1
self.progress.emit(i)
if i == 100:
self._success = True
self.finished.emit(result)
def stop(self):
self._isRunning = False
class MainWindow(QDialog):
def __init__(self):
super().__init__()
btn = QPushButton('test')
btn.clicked.connect(self.run_test)
self.pbar = QProgressBar()
self.resultLabel = QLabel('Result:')
layout = QGridLayout(self)
layout.addWidget(btn)
layout.addWidget(self.pbar)
layout.addWidget(self.resultLabel)
self.setLayout(layout)
self.handler = None
self.handler_thread = QThread()
self.result = None
def run_test(self):
self.handler = Handler()
self.handler.moveToThread(self.handler_thread)
self.handler.progress.connect(self.progress)
self.handler.finished.connect(self.finisher)
self.handler_thread.started.connect(self.handler.run)
self.handler_thread.start()
def progress(self, val):
self.pbar.setValue(val)
def finisher(self, result):
self.result = result
self.resultLabel.setText(f'Result: {result}')
self.pbar.setValue(0)
self.handler.stop()
self.handler.progress.disconnect(self.progress)
self.handler.finished.disconnect(self.finisher)
self.handler_thread.started.disconnect(self.handler.run)
self.handler_thread.terminate()
self.handler = None
if __name__ == '__main__':
app = QApplication(sys.argv)
GUI = MainWindow()
GUI.show()
sys.exit(app.exec_())
编辑
我忘了说我已经找到了this post,这可能与我的问题有关,但是,我并不理解顶部答案中解决方案的推理,更重要的是,我不'说出我认为是 C++ 的东西。
如果我们分析相等之前的情况,除非初始进度不同,否则相等发生之后的情况是相同的,这表明当相等发生时,必须向 GUI 发送信号以请求信息,当您获得该信息时,启动相同的任务,但初始进度与信号发出前的进度相同。
from functools import partial
import sys
import time
import numpy as np
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, QThread, QTimer
from PyQt5.QtWidgets import (
QDialog,
QApplication,
QPushButton,
QGridLayout,
QProgressBar,
QLabel,
)
class SpecialDialog(QDialog):
def __init__(self):
super().__init__()
btn = QPushButton("pass variable")
btn.clicked.connect(self.accept)
layout = QGridLayout()
layout.addWidget(btn)
# self.setLayout(layout)
self.variable = np.random.randint(0, 100)
class Handler(QObject):
progress = pyqtSignal(int)
finished = pyqtSignal()
resultChanged = pyqtSignal(int)
requestSignal = pyqtSignal()
def __init__(self):
super().__init__()
self._isRunning = True
self._success = False
self.iter = 0
self.result = None
@pyqtSlot()
def start(self):
self._isRunning = True
self._success = False
self.iter = 0
self.result = None
self.task()
@pyqtSlot()
@pyqtSlot(int)
def task(self, value=None):
if value is not None:
self.result = value
while self.iter < 100 and self._isRunning:
if self.iter == np.random.randint(0, 100):
self.requestSignal.emit()
return
time.sleep(0.01)
self.iter += 1
self.progress.emit(self.iter)
if self.iter == 100:
self._success = True
if self.result:
self.resultChanged.emit(self.result)
self.finished.emit()
def stop(self):
self._isRunning = False
class MainWindow(QDialog):
def __init__(self):
super().__init__()
btn = QPushButton("test")
btn.clicked.connect(self.run_test)
self.pbar = QProgressBar()
self.resultLabel = QLabel("Result:")
layout = QGridLayout(self)
layout.addWidget(btn)
layout.addWidget(self.pbar)
layout.addWidget(self.resultLabel)
self.setLayout(layout)
self.handler = None
self.handler_thread = QThread()
self.result = None
def run_test(self):
self.handler = Handler()
self.handler.moveToThread(self.handler_thread)
self.handler.progress.connect(self.progress)
self.handler.resultChanged.connect(self.on_result_changed)
self.handler.finished.connect(self.on_finished)
self.handler.requestSignal.connect(self.onRequestSignal)
self.handler_thread.started.connect(self.handler.start)
self.handler_thread.start()
def progress(self, val):
self.pbar.setValue(val)
def onRequestSignal(self):
dialog = SpecialDialog()
dialog.exec_()
wrapper = partial(self.handler.task, dialog.variable)
QTimer.singleShot(0, wrapper)
@pyqtSlot(int)
def on_result_changed(self, result):
self.result = result
self.resultLabel.setText(f"Result: {result}")
@pyqtSlot()
def on_finished(self):
self.pbar.setValue(0)
self.handler.stop()
self.handler_thread.quit()
self.handler_thread.wait()
if __name__ == "__main__":
app = QApplication(sys.argv)
GUI = MainWindow()
GUI.show()
sys.exit(app.exec_())