简体   繁体   English

PyQt5 QObject:无法为处于不同线程中的父级创建子级

[英]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.

相关问题 QObject:无法为不同线程中的父级创建子级。 PyQt5 - QObject: Cannot create children for a parent that is in a different thread. PyQt5 Python PyQt5 线程 QObject:无法为不同线程中的父级创建子级 - Python PyQt5 threading QObject: Cannot create children for a parent that is in a different thread PyQt5 无法从线程更新进度条并收到错误“无法为不同线程中的父级创建子级” - PyQt5 cannot update progress bar from thread and received the error “Cannot create children for a parent that is in a different thread” 使用pyqt5和sqlite的python程序出错,无法为不同线程中的父级创建子级 - Error with python program with pyqt5 and sqlite, Cannot create children for a parent that is in a different thread PyQt4和flask:无法为处于不同线程中的父级创建子级 - PyQt4 & flask : Cannot create children for a parent that is in a different thread QObject:无法为处于不同线程中的父级创建子级。父级是QTextDocument,父级的线程是QThread,当前线程是QThread - QObject:Cannot create children for a parent that is in different thread.Parent is QTextDocument,parent's thread is QThread,current thread is QThread PyQt5:如何将QObject移动到主线程? - PyQt5: How to move a QObject to the main thread? QObject::setParent:无法设置父级,新父级在 Python 中的不同线程中 - QObject::setParent: Cannot set parent, new parent is in a different thread in Python PyQt多线程,无法为父级创建子级 - PyQt Multi Threading, Cannot create children for a parent PyQt5中的QObject,pyqtSignal - QObject, pyqtSignal in PyQt5
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM