[英]PyQt5 QObject: Cannot create children for a parent that is in a different thread
I am working in a menu system tray with PyQt5. 我正在使用PyQt5在菜单系统托盘中工作。 I am very new with PyQt5, and what I want to do is to trigger an action without the menu being blocked (multithreading). 我对PyQt5非常陌生,我想做的是在不阻止菜单(多线程)的情况下触发操作。 After having read in many places, I have come to the conclusion that using Qthread
should be the way to go (but if only I could understand how that class works...). 在许多地方阅读完之后,我得出的结论是使用Qthread
应该是Qthread
的方法(但是,如果我能理解该类的工作原理……)。 However, the use of threading
wouldn't be that bad either given that my application is very simple. 但是,考虑到我的应用程序非常简单,使用threading
也不会很糟糕。 So, I have tried the following code using import 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()
When I start my menu, everything is in place as I expect it. 当我开始菜单时,一切都已准备就绪。 Then, when I click on the menu Search
the action triggers the self.search_cast
method, and my menu gets populated with the list it finds. 然后,当我单击菜单Search
该操作将触发self.search_cast
方法,并且我的菜单中会填充找到的列表。 I can also see my application doing the searching without getting blocked but when it finishes I get the following errors: 我还可以看到我的应用程序在进行搜索时不会被阻塞,但是完成时会出现以下错误:
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.
After this, the menu is still "functional" in the sense that it is responsive but no more action can be triggered. 此后,菜单仍然具有“响应性”,但不能触发更多操作,因此它仍然是“功能性”的。 Additionally, it seems that no more threads are created. 此外,似乎没有更多的线程被创建。 I would be glad if somebody could explain me why is this happening?. 如果有人可以解释我为什么会发生这种情况,我将感到非常高兴。 I don't see the light... 我看不到光...
Update : 更新 :
I have created now a worker.py
that contains: 我现在创建了一个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()
Then I have added in the class menubar
the following: 然后,我在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.
And now I get the following error: 现在我得到以下错误:
AttributeError: 'casting' object has no attribute 'availablecc'
I make sure that actually the worker
is recovering availablecc
from an external class that I called cc
. 我确保实际上该worker
程序正在从我称为cc
的外部类中恢复availablecc
。 But for some reason is not being received by the menubar
class. 但是由于某些原因, menubar
类未收到该消息。 I am working based on this https://stackoverflow.com/a/33453124/1995261 我正在基于此https://stackoverflow.com/a/33453124/1995261
I will proceed to answer myself. 我将继续回答自己。 Inspired by https://stackoverflow.com/a/33453124/1995261 , I solved this by implementing the following: 受https://stackoverflow.com/a/33453124/1995261的启发,我通过实现以下方法解决了这一问题:
1) I created a worker.py
that executes the method _search_cast_
that was blocking the menu. 1)我创建了一个worker.py
,它执行阻止菜单的_search_cast_
方法。 When this method finishes searching, it emits two signals: a) one informing that he recovered the list
, and b) that the method has finished. 当此方法完成搜索时,它将发出两个信号: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) In the main.py
I dumped the following and I try to explain inside the code with comments: 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()
In this way, when searching for the list
the menu does not get blocked, no errors are shown on the screen and the number of threads
when monitoring them in activity monitor
stay correct. 这样,在搜索list
,菜单不会被阻止,屏幕上不会显示任何错误,并且在activity monitor
threads
时, threads
数保持正确。
I hope this helps people. 希望对大家有帮助。 For more precise information (I am still learning PyQt and my wording may not be very good), I suggest you to check the link that I posted above. 有关更精确的信息(我仍在学习PyQt,并且措辞可能不太好),建议您检查上面发布的链接。
Since this is the google top answer for this error and it took me longer than expected to work this out properly, I will share my very simple solution for Python 3 and PyQt 5 (if you change some imports it should work in PyQt4 too I guess). 由于这是该错误的Google最佳答案,并且花费了我比预期的时间才能正确解决此问题,因此,我将分享我针对Python 3和PyQt 5的非常简单的解决方案(如果您更改某些导入,它也应该在PyQt4中工作) )。
The situation I had was a systray icon with a right-click menu, that should be re-built when a different thread requests it. 我遇到的情况是带有右键单击菜单的系统托盘图标,当其他线程请求它时应重新构建该图标。 You can of course apply this to other problems where you want to communicate through thread limits. 您当然可以将其应用于要通过线程限制进行通信的其他问题。
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:
https://stackoverflow.com/a/25930966/2441026 '''
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()
Basically you have to create a new class MySignal(QtCore.QObject)
why . 基本上,您必须创建一个新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 . 您当然可以定义其他参数 。 Then in your target thread you create a new instance of this class and connect the functions from that class, to the functions inside your target (the systray icon in my case). 然后,在目标线程中,创建该类的新实例,并将该类中的函数连接到目标内部的函数(本例中为systray图标)。 After that you can now call the emit(...)
functions like I do in the while-loop. 在那之后,您现在可以像在while循环中一样调用emit(...)
函数。
Now Qt is happy as you just emit a signal compared to when you would call trayIcon.build_menu()
directly from a different thread. 现在,Qt很高兴,因为与直接从其他线程调用trayIcon.build_menu()
相比,您只发出一个信号即可。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.