PyQt5 QObject:无法为不同线程中的父对象创建子对象
PyQt5 QObject: Cannot create children for a parent that is in a different thread
我正在使用 PyQt5 在菜单系统托盘中工作。我对 PyQt5 很陌生,我想做的是在不阻止菜单的情况下触发一个动作(多线程)。在阅读了很多地方之后,我得出的结论是使用 Qthread
应该是可行的方法(但如果我能理解 class 是如何工作的话……)。但是,考虑到我的应用程序非常简单,使用 threading
也不会那么糟糕。因此,我使用 import threading
尝试了以下代码:
from PyQt5 import QtCore, QtGui, QtWidgets
import threading
class menubar(object):
def __init__(self):
signal.signal(signal.SIGINT, signal.SIG_DFL)
self.systray = True
self.stopped = False
def search_menu(self):
self.SearchAction = menu.addAction("Search")
self.SearchAction.triggered.connect(self.search_cast)
def _search_cast_(self):
args.select_cc = True
self.cc.initialize_cast()
self.cast_list()
def search_cast(self):
threading.Thread(target=self._search_cast_).start()
#some more methods here...
def main():
menubar()
app = QtWidgets.QApplication(sys.argv)
tray = QtWidgets.QSystemTrayIcon(icon)
menu = QtWidgets.QMenu()
start = menubar()
start.search_menu()
start.separator_menu()
start.populating_menu()
start.separator_menu()
start.stop_menu()
start.resetaudio_menu()
start.about_menu()
start.exit_menu()
tray.setContextMenu(menu)
tray.show()
app.exec_()
if __name__ == '__main__':
main()
当我启动菜单时,一切都如我所料。然后,当我单击菜单 Search
时,操作会触发 self.search_cast
方法,并且我的菜单会填充它找到的列表。我还可以看到我的应用程序在没有被阻止的情况下进行搜索,但是当它完成时我收到以下错误:
QObject: Cannot create children for a parent that is in a different thread.
(Parent is QMenu(0x7fcef497c160), parent's thread is QThread(0x7fcef2603d10), current thread is QThread(0x7fcef4a89360)
QObject: Cannot create children for a parent that is in a different thread.
(Parent is QMenu(0x7fcef497c160), parent's thread is QThread(0x7fcef2603d10), current thread is QThread(0x7fcef4a89360)
QObject: Cannot create children for a parent that is in a different thread.
在此之后,菜单仍然是 "functional",因为它是响应式的,但无法触发更多操作。此外,似乎没有创建更多线程。如果有人能向我解释为什么会这样,我会很高兴。我看不到光...
更新:
我现在创建了一个 worker.py
,其中包含:
from PyQt5.QtCore import QThread, QObject, pyqtSignal, pyqtSlot
#some other imports
class Worker(QObject):
finished = pyqtSignal()
@pyqtSlot()
def _search_cast_(self):
self.cc = casting()
self.cc.initialize_cast()
self.finished.emit()
然后我在 class menubar
中添加了以下内容:
class menubar(object):
def __init__(self):
self.cc = casting()
signal.signal(signal.SIGINT, signal.SIG_DFL)
self.cc.cast = None
self.systray = True
self.stopped = False
self.obj = worker.Worker() # no parent!
self.thread = QThread() # no parent!
self.obj.moveToThread(self.thread)
self.obj.finished.connect(self.thread.quit)
self.thread.started.connect(self.obj._search_cast_)
def search_menu(self):
self.SearchAction = menu.addAction("Search")
self.SearchAction.triggered.connect(self.search_cast)
def search_cast(self):
self.thread.start()
self.cast_list()
def cast_list(self):
if len(self.cc.availablecc) == 0:
# some actions here.
现在我收到以下错误:
AttributeError: 'casting' object has no attribute 'availablecc'
我确保 worker
实际上正在从我称为 cc
的外部 class 恢复 availablecc
。但是由于某种原因没有被 menubar
class 接收到。我正在基于此
我将继续回答自己。受到 的启发,我通过实施以下方法解决了这个问题:
1) 我创建了一个 worker.py
来执行阻塞菜单的方法 _search_cast_
。当此方法完成搜索时,它会发出两个信号:a) 一个通知他已恢复 list
,以及 b) 该方法已完成。
#worker.py
from PyQt5.QtCore import QThread, QObject, pyqtSignal, pyqtSlot
class Worker(QObject):
finished = pyqtSignal()
intReady = pyqtSignal(list)
def __init__(self):
QObject.__init__(self)
@pyqtSlot()
def _search_cast_(self):
self.cc = casting()
self.cc.initialize_cast()
availablecc = self.cc.availablecc
self.intReady.emit(availablecc)
self.finished.emit()
2) 在 main.py
中,我丢弃了以下内容,并尝试在代码中用注释进行解释:
#main.py
from PyQt5.QtCore import QThread, QObject, pyqtSignal, pyqtSlot
import worker # This is to import worker.py
class menubar(object):
def __init__(self):
signal.signal(signal.SIGINT, signal.SIG_DFL)
self.cc.cast = None
self.systray = True
self.stopped = False
self.obj = worker.Worker() # The worker is started with no parent!
self.thread = QThread() # We initialise the Qthread class with no parent!
self.obj.intReady.connect(self.onIntReady) # We receive the signal that the list is ready
self.obj.moveToThread(self.thread) # Moving the object to the thread
self.obj.finished.connect(self.thread.quit) # When the method is finished we receive the signal that it is finished
self.thread.started.connect(self.obj._search_cast_) # We need to connect the above with the desired method inside the work.py
self.app = QtWidgets.QApplication(sys.argv)
def search_menu(self):
self.SearchAction = self.menu.addAction("Search")
self.SearchAction.triggered.connect(self.search_cast)
def onIntReady(self, availablecc): # This method receives the list from the worker
print ('availablecc', availablecc) # This is for debugging reasons to verify that I receive the list with the correct content
self.availablecc = availablecc
def search_cast(self): #This method starts the thread when self.SearchAction is triggered
args.select_cc = True
self.thread.start()
这样,搜索list
菜单时不会被屏蔽,屏幕上不会显示错误,在activity monitor
中监视它们时threads
的数量保持正确。
我希望这对人们有所帮助。更准确的信息(我还在学习PyQt,我的措辞可能不是很好),我建议你查看我上面发布的link。
由于这是此错误的 google 最佳答案,并且我花了比预期更长的时间才能正确解决此问题,因此我将分享我针对 Python 3 和 PyQt 5 的非常简单的解决方案(如果你改变一些导入,我猜它也应该在 PyQt4 中工作)。
我遇到的情况是一个带有 right-click 菜单的系统托盘图标,当另一个线程请求它时应该是 re-built。您当然可以将此应用于您希望通过线程限制进行通信的其他问题。
import time
import sys
import threading
from PyQt5 import QtGui
from PyQt5 import QtWidgets
from PyQt5 import QtCore
class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
def __init__(self, icon=None, parent=None):
icon = QtGui.QIcon(QtWidgets.QApplication.style().standardPixmap(QtWidgets.QStyle.SP_MediaPlay))
QtWidgets.QSystemTrayIcon.__init__(self, icon, parent)
self.menu = QtWidgets.QMenu(parent)
self.setContextMenu(self.menu)
self.build_menu()
self.show()
# see http://pyqt.sourceforge.net/Docs/PyQt5/signals_slots.html for more information
self.signal = MySignal()
self.signal.sig_no_args.connect(self.build_menu)
self.signal.sig_with_str.connect(self.print_string)
def build_menu(self):
''' This function should be called in order to rebuild
the right-click menu for the systray icon'''
global list_dict_streams
self.menu.clear()
exitAction = self.menu.addAction("Exit")
exitAction.triggered.connect(self._exit)
for x in list_dict_streams :
self.menu.addAction(x)
def print_string(self, str):
print(str)
def _exit(self):
QtCore.QCoreApplication.exit()
class MySignal(QtCore.QObject):
''' Why a whole new class? See here:
'''
sig_no_args = QtCore.pyqtSignal()
sig_with_str = QtCore.pyqtSignal(str)
list_dict_streams = ["1"]
def work_thread(trayIcon):
''' Will add one menu item to the systray menu every 5 seconds
and will send a signal with a string '''
global list_dict_streams
while True:
trayIcon.signal.sig_no_args.emit()
trayIcon.signal.sig_with_str.emit("String emitted")
list_dict_streams.append(str(len(list_dict_streams)+1))
time.sleep(5)
def main():
app = QtWidgets.QApplication(sys.argv)
trayIcon = SystemTrayIcon()
t = threading.Thread(target=work_thread, args=(trayIcon,))
t.daemon = True # otherwise the 'Exit' from the systray menu will not work
t.start()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
基本上你必须创建一个新的 class MySignal(QtCore.QObject)
why. I created a class with two examples - one that sends no arguments along another one that you can pass a string. You can of course define other arguments。然后在你的目标线程中创建这个 class 的新实例,并将来自那个 class 的函数连接到你的目标中的函数(在我的例子中是系统托盘图标)。之后,您现在可以像我在 while-loop.
中那样调用 emit(...)
函数
现在 Qt 很高兴,因为与直接从不同线程调用 trayIcon.build_menu()
相比,您只需发出一个信号。
我正在使用 PyQt5 在菜单系统托盘中工作。我对 PyQt5 很陌生,我想做的是在不阻止菜单的情况下触发一个动作(多线程)。在阅读了很多地方之后,我得出的结论是使用 Qthread
应该是可行的方法(但如果我能理解 class 是如何工作的话……)。但是,考虑到我的应用程序非常简单,使用 threading
也不会那么糟糕。因此,我使用 import threading
尝试了以下代码:
from PyQt5 import QtCore, QtGui, QtWidgets
import threading
class menubar(object):
def __init__(self):
signal.signal(signal.SIGINT, signal.SIG_DFL)
self.systray = True
self.stopped = False
def search_menu(self):
self.SearchAction = menu.addAction("Search")
self.SearchAction.triggered.connect(self.search_cast)
def _search_cast_(self):
args.select_cc = True
self.cc.initialize_cast()
self.cast_list()
def search_cast(self):
threading.Thread(target=self._search_cast_).start()
#some more methods here...
def main():
menubar()
app = QtWidgets.QApplication(sys.argv)
tray = QtWidgets.QSystemTrayIcon(icon)
menu = QtWidgets.QMenu()
start = menubar()
start.search_menu()
start.separator_menu()
start.populating_menu()
start.separator_menu()
start.stop_menu()
start.resetaudio_menu()
start.about_menu()
start.exit_menu()
tray.setContextMenu(menu)
tray.show()
app.exec_()
if __name__ == '__main__':
main()
当我启动菜单时,一切都如我所料。然后,当我单击菜单 Search
时,操作会触发 self.search_cast
方法,并且我的菜单会填充它找到的列表。我还可以看到我的应用程序在没有被阻止的情况下进行搜索,但是当它完成时我收到以下错误:
QObject: Cannot create children for a parent that is in a different thread.
(Parent is QMenu(0x7fcef497c160), parent's thread is QThread(0x7fcef2603d10), current thread is QThread(0x7fcef4a89360)
QObject: Cannot create children for a parent that is in a different thread.
(Parent is QMenu(0x7fcef497c160), parent's thread is QThread(0x7fcef2603d10), current thread is QThread(0x7fcef4a89360)
QObject: Cannot create children for a parent that is in a different thread.
在此之后,菜单仍然是 "functional",因为它是响应式的,但无法触发更多操作。此外,似乎没有创建更多线程。如果有人能向我解释为什么会这样,我会很高兴。我看不到光...
更新:
我现在创建了一个 worker.py
,其中包含:
from PyQt5.QtCore import QThread, QObject, pyqtSignal, pyqtSlot
#some other imports
class Worker(QObject):
finished = pyqtSignal()
@pyqtSlot()
def _search_cast_(self):
self.cc = casting()
self.cc.initialize_cast()
self.finished.emit()
然后我在 class menubar
中添加了以下内容:
class menubar(object):
def __init__(self):
self.cc = casting()
signal.signal(signal.SIGINT, signal.SIG_DFL)
self.cc.cast = None
self.systray = True
self.stopped = False
self.obj = worker.Worker() # no parent!
self.thread = QThread() # no parent!
self.obj.moveToThread(self.thread)
self.obj.finished.connect(self.thread.quit)
self.thread.started.connect(self.obj._search_cast_)
def search_menu(self):
self.SearchAction = menu.addAction("Search")
self.SearchAction.triggered.connect(self.search_cast)
def search_cast(self):
self.thread.start()
self.cast_list()
def cast_list(self):
if len(self.cc.availablecc) == 0:
# some actions here.
现在我收到以下错误:
AttributeError: 'casting' object has no attribute 'availablecc'
我确保 worker
实际上正在从我称为 cc
的外部 class 恢复 availablecc
。但是由于某种原因没有被 menubar
class 接收到。我正在基于此
我将继续回答自己。受到 的启发,我通过实施以下方法解决了这个问题:
1) 我创建了一个 worker.py
来执行阻塞菜单的方法 _search_cast_
。当此方法完成搜索时,它会发出两个信号:a) 一个通知他已恢复 list
,以及 b) 该方法已完成。
#worker.py
from PyQt5.QtCore import QThread, QObject, pyqtSignal, pyqtSlot
class Worker(QObject):
finished = pyqtSignal()
intReady = pyqtSignal(list)
def __init__(self):
QObject.__init__(self)
@pyqtSlot()
def _search_cast_(self):
self.cc = casting()
self.cc.initialize_cast()
availablecc = self.cc.availablecc
self.intReady.emit(availablecc)
self.finished.emit()
2) 在 main.py
中,我丢弃了以下内容,并尝试在代码中用注释进行解释:
#main.py
from PyQt5.QtCore import QThread, QObject, pyqtSignal, pyqtSlot
import worker # This is to import worker.py
class menubar(object):
def __init__(self):
signal.signal(signal.SIGINT, signal.SIG_DFL)
self.cc.cast = None
self.systray = True
self.stopped = False
self.obj = worker.Worker() # The worker is started with no parent!
self.thread = QThread() # We initialise the Qthread class with no parent!
self.obj.intReady.connect(self.onIntReady) # We receive the signal that the list is ready
self.obj.moveToThread(self.thread) # Moving the object to the thread
self.obj.finished.connect(self.thread.quit) # When the method is finished we receive the signal that it is finished
self.thread.started.connect(self.obj._search_cast_) # We need to connect the above with the desired method inside the work.py
self.app = QtWidgets.QApplication(sys.argv)
def search_menu(self):
self.SearchAction = self.menu.addAction("Search")
self.SearchAction.triggered.connect(self.search_cast)
def onIntReady(self, availablecc): # This method receives the list from the worker
print ('availablecc', availablecc) # This is for debugging reasons to verify that I receive the list with the correct content
self.availablecc = availablecc
def search_cast(self): #This method starts the thread when self.SearchAction is triggered
args.select_cc = True
self.thread.start()
这样,搜索list
菜单时不会被屏蔽,屏幕上不会显示错误,在activity monitor
中监视它们时threads
的数量保持正确。
我希望这对人们有所帮助。更准确的信息(我还在学习PyQt,我的措辞可能不是很好),我建议你查看我上面发布的link。
由于这是此错误的 google 最佳答案,并且我花了比预期更长的时间才能正确解决此问题,因此我将分享我针对 Python 3 和 PyQt 5 的非常简单的解决方案(如果你改变一些导入,我猜它也应该在 PyQt4 中工作)。
我遇到的情况是一个带有 right-click 菜单的系统托盘图标,当另一个线程请求它时应该是 re-built。您当然可以将此应用于您希望通过线程限制进行通信的其他问题。
import time
import sys
import threading
from PyQt5 import QtGui
from PyQt5 import QtWidgets
from PyQt5 import QtCore
class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
def __init__(self, icon=None, parent=None):
icon = QtGui.QIcon(QtWidgets.QApplication.style().standardPixmap(QtWidgets.QStyle.SP_MediaPlay))
QtWidgets.QSystemTrayIcon.__init__(self, icon, parent)
self.menu = QtWidgets.QMenu(parent)
self.setContextMenu(self.menu)
self.build_menu()
self.show()
# see http://pyqt.sourceforge.net/Docs/PyQt5/signals_slots.html for more information
self.signal = MySignal()
self.signal.sig_no_args.connect(self.build_menu)
self.signal.sig_with_str.connect(self.print_string)
def build_menu(self):
''' This function should be called in order to rebuild
the right-click menu for the systray icon'''
global list_dict_streams
self.menu.clear()
exitAction = self.menu.addAction("Exit")
exitAction.triggered.connect(self._exit)
for x in list_dict_streams :
self.menu.addAction(x)
def print_string(self, str):
print(str)
def _exit(self):
QtCore.QCoreApplication.exit()
class MySignal(QtCore.QObject):
''' Why a whole new class? See here:
'''
sig_no_args = QtCore.pyqtSignal()
sig_with_str = QtCore.pyqtSignal(str)
list_dict_streams = ["1"]
def work_thread(trayIcon):
''' Will add one menu item to the systray menu every 5 seconds
and will send a signal with a string '''
global list_dict_streams
while True:
trayIcon.signal.sig_no_args.emit()
trayIcon.signal.sig_with_str.emit("String emitted")
list_dict_streams.append(str(len(list_dict_streams)+1))
time.sleep(5)
def main():
app = QtWidgets.QApplication(sys.argv)
trayIcon = SystemTrayIcon()
t = threading.Thread(target=work_thread, args=(trayIcon,))
t.daemon = True # otherwise the 'Exit' from the systray menu will not work
t.start()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
基本上你必须创建一个新的 class MySignal(QtCore.QObject)
why. I created a class with two examples - one that sends no arguments along another one that you can pass a string. You can of course define other arguments。然后在你的目标线程中创建这个 class 的新实例,并将来自那个 class 的函数连接到你的目标中的函数(在我的例子中是系统托盘图标)。之后,您现在可以像我在 while-loop.
中那样调用 emit(...)
函数
现在 Qt 很高兴,因为与直接从不同线程调用 trayIcon.build_menu()
相比,您只需发出一个信号。